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

✨ Add support negative polarity conditions #10550

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

fabriziopandini
Copy link
Member

What this PR does / why we need it:
This PR is an iteration on the condition proposal & corresponding utils allowing the implementation of conditions with negative polarity, which is a step toward latest K8s guidelines about conditions

Which issue(s) this PR fixes:
Part of #7635

/area api

cc @JoelSpeed @vincepri @theobarberbany

@fabriziopandini fabriziopandini added the area/api Issues or PRs related to the APIs label May 3, 2024
@k8s-ci-robot k8s-ci-robot added the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label May 3, 2024
@k8s-ci-robot k8s-ci-robot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label May 3, 2024
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please ask for approval from fabriziopandini. For more information see the Kubernetes Code Review Process.

The full list of commands accepted by this bot can be found 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

util/conditions/getter.go Outdated Show resolved Hide resolved
}

// PositiveFalseCondition returns a negative polarity condition with Status=false and the given type (Status=False has a positive meaning).
func PositiveFalseCondition(t clusterv1.ConditionType) *clusterv1.Condition {
Copy link
Member

Choose a reason for hiding this comment

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

Why can't this have a reason and message?

Copy link
Member Author

Choose a reason for hiding this comment

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

Back in time we introduced all those helpers to enforce a sort of consistency, and what we have to express when status is good (TrueCondition) does not allow to express reason and message & severity.
This func mirror the same for a condition with negative polarity(when status is False but with a positive meaning).

In other words, I tried to keep things consistent with what we have.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sometimes it can be awkward to require a reason, but there are cases where I think it can make sense.

I guess something like Available as a condition, True status, reason would be AsExpected maybe? But does it also make sense without?

But yeah, in general, I wonder if we should just have the reason and message allowed in all cases, rather than being prescriptive?

Are there kube conventions that say you should or shouldn't set reason/message in certain cases?

Copy link
Member Author

Choose a reason for hiding this comment

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

If a user wants flexibility, he/she/they can already do this today using the Set helper.
But as far as I know, in the last 4 years almost everyone used "prescriptive" helpers.

WRT to API conventions, before digging into "reason", let's keep in mind that the scope of this PR is to make one step toward API compliance, not the entire path 😉

Based on my understanding, reason is another area where the thinking about convention evolved over time. The current field description in corev1 types sort of captures it:

// reason contains a programmatic identifier indicating the reason for the condition's last transition.
// Producers of specific condition types may define expected values and meanings for this field,
// and whether the values are considered a guaranteed API.
// The value should be a CamelCase string.
// This field may not be empty.

My reading of this is that community is moving towards having reason everywhere, but they recognize producers of condition might have adopted different approaches.

CAPI as of today has two main differences from the API conventions WRT to how we handle reasons:

  • From a semantic point of view, we use reason to "explain" the current state, not the last transition (in some case the diff is subtle, but in controller we just observe what is the current state and surface it in conditions, we do not track how do we get to the current state)
  • We set reason only when condition have a negative meaning (as far as I remember setting a reason for positive state was considered redoundant, but it was 4 years ago so I might be wrong...)

This PR intentionally does not mean to change what CAPI today is doing WRT to reasons, because this could imply a way bigger impact that probably we should defer to when we cut a new API version.

Choose a reason for hiding this comment

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

This PR intentionally does not mean to change what CAPI today is doing WRT to reasons, because this could imply a way bigger impact that probably we should defer to when we cut a new API version.

This feels sensible for keeping the scope small here 😄

For my 0.02c: this approach 'feels' more right to me (less surprising)

From a semantic point of view, we use reason to "explain" the current state, not the last transition (in some case the diff is subtle, but in controller we just observe what is the current state and surface it in conditions, we do not track how do we get to the current state)

Copy link
Member

Choose a reason for hiding this comment

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

I also prefer explaining the current state not the transition. I'm not sure how we would even do this if a controller should be able to calculate the status from scratch. How could it even know what the previous state was

(explaining transitions in conditions also sounds very much like describing the "edges" of reconciliations)

@@ -96,6 +96,25 @@ func FalseCondition(t clusterv1.ConditionType, reason string, severity clusterv1
}
}

// NegativeTrueCondition returns a negative polarity condition with Status=True and the given type (Status=True has a negative meaning).
func NegativeTrueCondition(t clusterv1.ConditionType, reason string, severity clusterv1.ConditionSeverity, messageFormat string, messageArgs ...interface{}) *clusterv1.Condition {
Copy link
Member

Choose a reason for hiding this comment

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

why do NegativeTrueCondition and PositiveFalseCondition include the polarity in the func name?
Since all they are doing is set values in a clusterv1.Condition type wouldn't they be called just TrueCondition / falseCondition and let higher invoker decide on polarity?

Copy link
Member Author

Choose a reason for hiding this comment

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

See #10550 (comment), but the intent is to keep things consistent with what we have.

Just a note about the approach you are suggesting about let higher invoker decide on polarity: this is not in discussion.

The difference is that:

  • If we change the existing TrueCondition & FalseCondition, this will impact everyone and we end up to defer to users the task to enforce the same consistency that today we have with this helpers
  • With the proposed change instead, the users specific helpers for the NegativePolarity, which are consistent with the existing one for positive polarity

Last, but not least, there is already a Set helper with allow the users to decide which field to set (but in this case, it also imply the task to enforce consistency across the codebase/all the providers)

util/conditions/getter_test.go Outdated Show resolved Hide resolved
}

// PositiveFalseCondition returns a negative polarity condition with Status=false and the given type (Status=False has a positive meaning).
func PositiveFalseCondition(t clusterv1.ConditionType) *clusterv1.Condition {
Copy link
Contributor

Choose a reason for hiding this comment

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

Sometimes it can be awkward to require a reason, but there are cases where I think it can make sense.

I guess something like Available as a condition, True status, reason would be AsExpected maybe? But does it also make sense without?

But yeah, in general, I wonder if we should just have the reason and message allowed in all cases, rather than being prescriptive?

Are there kube conventions that say you should or shouldn't set reason/message in certain cases?

Copy link
Member

@sbueringer sbueringer left a comment

Choose a reason for hiding this comment

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

I have to spend more time on it, but NegativeTrueCondition and PositiveFalseCondition are really confusing me

The `Severity` field MUST be set only when `Status=False` and it is designed to provide a standard classification
of possible conditions failure `Reason`.
The `Severity` field MUST be set only when `Status=False` + positive polarity or when `Status=True` + negative polarity;
severity it is designed to provide a standard classification of possible conditions failure `Reason`.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
severity it is designed to provide a standard classification of possible conditions failure `Reason`.
severity is designed to provide a standard classification of possible conditions failure `Reason`.

Choose a reason for hiding this comment

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

I agree the wording is a touch confusing, I read this as:

Severity must only be set when something of positive polarity has 'Status=False' (something good is not true), or when something of negative polarity has 'Status =True' (something bad is true)

@@ -471,6 +473,7 @@ enhance the condition utilities to handle those situations in a generalized way.
- Risk: This proposal aims to be consistent with the target state of conditions in Kubernetes, but this
is still under definition (see [KEP](https://github.com/kubernetes/enhancements/pull/1624)).
- Mitigation: Periodically re-evaluate this proposal vs the Kubernetes KEP.
- 2024-05-03: Change allowing conditions with negative polarity goes into this direction
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
- 2024-05-03: Change allowing conditions with negative polarity goes into this direction
- 2024-05-03: Change to allow conditions without positive polarity goes into this direction

(~ means the same but this framing would also include conditions without polarity. I think Paused is basically neiter positive nor negative)

(same in Implementation History)

@@ -31,6 +32,13 @@ var (
falseInfo1 = FalseCondition("falseInfo1", "reason falseInfo1", clusterv1.ConditionSeverityInfo, "message falseInfo1")
falseWarning1 = FalseCondition("falseWarning1", "reason falseWarning1", clusterv1.ConditionSeverityWarning, "message falseWarning1")
falseError1 = FalseCondition("falseError1", "reason falseError1", clusterv1.ConditionSeverityError, "message falseError1")

negativePolarityConditions = sets.New("positive-false1", "negative-unknown1", "negative-trueInfo1", "negative-trueWarning1", "negative-trueError1")
Copy link
Member

Choose a reason for hiding this comment

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

Is a condition with positive polarity that is false really a negative polarity condition?

(or is the naming here just confusing)

@JoelSpeed
Copy link
Contributor

What are the next steps here? Do we want to discuss the Positive/Negative nomenclature in more depth? I'm not personally a huge fan of it and would rather users didn't have to worry, ie we could have helpers that allow you just to specify the state true or false explicitly.

But I can also see that this would make a major departure from where we are now and understand the desire to make a small step here to allow more flexibility in conditions. The PR does this in a sympathetic way and unblocks what is required.

I think the naming, and perhaps eventually removing the connotation of positive/negative could be done at a later point once we've understood more about the usage pattern of the negative/neutral condition types being introduced

@fabriziopandini
Copy link
Member Author

@JoelSpeed on the positive side there is agreement on the idea to support negative polarity conditions, that's already a good point to be.

The only thing we are discussing are the new helpers: NegativeTrueCondition, PositiveFalseCondition and the corresponding MarkNegativeTrue and MarkPositiveFalse

Probably, the most quick way forward is to just drop them, and let users willing to use negative polarity conditions to create Condition types "freely" use the Set(Condition) helper.
I personally think this could be accettable for now because I assume there will be just a few negative polarity conditions at the beginning, and we can come out with better helpers in a second time if necessary.

From the other side of the coin, having helpers really helped users so far in keeping things consistent across all providers, and I still see value in investing some time to figure out new names for helpers.

Another option, if we all agree that there is value in having helpers, is to start with the current names (or a variation of them we quickly agree upon) and eventually iterate in the future.

@fabriziopandini
Copy link
Member Author

fabriziopandini commented May 30, 2024

Might be using more explicit names for helpers might help:
NegativeTrueCondition --> TrueConditionWithNegativePolarity
PositiveFalseCondition --> FalseConditionWithNegativePolarity
MarkNegativeTrue --> MarkTrueWithNegativePolarity
MarkPositiveFalse --> MarkFalseWithNegativePolarity

(and BTW, the fact that we add helpers don't prevent users to create Condition types "freely" use the Set(Condition) helper)

@sbueringer
Copy link
Member

Definitely less confusing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/api Issues or PRs related to the APIs cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants