-
Notifications
You must be signed in to change notification settings - Fork 85
PoC for general processing of status conditions for multiple operands #447
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
Closed
Closed
Changes from 1 commit
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
20b3ff9
wild prototype for generic approach to status conditions
tremes f10427a
add Alertmanager operand to status conditions
tremes 739cd7c
add resourceDiscovery condition
tremes b78b40f
rebase
tremes 5680b97
share one condition type
tremes File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,207 @@ | ||
| package status | ||
|
|
||
| import ( | ||
| "fmt" | ||
|
|
||
| "github.com/rhobs/observability-operator/pkg/apis/monitoring/v1alpha1" | ||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||
| "k8s.io/apimachinery/pkg/runtime" | ||
| "sigs.k8s.io/controller-runtime/pkg/client" | ||
| ) | ||
|
|
||
| const ( | ||
| AvailableReason = "MonitoringStackAvailable" | ||
| ReconciledReason = "MonitoringStackReconciled" | ||
| FailedToReconcileReason = "FailedToReconcile" | ||
| ResourceSelectorIsNil = "ResourceSelectorNil" | ||
| AvailableMessage = "Monitoring Stack is available" | ||
| SuccessfullyReconciledMessage = "Monitoring Stack is successfully reconciled" | ||
| ResourceSelectorIsNilMessage = "No resources will be discovered, ResourceSelector is nil" | ||
| ResourceDiscoveryOnMessage = "Resource discovery is operational" | ||
| NoReason = "None" | ||
| available = "Available" | ||
| reconciled = "Reconciled" | ||
| ) | ||
|
|
||
| func UpdateConditions(stackObj client.Object, operands []Operand, recError error) ([]v1alpha1.Condition, error) { | ||
| var availableCon v1alpha1.Condition | ||
| var reconciledCon v1alpha1.Condition | ||
| conditions, err := getConditionsFromObject(stackObj) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| for _, opr := range operands { | ||
| if opr.affectsAvailability { | ||
| availableCon = updateAvailable(conditions, opr, stackObj.GetGeneration()) | ||
| } | ||
| if opr.affectsReconciled { | ||
| reconciledCon = updateReconciled(conditions, opr, stackObj.GetGeneration(), recError) | ||
| } | ||
| } | ||
|
|
||
| resourceDiscoveryCon, err := updateResourceDiscovery(stackObj) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return []v1alpha1.Condition{ | ||
| availableCon, | ||
| reconciledCon, | ||
| *resourceDiscoveryCon, | ||
| }, nil | ||
| } | ||
|
|
||
| // updateAvailable gets existing "Available" condition and updates its parameters | ||
| // based on the operand "Available" condition | ||
| func updateAvailable(conditions []v1alpha1.Condition, opr Operand, generation int64) v1alpha1.Condition { | ||
| ac, err := getConditionByType(conditions, v1alpha1.AvailableCondition) | ||
| if err != nil { | ||
| ac = v1alpha1.Condition{ | ||
| Type: v1alpha1.AvailableCondition, | ||
| Status: v1alpha1.ConditionUnknown, | ||
| Reason: NoReason, | ||
| LastTransitionTime: metav1.Now(), | ||
| } | ||
| } | ||
|
|
||
| operandAvailable, err := opr.getConditionByType(available) | ||
|
|
||
| if err != nil { | ||
| ac.Status = v1alpha1.ConditionUnknown | ||
| ac.Reason = fmt.Sprintf("%sNotAvailable", opr.name) | ||
| ac.Message = fmt.Sprintf("Cannot read %s status conditions", opr.name) | ||
| ac.LastTransitionTime = metav1.Now() | ||
| return ac | ||
| } | ||
| // MonitoringStack status will not be updated if there is a difference between the operand generation | ||
| // and the operand ObservedGeneration. This can occur, for example, in the case of an invalid operand configuration. | ||
| if operandAvailable.ObservedGeneration != opr.Object.GetGeneration() { | ||
| return ac | ||
| } | ||
|
|
||
| if operandAvailable.Status != "True" { | ||
| ac.Status = prometheusStatusToMSStatus(operandAvailable.Status) | ||
| if operandAvailable.Status == "Degraded" { | ||
| ac.Reason = fmt.Sprintf("%sDegraded", opr.name) | ||
| } else { | ||
| ac.Reason = fmt.Sprintf("%sNotAvailable", opr.name) | ||
| } | ||
| ac.Message = operandAvailable.Message | ||
| ac.LastTransitionTime = metav1.Now() | ||
| return ac | ||
| } | ||
| ac.Status = v1alpha1.ConditionTrue | ||
| ac.Reason = AvailableReason | ||
| ac.Message = AvailableMessage | ||
| ac.ObservedGeneration = generation | ||
| ac.LastTransitionTime = metav1.Now() | ||
| return ac | ||
| } | ||
|
|
||
| // updateReconciled updates "Reconciled" conditions based on the provided error value and | ||
| // the operand "Reconciled" condition | ||
| func updateReconciled(conditions []v1alpha1.Condition, opr Operand, generation int64, reconcileErr error) v1alpha1.Condition { | ||
| rc, cErr := getConditionByType(conditions, v1alpha1.ReconciledCondition) | ||
| if cErr != nil { | ||
| rc = v1alpha1.Condition{ | ||
| Type: v1alpha1.ReconciledCondition, | ||
| Status: v1alpha1.ConditionUnknown, | ||
| Reason: NoReason, | ||
| LastTransitionTime: metav1.Now(), | ||
| } | ||
| } | ||
| if reconcileErr != nil { | ||
| rc.Status = v1alpha1.ConditionFalse | ||
| rc.Message = reconcileErr.Error() | ||
| rc.Reason = FailedToReconcileReason | ||
| rc.LastTransitionTime = metav1.Now() | ||
| return rc | ||
| } | ||
| operandReconciled, reconcileErr := opr.getConditionByType(reconciled) | ||
|
|
||
| if reconcileErr != nil { | ||
| rc.Status = v1alpha1.ConditionUnknown | ||
| rc.Reason = fmt.Sprintf("%sNotReconciled", opr.name) | ||
| rc.Message = fmt.Sprintf("Cannot read %s status conditions", opr.name) | ||
| rc.LastTransitionTime = metav1.Now() | ||
| return rc | ||
| } | ||
|
|
||
| if operandReconciled.ObservedGeneration != opr.Object.GetGeneration() { | ||
| return rc | ||
| } | ||
|
|
||
| if operandReconciled.Status != "True" { | ||
| rc.Status = prometheusStatusToMSStatus(operandReconciled.Status) | ||
| rc.Reason = fmt.Sprintf("%sNotReconciled", opr.name) | ||
| rc.Message = operandReconciled.Message | ||
| rc.LastTransitionTime = metav1.Now() | ||
| return rc | ||
| } | ||
| rc.Status = v1alpha1.ConditionTrue | ||
| rc.Reason = ReconciledReason | ||
| rc.Message = SuccessfullyReconciledMessage | ||
| rc.ObservedGeneration = generation | ||
| rc.LastTransitionTime = metav1.Now() | ||
| return rc | ||
| } | ||
|
|
||
| func getConditionByType(conditions []v1alpha1.Condition, t v1alpha1.ConditionType) (v1alpha1.Condition, error) { | ||
| for _, c := range conditions { | ||
| if c.Type == t { | ||
| return c, nil | ||
| } | ||
| } | ||
| return v1alpha1.Condition{}, fmt.Errorf("ERROR: condition type %v not found", t) | ||
| } | ||
|
|
||
| func getConditionsFromObject(o client.Object) ([]v1alpha1.Condition, error) { | ||
| unstrObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(o) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| var conditions []v1alpha1.Condition | ||
| untypedCon, ok, err := unstructured.NestedSlice(unstrObj, "status", "conditions") | ||
| // if no conditions found, return empty conditions | ||
| if !ok { | ||
| return conditions, nil | ||
| } | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| for _, untypedC := range untypedCon { | ||
| cMap, ok := untypedC.(map[string]interface{}) | ||
| if !ok { | ||
| return nil, fmt.Errorf("converting to map[string]interface{}: %v", untypedC) | ||
| } | ||
| conditions = append(conditions, v1alpha1.Condition{ | ||
| Type: v1alpha1.ConditionType(convert[string](cMap["type"])), | ||
| Reason: convert[string](cMap["reason"]), | ||
| Status: v1alpha1.ConditionStatus(convert[string](cMap["status"])), | ||
| Message: convert[string](cMap["message"]), | ||
| ObservedGeneration: convert[int64](cMap["observedGeneration"]), | ||
| LastTransitionTime: convert[metav1.Time](cMap["lastTransitionTime"]), | ||
| }) | ||
| } | ||
| return conditions, nil | ||
|
|
||
| } | ||
|
|
||
| func prometheusStatusToMSStatus(ps string) v1alpha1.ConditionStatus { | ||
| switch ps { | ||
| // Prometheus "Available" condition with status "Degraded" is reported as "Available" condition | ||
| // with status false | ||
| case "Degraded": | ||
| return v1alpha1.ConditionFalse | ||
| case "True": | ||
| return v1alpha1.ConditionTrue | ||
| case "False": | ||
| return v1alpha1.ConditionFalse | ||
| case "Unknown": | ||
| return v1alpha1.ConditionUnknown | ||
| default: | ||
| return v1alpha1.ConditionUnknown | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps we could avoid this function by using some interface like:
And require types like
MonitoringStackto implement this interface.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jan--f I was updating this PR this morning a bit (locally so far) and I think this ^^ can probably make few things more simple here.
What do you think about this idea?
TheOperandtype introduced here can then use thisStatusReportingtype instead ofclient.Object(but it's just theory. I haven't tried yet)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm actually the theory was not correct :) I guess it won't help much with the conditions reading from the operands.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah I see.
Could we perhaps use generics to implement the
update*functions? The idea would be to simply provide a helper function that implements what's described in https://docs.google.com/presentation/d/1f-0UtZB8RvIlw49pBYCLfLfWeRflUYo2bJvkKpL2sRo/edit#slide=id.g28d2c6c7c1e_0_10wdyt?
we certainly could make do without this, but maybe this is nice chance to testdrive golang generics :)