Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce error util ErrGeneric for invalid combination #638

Merged
merged 2 commits into from
Sep 12, 2019
Merged

Introduce error util ErrGeneric for invalid combination #638

merged 2 commits into from
Sep 12, 2019

Conversation

nak3
Copy link
Contributor

@nak3 nak3 commented Sep 6, 2019

example 1. knative/serving#5382
example 2. following combination in spec.traffic.

  traffic:
  - latestRevision: true
    revisionName: hello-example-dk7nd
    percent: 100

But there are no error util for them, so we need to create
custom error like knative/serving@c1583f3
or ErrInvalidValue.

The custom error will make code complicated and ErrInvalidValue is
not debug friendly.

To solve it, this patch introduces an util func ErrInvalidCombination.

@googlebot googlebot added the cla: yes Indicates the PR's author has signed the CLA. label Sep 6, 2019
@knative-prow-robot knative-prow-robot added the size/S Denotes a PR that changes 10-29 lines, ignoring generated files. label Sep 6, 2019
Sometimes valid value becomes invalid value by combination.

example 1. knative/serving#5382
example 2. following combination in `spec.traffic`.

```
  traffic:
  - latestRevision: true
    revisionName: hello-example-dk7nd
    percent: 100
```

But there are no error util for them, so we need to create
custom error like knative/serving@c1583f3
or `ErrInvalidValue`.

The custom error will make code complicated and `ErrInvalidValue` is
not debug friendly.

To solve it, this patch introduces an util func `ErrInvalidCombination`.
@nak3 nak3 changed the title Sometimes valid value becomes invalid value by combination. Introduce error util ErrInvalidCombination for invalid combination Sep 6, 2019
@vagababov
Copy link
Contributor

/lgtm
/assign @n3wscott mattmoor

@knative-prow-robot knative-prow-robot added the lgtm Indicates that a PR is ready to be merged. label Sep 6, 2019
func ErrInvalidValue(value interface{}, fieldPath string) *FieldError {
return &FieldError{
Message: fmt.Sprintf("invalid value: %v", value),
Paths: []string{fieldPath},
}
}

// ErrInvalidCombination constructs a FieldError for a field that has received an
// invalid combination of values.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a cross-field validation -- it seems like this might be better represented (given your example) as:

Can't set both latestRevision and revisionName in

What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is good idea, but it cannot be accurate. I would like to represent
following differences.

example1:

  • errror (failureThreshold is invalid when periodSeconds is zero)
        readinessProbe:
          periodSeconds: 0
          failureThreshold: 3
  • OK (failureThreshold is valid when periodSeconds is not zero.)
        readinessProbe:
          periodSeconds: 5
          failureThreshold: 3

example2:

  • error (revisionName is invalid when latestRevision is true)
  traffic:
  - latestRevision: true
    revisionName: hello-example-dk7nd
    percent: 100
  • OK (revisionName is valid when latestRevision is false.)
  traffic:
  - latestRevision: false
    revisionName: hello-example-dk7nd
    percent: 100

Can't set both message conflicts with OK scenario above - especially example1.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should example1 and example2 have the same error message?

I think the top priority is clear error messages; the current message "invalid value 0, when failureThreshold is 3" seems confusing for the following case:

   readinessProbe:
    failureThreshold: 3

I think at a minimum you need to mention all the field names which have conflicts.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dgerd @evankanderson thank you for your comments.

I think at a minimum you need to mention all the field names which have conflicts.

Yes, all the field names are covered by Paths: []string{fieldPath}. For example, errror message for

   readinessProbe:
    failureThreshold: 3

... is

invalid value 3, when periodSeconds is 0: spec.template.spec.containers[0].readinessProbe.failureThreshold

or

invalid value 0, when failureThreshold is 3: spec.template.spec.containers[0].readinessProbe.periodSeconds

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see...

I'd expected the fieldPath would be for the parent object (or both leaves), rather than for one of the fields in the object but not the other. My thought:

periodSeconds must be greater than 0 if failureThreshold is set: spec.template.spec.containers[0].readinessProbe

This would basically be:

if c.ReadinessProbe.PeriodSeconds <= 0 && c.ReadinessProbe.FailureThreshold > 0 {
  return &apis.FieldError{  // Or err.Also(&apis.FieldError{...
    Message: "periodSeconds must be greater than 0 if failureThreshold is set"
    Paths: "readinessProbe"
  }
}

I don't want to knee-jerk say "do it my way"; I am wondering if we have enough cases of this error to make a factory function necessary.

Copy link
Contributor Author

@nak3 nak3 Sep 11, 2019

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for doing this! The first three cases can definitely be improved:

  • v1alpha1/service_validation.go:
    Should probably read: "May not set rolloutPercent for a single revision" (The error is on the length of another field, not the values in the list.)
  • v1beta1/route_validation.go:
    Should probably read: "May not set revisionName and latestRevision"
  • v1/route_validation.go:
    A duplicate of the above code, because Go inheritance is hard.
  • serving/k8s_validation.go:
    Both of these use the &apis.FieldError{Message: "...", Paths: []string{...}} format I suggested, with the message "... is disallowed when periodSeconds is zero"

If you can get a consistently-good message for these use cases, go ahead. Right now, I think these error messages look like:

  • "invalid value 33, when len(revisions) is 1: spec.release.rolloutPercent"
  • "invalid value true, when revisionName is foo: spec.traffic[2].latestRevision"
  • "invalid value 3, when periodSeconds is 0: spec.template.spec.containers[0].readinessProbe.failureThreshold"

More generically, I think the problem is that there are two field values that conflict -- this code suggests that users need to change one field (fieldPath), rather than suggesting that either fieldPath or key could be changed to get a working system.

You can also add a func GenericError(diagnostic string, paths ...string) *FieldError if that would help in these cases -- that would allow for the different error strings for the different cases, and would look like:

  err.Also(api.GenericError("May not set rolloutPercent for a single revision", apis.CurrentField))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I agree to GenericError. It will make shorten a few line, but it shortens many unit test codes, so I believe that it has value.

// invalid combination of values.
func ErrInvalidCombination(bad, value interface{}, key, fieldPath string) *FieldError {
return &FieldError{
Message: fmt.Sprintf("invalid value %v, when %s is %v", bad, key, value),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be confusing as failureThreshold is a key and not a value. To move towards Evan's suggestion, perhaps rephrase as: "%s must be unset when %s is %v"

For example,
failureThreshold must be unset when periodSeconds is zero <-- Works for current validation errors
foo must be unset when bar is present <-- Works for strict A or B errors where value does not matter

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I wanted to use the arg bad as a value, not key. Invalid value's key could be printed by Paths: []string{fieldPath}.

func ErrInvalidValue(value interface{}, fieldPath string) *FieldError {
return &FieldError{
Message: fmt.Sprintf("invalid value: %v", value),
Paths: []string{fieldPath},
}
}

// ErrInvalidCombination constructs a FieldError for a field that has received an
// invalid combination of values.
func ErrInvalidCombination(bad, value interface{}, key, fieldPath string) *FieldError {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ordering of fields seems confusing here and easy to get wrong.

Suggested change
func ErrInvalidCombination(bad, value interface{}, key, fieldPath string) *FieldError {
func ErrInvalidCombination(invalidKey, conflictingKey, conflictingValue interface{}, fieldPath string) *FieldError {

@knative-prow-robot knative-prow-robot removed the lgtm Indicates that a PR is ready to be merged. label Sep 12, 2019
@nak3
Copy link
Contributor Author

nak3 commented Sep 12, 2019

Updated the code to introduce ErrGeneric instead of ErrInvalidCombination after the discussion #638 (comment)

Copy link
Member

@evankanderson evankanderson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm
/approve

Thanks for your patience, and for digging out the various cases where this could apply!

@knative-prow-robot knative-prow-robot added the lgtm Indicates that a PR is ready to be merged. label Sep 12, 2019
@knative-prow-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: evankanderson, nak3

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@knative-prow-robot knative-prow-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Sep 12, 2019
@knative-prow-robot knative-prow-robot merged commit 3415797 into knative:master Sep 12, 2019
@nak3 nak3 deleted the err-invalid-combi branch September 13, 2019 03:30
knative-prow-robot pushed a commit that referenced this pull request Sep 13, 2019
* Introduce error util ErrInvalidCombination for invalid combination (#638)

* Introduce error util ErrInvalidCombination for invalid combination

Sometimes valid value becomes invalid value by combination.

example 1. knative/serving#5382
example 2. following combination in `spec.traffic`.

```
  traffic:
  - latestRevision: true
    revisionName: hello-example-dk7nd
    percent: 100
```

But there are no error util for them, so we need to create
custom error like knative/serving@c1583f3
or `ErrInvalidValue`.

The custom error will make code complicated and `ErrInvalidValue` is
not debug friendly.

To solve it, this patch introduces an util func `ErrInvalidCombination`.

* Introduce ErrGeneric instead of ErrInvalidCombination

* golang format tools (#672)

Produced via:
  `gofmt -s -w $(find -path './vendor' -prune -o -type f -name '*.go' -print))`
  `goimports -w $(find -name '*.go' | grep -v vendor)`
/assign mattmoor
@nak3 nak3 changed the title Introduce error util ErrInvalidCombination for invalid combination Introduce error util ErrGeneric for invalid combination Sep 17, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cla: yes Indicates the PR's author has signed the CLA. lgtm Indicates that a PR is ready to be merged. size/S Denotes a PR that changes 10-29 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants