From 20b3ff906fb0089b84d171416f054ab2a29b6332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Reme=C5=A1?= Date: Fri, 15 Mar 2024 13:57:30 +0100 Subject: [PATCH 1/5] wild prototype for generic approach to status conditions --- .../monitoring/monitoring-stack/controller.go | 11 +- pkg/status/conditions.go | 207 +++++++++ pkg/status/conditions_test.go | 412 ++++++++++++++++++ pkg/status/operand.go | 84 ++++ 4 files changed, 713 insertions(+), 1 deletion(-) create mode 100644 pkg/status/conditions.go create mode 100644 pkg/status/conditions_test.go create mode 100644 pkg/status/operand.go diff --git a/pkg/controllers/monitoring/monitoring-stack/controller.go b/pkg/controllers/monitoring/monitoring-stack/controller.go index 52068406a..2409a4bb3 100644 --- a/pkg/controllers/monitoring/monitoring-stack/controller.go +++ b/pkg/controllers/monitoring/monitoring-stack/controller.go @@ -36,6 +36,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" stack "github.com/rhobs/observability-operator/pkg/apis/monitoring/v1alpha1" + "github.com/rhobs/observability-operator/pkg/status" + + "github.com/go-logr/logr" + monv1 "github.com/rhobs/obo-prometheus-operator/pkg/apis/monitoring/v1" ) type resourceManager struct { @@ -184,7 +188,12 @@ func (rm resourceManager) updateStatus(ctx context.Context, req ctrl.Request, ms logger.Info("Failed to get prometheus object", "err", err) return ctrl.Result{RequeueAfter: 2 * time.Second} } - ms.Status.Conditions = updateConditions(ms, prom, recError) + + ms.Status.Conditions, err = status.UpdateConditions(ms, operands, recError) + if err != nil { + logger.Info("Failed to update status conditions", "err", err) + return ctrl.Result{RequeueAfter: 2 * time.Second} + } err = rm.k8sClient.Status().Update(ctx, ms) if err != nil { logger.Info("Failed to update status", "err", err) diff --git a/pkg/status/conditions.go b/pkg/status/conditions.go new file mode 100644 index 000000000..f2e28ea8a --- /dev/null +++ b/pkg/status/conditions.go @@ -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 + } +} diff --git a/pkg/status/conditions_test.go b/pkg/status/conditions_test.go new file mode 100644 index 000000000..b14540f7f --- /dev/null +++ b/pkg/status/conditions_test.go @@ -0,0 +1,412 @@ +package status + +import ( + "testing" + + monv1 "github.com/rhobs/obo-prometheus-operator/pkg/apis/monitoring/v1" + "github.com/rhobs/observability-operator/pkg/apis/monitoring/v1alpha1" + "gotest.tools/v3/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func TestUpdateAvailable(t *testing.T) { + tt := []struct { + name string + operand Operand + previousConditions []v1alpha1.Condition + generation int64 + expectedResult v1alpha1.Condition + }{ + { + name: "conditions not changed when Prometheus Available", + previousConditions: []v1alpha1.Condition{ + { + Type: v1alpha1.AvailableCondition, + Status: v1alpha1.ConditionTrue, + ObservedGeneration: 1, + Reason: AvailableReason, + Message: AvailableMessage, + }, + }, + operand: Operand{ + name: "Prometheus", + affectsAvailability: true, + affectsReconciled: true, + Object: &monv1.Prometheus{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 1, + }, + Status: monv1.PrometheusStatus{ + Conditions: []monv1.Condition{ + { + Type: monv1.Available, + Status: monv1.ConditionTrue, + ObservedGeneration: 1, + }, + }}}, + }, + generation: 1, + expectedResult: v1alpha1.Condition{ + Type: v1alpha1.AvailableCondition, + Status: v1alpha1.ConditionTrue, + ObservedGeneration: 1, + Reason: AvailableReason, + Message: AvailableMessage, + }, + }, + { + name: "cannot read Prometheus conditions", + previousConditions: []v1alpha1.Condition{ + { + Type: v1alpha1.AvailableCondition, + Status: v1alpha1.ConditionTrue, + ObservedGeneration: 1, + Reason: AvailableReason, + Message: AvailableMessage, + }, + }, + generation: 1, + operand: Operand{ + name: "Prometheus", + affectsAvailability: true, + affectsReconciled: true, + Object: &monv1.Prometheus{}, + }, + expectedResult: v1alpha1.Condition{ + Type: v1alpha1.AvailableCondition, + Status: v1alpha1.ConditionUnknown, + ObservedGeneration: 1, + Reason: "PrometheusNotAvailable", + Message: "Cannot read Prometheus status conditions", + }, + }, + { + name: "degraded Prometheus conditions", + previousConditions: []v1alpha1.Condition{ + { + Type: v1alpha1.AvailableCondition, + Status: v1alpha1.ConditionTrue, + ObservedGeneration: 1, + Reason: AvailableReason, + Message: AvailableMessage, + }, + }, + generation: 1, + operand: Operand{ + name: "Prometheus", + affectsAvailability: true, + affectsReconciled: true, + Object: &monv1.Prometheus{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 1, + }, + Status: monv1.PrometheusStatus{ + Conditions: []monv1.Condition{ + { + Type: monv1.Available, + Status: monv1.ConditionDegraded, + ObservedGeneration: 1, + }, + }}}, + }, + expectedResult: v1alpha1.Condition{ + Type: v1alpha1.AvailableCondition, + Status: v1alpha1.ConditionFalse, + ObservedGeneration: 1, + Reason: "PrometheusDegraded", + }, + }, + { + name: "Prometheus observed generation is different from the Prometheus generation", + previousConditions: []v1alpha1.Condition{ + { + Type: v1alpha1.AvailableCondition, + Status: v1alpha1.ConditionTrue, + ObservedGeneration: 2, + Reason: AvailableReason, + Message: AvailableMessage, + }, + }, + generation: 1, + operand: Operand{ + name: "Prometheus", + affectsAvailability: true, + affectsReconciled: true, + Object: &monv1.Prometheus{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 3, + }, + Status: monv1.PrometheusStatus{ + Conditions: []monv1.Condition{ + { + Type: monv1.Available, + Status: monv1.ConditionFalse, + ObservedGeneration: 2, + }, + }}}, + }, + expectedResult: v1alpha1.Condition{ + Type: v1alpha1.AvailableCondition, + Status: v1alpha1.ConditionTrue, + ObservedGeneration: 2, + Reason: AvailableReason, + Message: AvailableMessage, + }, + }, + } + + for _, test := range tt { + res := updateAvailable(test.previousConditions, test.operand, test.generation) + assert.Check(t, test.expectedResult.Equal(res), "%s - expected:\n %v\n and got:\n %v\n", test.name, test.expectedResult, res) + } +} + +func TestUpdateReconciled(t *testing.T) { + tt := []struct { + name string + operand Operand + previousConditions []v1alpha1.Condition + generation int64 + recError error + expectedResult v1alpha1.Condition + }{ + { + name: "conditions not changed when Prometheus Available", + previousConditions: []v1alpha1.Condition{ + { + Type: v1alpha1.ReconciledCondition, + Status: v1alpha1.ConditionTrue, + ObservedGeneration: 1, + Reason: ReconciledReason, + Message: SuccessfullyReconciledMessage, + }, + }, + recError: nil, + generation: 1, + operand: Operand{ + name: "Prometheus", + affectsAvailability: true, + affectsReconciled: true, + Object: &monv1.Prometheus{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 1, + }, + Status: monv1.PrometheusStatus{ + Conditions: []monv1.Condition{ + { + Type: monv1.Reconciled, + Status: monv1.ConditionTrue, + ObservedGeneration: 1, + }, + }}}, + }, + expectedResult: v1alpha1.Condition{ + Type: v1alpha1.ReconciledCondition, + Status: v1alpha1.ConditionTrue, + ObservedGeneration: 1, + Reason: ReconciledReason, + Message: SuccessfullyReconciledMessage, + }, + }, + { + name: "cannot read Prometheus status conditions", + previousConditions: []v1alpha1.Condition{ + { + Type: v1alpha1.ReconciledCondition, + Status: v1alpha1.ConditionTrue, + ObservedGeneration: 1, + Reason: ReconciledReason, + Message: SuccessfullyReconciledMessage, + }, + }, + recError: nil, + generation: 1, + operand: Operand{ + name: "Prometheus", + affectsAvailability: true, + affectsReconciled: true, + Object: &monv1.Prometheus{}, + }, + expectedResult: v1alpha1.Condition{ + Type: v1alpha1.ReconciledCondition, + Status: v1alpha1.ConditionUnknown, + ObservedGeneration: 1, + Reason: "PrometheusNotReconciled", + Message: "Cannot read Prometheus status conditions", + }, + }, + { + name: "degraded Prometheus conditions", + previousConditions: []v1alpha1.Condition{ + { + Type: v1alpha1.ReconciledCondition, + Status: v1alpha1.ConditionTrue, + ObservedGeneration: 1, + Reason: ReconciledReason, + Message: SuccessfullyReconciledMessage, + }, + }, + recError: nil, + generation: 1, + operand: Operand{ + name: "Prometheus", + affectsAvailability: true, + affectsReconciled: true, + Object: &monv1.Prometheus{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 1, + }, + Status: monv1.PrometheusStatus{ + Conditions: []monv1.Condition{ + { + Type: monv1.Reconciled, + Status: monv1.ConditionDegraded, + ObservedGeneration: 1, + }, + }}}, + }, + expectedResult: v1alpha1.Condition{ + Type: v1alpha1.ReconciledCondition, + Status: v1alpha1.ConditionFalse, + ObservedGeneration: 1, + Reason: "PrometheusNotReconciled", + }, + }, + { + name: "Prometheus observed generation is different from the Prometheus generation", + previousConditions: []v1alpha1.Condition{ + { + Type: v1alpha1.ReconciledCondition, + Status: v1alpha1.ConditionTrue, + ObservedGeneration: 2, + Reason: ReconciledReason, + Message: SuccessfullyReconciledMessage, + }, + }, + recError: nil, + generation: 1, + operand: Operand{ + name: "Prometheus", + affectsAvailability: true, + affectsReconciled: true, + Object: &monv1.Prometheus{ + ObjectMeta: metav1.ObjectMeta{ + Generation: 3, + }, + Status: monv1.PrometheusStatus{ + Conditions: []monv1.Condition{ + { + Type: monv1.Reconciled, + Status: monv1.ConditionFalse, + ObservedGeneration: 2, + }, + }}}, + }, + expectedResult: v1alpha1.Condition{ + Type: v1alpha1.ReconciledCondition, + Status: v1alpha1.ConditionTrue, + ObservedGeneration: 2, + Reason: ReconciledReason, + Message: SuccessfullyReconciledMessage, + }, + }, + } + + for _, test := range tt { + res := updateReconciled(test.previousConditions, test.operand, test.generation, test.recError) + assert.Check(t, test.expectedResult.Equal(res), "%s - expected:\n %v\n and got:\n %v\n", test.name, test.expectedResult, res) + } +} + +func TestUpdateResourceDiscovery(t *testing.T) { + transitionTime := metav1.Now() + tt := []struct { + name string + msWithConditions *v1alpha1.MonitoringStack + expectedResults v1alpha1.Condition + }{ + { + name: "set resource discovery true when ResourceSelector not nil", + msWithConditions: &v1alpha1.MonitoringStack{ + Spec: v1alpha1.MonitoringStackSpec{ + ResourceSelector: &metav1.LabelSelector{}, + }, + }, + expectedResults: v1alpha1.Condition{ + Type: v1alpha1.ResourceDiscoveryCondition, + Status: v1alpha1.ConditionTrue, + Reason: NoReason, + Message: ResourceDiscoveryOnMessage, + }, + }, + { + name: "set resource discovery false when ResourceSelector is nil", + msWithConditions: &v1alpha1.MonitoringStack{ + Spec: v1alpha1.MonitoringStackSpec{ + ResourceSelector: nil, + }, + }, + expectedResults: v1alpha1.Condition{ + Type: v1alpha1.ResourceDiscoveryCondition, + Status: v1alpha1.ConditionFalse, + Reason: ResourceSelectorIsNil, + Message: ResourceSelectorIsNilMessage, + LastTransitionTime: transitionTime, + }, + }, + } + + for _, test := range tt { + res, err := updateResourceDiscovery(test.msWithConditions) + assert.NilError(t, err) + assert.Check(t, test.expectedResults.Equal(*res), "%s - expected:\n %v\n and got:\n %v\n", test.name, test.expectedResults, res) + } +} + +func TestGetConditionsFromObject(t *testing.T) { + tests := []struct { + name string + testObject client.Object + expectedConditions []v1alpha1.Condition + }{ + { + name: "empty monitoring stack", + testObject: &v1alpha1.MonitoringStack{}, + expectedConditions: nil, + }, + { + name: "monitoring stack with some valid conditions", + testObject: &v1alpha1.MonitoringStack{ + Status: v1alpha1.MonitoringStackStatus{ + Conditions: []v1alpha1.Condition{ + { + Type: available, + Status: v1alpha1.ConditionTrue, + Reason: AvailableReason, + Message: AvailableMessage, + ObservedGeneration: 1, + }, + }, + }, + }, + expectedConditions: []v1alpha1.Condition{ + { + Type: available, + Status: v1alpha1.ConditionTrue, + Reason: AvailableReason, + Message: AvailableMessage, + ObservedGeneration: 1, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + conditions, err := getConditionsFromObject(tt.testObject) + assert.NilError(t, err) + assert.DeepEqual(t, conditions, tt.expectedConditions) + }) + } +} diff --git a/pkg/status/operand.go b/pkg/status/operand.go new file mode 100644 index 000000000..168c1dec4 --- /dev/null +++ b/pkg/status/operand.go @@ -0,0 +1,84 @@ +package status + +import ( + "fmt" + + 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" +) + +type conditionHelper struct { + Type string + Status string + Reason string + ObservedGeneration int64 + Message string + LastTransitionTime metav1.Time +} + +func convert[T comparable](v interface{}) T { + var r T + converted, ok := v.(T) + if !ok { + return r + } + return converted +} + +// Operand is a wrapper type around client.Object +// It is a helper type to evaluate status condtions +// in generic fashion +type Operand struct { + name string + affectsAvailability bool + affectsReconciled bool + Object client.Object +} + +func NewOperand(obj client.Object, affectsStackAvailability bool, affectsStackReconciled bool) *Operand { + name := obj.GetObjectKind().GroupVersionKind().Kind + return &Operand{ + Object: obj, + name: name, + affectsAvailability: affectsStackAvailability, + affectsReconciled: affectsStackReconciled, + } +} + +// getConditionByType converts the operand object to unstructured and +// then tries to find conidtion with provided type. +func (o *Operand) getConditionByType(ctype string) (*conditionHelper, error) { + unstrObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(o.Object) + if err != nil { + return nil, err + } + + untypedCon, ok, err := unstructured.NestedSlice(unstrObj, "status", "conditions") + if !ok { + return nil, fmt.Errorf("conditions not available") + } + 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) + } + + if t, ok := cMap["type"]; ok { + if t == ctype { + return &conditionHelper{ + Type: convert[string](t), + Status: convert[string](cMap["status"]), + ObservedGeneration: convert[int64](cMap["observedGeneration"]), + Message: convert[string](cMap["message"]), + }, nil + } + } + } + return nil, fmt.Errorf("can't find any condition with type %s ", ctype) +} From f10427a9c9ccf71faf4aef80cf1a53faffbe8d44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Reme=C5=A1?= Date: Wed, 20 Mar 2024 08:52:48 +0100 Subject: [PATCH 2/5] add Alertmanager operand to status conditions --- .../monitoring/monitoring-stack/controller.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pkg/controllers/monitoring/monitoring-stack/controller.go b/pkg/controllers/monitoring/monitoring-stack/controller.go index 2409a4bb3..c942cff97 100644 --- a/pkg/controllers/monitoring/monitoring-stack/controller.go +++ b/pkg/controllers/monitoring/monitoring-stack/controller.go @@ -177,6 +177,7 @@ func (rm resourceManager) Reconcile(ctx context.Context, req ctrl.Request) (ctrl } func (rm resourceManager) updateStatus(ctx context.Context, req ctrl.Request, ms *stack.MonitoringStack, recError error) ctrl.Result { + var operands []status.Operand var prom monv1.Prometheus logger := rm.logger.WithValues("stack", req.NamespacedName) key := client.ObjectKey{ @@ -188,6 +189,16 @@ func (rm resourceManager) updateStatus(ctx context.Context, req ctrl.Request, ms logger.Info("Failed to get prometheus object", "err", err) return ctrl.Result{RequeueAfter: 2 * time.Second} } + operands = append(operands, *status.NewOperand(&prom, true, true)) + if !ms.Spec.AlertmanagerConfig.Disabled { + var am monv1.Alertmanager + err := rm.k8sClient.Get(ctx, key, &am) + if err != nil { + logger.Info("Failed to get alertmanager object", "err", err) + return ctrl.Result{RequeueAfter: 2 * time.Second} + } + operands = append(operands, *status.NewOperand(&am, false, true)) + } ms.Status.Conditions, err = status.UpdateConditions(ms, operands, recError) if err != nil { From 739cd7ca1fc562f3e9ec1e2df6074003cb6d6212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Reme=C5=A1?= Date: Thu, 21 Mar 2024 14:12:44 +0100 Subject: [PATCH 3/5] add resourceDiscovery condition --- pkg/status/conditions.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/pkg/status/conditions.go b/pkg/status/conditions.go index f2e28ea8a..e2235dd83 100644 --- a/pkg/status/conditions.go +++ b/pkg/status/conditions.go @@ -52,6 +52,40 @@ func UpdateConditions(stackObj client.Object, operands []Operand, recError error }, nil } +// updateResourceDiscovery updates the ResourceDiscoveryCondition based on the +// ResourceSelector in the MonitorinStack spec. A ResourceSelector of nil causes +// the condition to be false, any other value sets the condition to true +func updateResourceDiscovery(stackObj client.Object) (*v1alpha1.Condition, error) { + unstrObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(stackObj) + if err != nil { + return nil, err + } + rs, ok, err := unstructured.NestedFieldCopy(unstrObj, "spec", "resourceSelector") + if err != nil { + return nil, err + } + if rs == nil || !ok { + return &v1alpha1.Condition{ + Type: v1alpha1.ResourceDiscoveryCondition, + Status: v1alpha1.ConditionFalse, + Reason: ResourceSelectorIsNil, + Message: ResourceSelectorIsNilMessage, + LastTransitionTime: metav1.Now(), + ObservedGeneration: stackObj.GetGeneration(), + }, nil + } else { + return &v1alpha1.Condition{ + Type: v1alpha1.ResourceDiscoveryCondition, + Status: v1alpha1.ConditionTrue, + Reason: NoReason, + Message: ResourceDiscoveryOnMessage, + LastTransitionTime: metav1.Now(), + ObservedGeneration: stackObj.GetGeneration(), + }, 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 { From b78b40f9480ab924c9cdfcbea99e9b378aec8ca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Reme=C5=A1?= Date: Fri, 3 May 2024 09:53:27 +0200 Subject: [PATCH 4/5] rebase --- pkg/controllers/monitoring/monitoring-stack/controller.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/controllers/monitoring/monitoring-stack/controller.go b/pkg/controllers/monitoring/monitoring-stack/controller.go index c942cff97..87ff0b9c5 100644 --- a/pkg/controllers/monitoring/monitoring-stack/controller.go +++ b/pkg/controllers/monitoring/monitoring-stack/controller.go @@ -37,9 +37,6 @@ import ( stack "github.com/rhobs/observability-operator/pkg/apis/monitoring/v1alpha1" "github.com/rhobs/observability-operator/pkg/status" - - "github.com/go-logr/logr" - monv1 "github.com/rhobs/obo-prometheus-operator/pkg/apis/monitoring/v1" ) type resourceManager struct { From 5680b970a2a8391ae46a870729a4a54456f27129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Reme=C5=A1?= Date: Thu, 2 May 2024 10:54:16 +0200 Subject: [PATCH 5/5] share one condition type --- pkg/apis/monitoring/v1alpha1/types.go | 74 +--- .../v1alpha1/zz_generated.deepcopy.go | 19 +- pkg/apis/shared/types.go | 74 ++++ pkg/apis/shared/zz_generated.deepcopy.go | 39 +++ pkg/apis/uiplugin/v1alpha1/types.go | 70 +--- .../v1alpha1/zz_generated.deepcopy.go | 19 +- .../monitoring/monitoring-stack/conditions.go | 190 ---------- .../monitoring-stack/conditions_test.go | 325 ------------------ pkg/controllers/uiplugin/controller.go | 21 +- pkg/status/conditions.go | 115 +++---- pkg/status/conditions_test.go | 136 ++++---- pkg/status/operand.go | 23 +- test/e2e/framework/assertions.go | 7 +- test/e2e/monitoring_stack_controller_test.go | 17 +- 14 files changed, 269 insertions(+), 860 deletions(-) create mode 100644 pkg/apis/shared/types.go create mode 100644 pkg/apis/shared/zz_generated.deepcopy.go delete mode 100644 pkg/controllers/monitoring/monitoring-stack/conditions.go delete mode 100644 pkg/controllers/monitoring/monitoring-stack/conditions_test.go diff --git a/pkg/apis/monitoring/v1alpha1/types.go b/pkg/apis/monitoring/v1alpha1/types.go index e8f4e30e5..b2e8a9b29 100644 --- a/pkg/apis/monitoring/v1alpha1/types.go +++ b/pkg/apis/monitoring/v1alpha1/types.go @@ -8,6 +8,8 @@ import ( monv1 "github.com/rhobs/obo-prometheus-operator/pkg/apis/monitoring/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/rhobs/observability-operator/pkg/apis/shared" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -24,6 +26,10 @@ type MonitoringStack struct { Status MonitoringStackStatus `json:"status,omitempty"` } +func (m *MonitoringStack) Conditions() []shared.Condition { + return m.Status.Conditions +} + // MonitoringStackList contains a list of MonitoringStack // +kubebuilder:resource // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -96,73 +102,7 @@ type MonitoringStackSpec struct { type MonitoringStackStatus struct { // Conditions provide status information about the MonitoringStack // +listType=atomic - Conditions []Condition `json:"conditions"` -} - -type ConditionStatus string - -// +required -// +kubebuilder:validation:Required -// +kubebuilder:validation:Pattern=`^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$` -// +kubebuilder:validation:MaxLength=316 -type ConditionType string - -const ( - ConditionTrue ConditionStatus = "True" - ConditionFalse ConditionStatus = "False" - ConditionUnknown ConditionStatus = "Unknown" - - ReconciledCondition ConditionType = "Reconciled" - AvailableCondition ConditionType = "Available" - ResourceDiscoveryCondition ConditionType = "ResourceDiscovery" -) - -type Condition struct { - // type of condition in CamelCase or in foo.example.com/CamelCase. - // The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - Type ConditionType `json:"type"` - // observedGeneration represents the .metadata.generation that the condition was set based upon. - // For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - // with respect to the current state of the instance. - // +optional - // +kubebuilder:validation:Minimum=0 - ObservedGeneration int64 `json:"observedGeneration,omitempty"` - // lastTransitionTime is the last time the condition transitioned from one status to another. - // This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - // +required - // +kubebuilder:validation:Required - // +kubebuilder:validation:Type=string - // +kubebuilder:validation:Format=date-time - LastTransitionTime metav1.Time `json:"lastTransitionTime"` - // 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. - // +required - // +kubebuilder:validation:Required - // +kubebuilder:validation:MaxLength=1024 - // +kubebuilder:validation:MinLength=1 - // +kubebuilder:validation:Pattern=`^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$` - Reason string `json:"reason"` - // message is a human readable message indicating details about the transition. - // This may be an empty string. - // +required - // +kubebuilder:validation:Required - // +kubebuilder:validation:MaxLength=32768 - Message string `json:"message"` - // status of the condition - // +required - // +kubebuilder:validation:Required - // +kubebuilder:validation:Enum=True;False;Unknown;Degraded - Status ConditionStatus `json:"status"` -} - -func (c Condition) Equal(n Condition) bool { - if c.Reason == n.Reason && c.Status == n.Status && c.Message == n.Message && c.ObservedGeneration == n.ObservedGeneration { - return true - } - return false + Conditions []shared.Condition `json:"conditions"` } type PrometheusConfig struct { diff --git a/pkg/apis/monitoring/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/monitoring/v1alpha1/zz_generated.deepcopy.go index 78e240359..7fb1a2610 100644 --- a/pkg/apis/monitoring/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/monitoring/v1alpha1/zz_generated.deepcopy.go @@ -22,6 +22,7 @@ package v1alpha1 import ( monitoringv1 "github.com/rhobs/obo-prometheus-operator/pkg/apis/monitoring/v1" + "github.com/rhobs/observability-operator/pkg/apis/shared" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" @@ -42,22 +43,6 @@ func (in *AlertmanagerConfig) DeepCopy() *AlertmanagerConfig { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Condition) DeepCopyInto(out *Condition) { - *out = *in - in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition. -func (in *Condition) DeepCopy() *Condition { - if in == nil { - return nil - } - out := new(Condition) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MonitoringStack) DeepCopyInto(out *MonitoringStack) { *out = *in @@ -154,7 +139,7 @@ func (in *MonitoringStackStatus) DeepCopyInto(out *MonitoringStackStatus) { *out = *in if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions - *out = make([]Condition, len(*in)) + *out = make([]shared.Condition, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } diff --git a/pkg/apis/shared/types.go b/pkg/apis/shared/types.go new file mode 100644 index 000000000..b1278307e --- /dev/null +++ b/pkg/apis/shared/types.go @@ -0,0 +1,74 @@ +package shared + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +type StatusReporter interface { + Conditions() []Condition + GetGeneration() int64 +} + +type ConditionStatus string + +// +required +// +kubebuilder:validation:Required +// +kubebuilder:validation:Pattern=`^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$` +// +kubebuilder:validation:MaxLength=316 +type ConditionType string + +const ( + ConditionTrue ConditionStatus = "True" + ConditionFalse ConditionStatus = "False" + ConditionUnknown ConditionStatus = "Unknown" + + ReconciledCondition ConditionType = "Reconciled" + AvailableCondition ConditionType = "Available" + ResourceDiscoveryCondition ConditionType = "ResourceDiscovery" +) + +type Condition struct { + // type of condition in CamelCase or in foo.example.com/CamelCase. + // The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + Type ConditionType `json:"type"` + // observedGeneration represents the .metadata.generation that the condition was set based upon. + // For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + // with respect to the current state of the instance. + // +optional + // +kubebuilder:validation:Minimum=0 + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + // lastTransitionTime is the last time the condition transitioned from one status to another. + // This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + // +required + // +kubebuilder:validation:Required + // +kubebuilder:validation:Type=string + // +kubebuilder:validation:Format=date-time + LastTransitionTime metav1.Time `json:"lastTransitionTime"` + // 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. + // +required + // +kubebuilder:validation:Required + // +kubebuilder:validation:MaxLength=1024 + // +kubebuilder:validation:MinLength=1 + // +kubebuilder:validation:Pattern=`^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$` + Reason string `json:"reason"` + // message is a human readable message indicating details about the transition. + // This may be an empty string. + // +required + // +kubebuilder:validation:Required + // +kubebuilder:validation:MaxLength=32768 + Message string `json:"message"` + // status of the condition + // +required + // +kubebuilder:validation:Required + // +kubebuilder:validation:Enum=True;False;Unknown;Degraded + Status ConditionStatus `json:"status"` +} + +func (c Condition) Equal(n Condition) bool { + if c.Reason == n.Reason && c.Status == n.Status && c.Message == n.Message && c.ObservedGeneration == n.ObservedGeneration { + return true + } + return false +} diff --git a/pkg/apis/shared/zz_generated.deepcopy.go b/pkg/apis/shared/zz_generated.deepcopy.go new file mode 100644 index 000000000..ab22c9a79 --- /dev/null +++ b/pkg/apis/shared/zz_generated.deepcopy.go @@ -0,0 +1,39 @@ +//go:build !ignore_autogenerated + +/* +Copyright 2021. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package shared + + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Condition) DeepCopyInto(out *Condition) { + *out = *in + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition. +func (in *Condition) DeepCopy() *Condition { + if in == nil { + return nil + } + out := new(Condition) + in.DeepCopyInto(out) + return out +} + diff --git a/pkg/apis/uiplugin/v1alpha1/types.go b/pkg/apis/uiplugin/v1alpha1/types.go index 2fdbe7cf0..43ee5da3c 100644 --- a/pkg/apis/uiplugin/v1alpha1/types.go +++ b/pkg/apis/uiplugin/v1alpha1/types.go @@ -6,6 +6,8 @@ package v1alpha1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/rhobs/observability-operator/pkg/apis/shared" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -52,71 +54,5 @@ type UIPluginSpec struct { type UIPluginStatus struct { // Conditions provide status information about the plugin. // +listType=atomic - Conditions []Condition `json:"conditions"` -} - -type ConditionStatus string - -// +required -// +kubebuilder:validation:Required -// +kubebuilder:validation:Pattern=`^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$` -// +kubebuilder:validation:MaxLength=316 -type ConditionType string - -const ( - ConditionTrue ConditionStatus = "True" - ConditionFalse ConditionStatus = "False" - ConditionUnknown ConditionStatus = "Unknown" - - ReconciledCondition ConditionType = "Reconciled" - AvailableCondition ConditionType = "Available" - ResourceDiscoveryCondition ConditionType = "ResourceDiscovery" -) - -type Condition struct { - // type of condition in CamelCase or in foo.example.com/CamelCase. - // The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - Type ConditionType `json:"type"` - // observedGeneration represents the .metadata.generation that the condition was set based upon. - // For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - // with respect to the current state of the instance. - // +optional - // +kubebuilder:validation:Minimum=0 - ObservedGeneration int64 `json:"observedGeneration,omitempty"` - // lastTransitionTime is the last time the condition transitioned from one status to another. - // This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - // +required - // +kubebuilder:validation:Required - // +kubebuilder:validation:Type=string - // +kubebuilder:validation:Format=date-time - LastTransitionTime metav1.Time `json:"lastTransitionTime"` - // 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. - // +required - // +kubebuilder:validation:Required - // +kubebuilder:validation:MaxLength=1024 - // +kubebuilder:validation:MinLength=1 - // +kubebuilder:validation:Pattern=`^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$` - Reason string `json:"reason"` - // message is a human readable message indicating details about the transition. - // This may be an empty string. - // +required - // +kubebuilder:validation:Required - // +kubebuilder:validation:MaxLength=32768 - Message string `json:"message"` - // status of the condition - // +required - // +kubebuilder:validation:Required - // +kubebuilder:validation:Enum=True;False;Unknown;Degraded - Status ConditionStatus `json:"status"` -} - -func (c Condition) Equal(n Condition) bool { - if c.Reason == n.Reason && c.Status == n.Status && c.Message == n.Message && c.ObservedGeneration == n.ObservedGeneration { - return true - } - return false + Conditions []shared.Condition `json:"conditions"` } diff --git a/pkg/apis/uiplugin/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/uiplugin/v1alpha1/zz_generated.deepcopy.go index 292e70e7e..fc93ac739 100644 --- a/pkg/apis/uiplugin/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/uiplugin/v1alpha1/zz_generated.deepcopy.go @@ -21,25 +21,10 @@ limitations under the License. package v1alpha1 import ( + "github.com/rhobs/observability-operator/pkg/apis/shared" runtime "k8s.io/apimachinery/pkg/runtime" ) -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Condition) DeepCopyInto(out *Condition) { - *out = *in - in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Condition. -func (in *Condition) DeepCopy() *Condition { - if in == nil { - return nil - } - out := new(Condition) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *UIPlugin) DeepCopyInto(out *UIPlugin) { *out = *in @@ -119,7 +104,7 @@ func (in *UIPluginStatus) DeepCopyInto(out *UIPluginStatus) { *out = *in if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions - *out = make([]Condition, len(*in)) + *out = make([]shared.Condition, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } diff --git a/pkg/controllers/monitoring/monitoring-stack/conditions.go b/pkg/controllers/monitoring/monitoring-stack/conditions.go deleted file mode 100644 index e4db554f6..000000000 --- a/pkg/controllers/monitoring/monitoring-stack/conditions.go +++ /dev/null @@ -1,190 +0,0 @@ -package monitoringstack - -import ( - "fmt" - - monv1 "github.com/rhobs/obo-prometheus-operator/pkg/apis/monitoring/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/rhobs/observability-operator/pkg/apis/monitoring/v1alpha1" -) - -const ( - AvailableReason = "MonitoringStackAvailable" - ReconciledReason = "MonitoringStackReconciled" - FailedToReconcileReason = "FailedToReconcile" - PrometheusNotAvailable = "PrometheusNotAvailable" - PrometheusNotReconciled = "PrometheusNotReconciled" - PrometheusDegraded = "PrometheusDegraded" - ResourceSelectorIsNil = "ResourceSelectorNil" - CannotReadPrometheusConditions = "Cannot read Prometheus status conditions" - 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" -) - -func updateConditions(ms *v1alpha1.MonitoringStack, prom monv1.Prometheus, recError error) []v1alpha1.Condition { - return []v1alpha1.Condition{ - updateResourceDiscovery(ms), - updateAvailable(ms.Status.Conditions, prom, ms.Generation), - updateReconciled(ms.Status.Conditions, prom, ms.Generation, recError), - } -} - -func getMSCondition(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("condition type %v not found", t) -} - -// updateResourceDiscovery updates the ResourceDiscoveryCondition based on the -// ResourceSelector in the MonitorinStack spec. A ResourceSelector of nil causes -// the condition to be false, any other value sets the condition to true -func updateResourceDiscovery(ms *v1alpha1.MonitoringStack) v1alpha1.Condition { - if ms.Spec.ResourceSelector == nil { - return v1alpha1.Condition{ - Type: v1alpha1.ResourceDiscoveryCondition, - Status: v1alpha1.ConditionFalse, - Reason: ResourceSelectorIsNil, - Message: ResourceSelectorIsNilMessage, - LastTransitionTime: metav1.Now(), - ObservedGeneration: ms.Generation, - } - } else { - return v1alpha1.Condition{ - Type: v1alpha1.ResourceDiscoveryCondition, - Status: v1alpha1.ConditionTrue, - Reason: NoReason, - Message: ResourceDiscoveryOnMessage, - LastTransitionTime: metav1.Now(), - ObservedGeneration: ms.Generation, - } - } - -} - -// updateAvailable gets existing "Available" condition and updates its parameters -// based on the Prometheus "Available" condition -func updateAvailable(conditions []v1alpha1.Condition, prom monv1.Prometheus, generation int64) v1alpha1.Condition { - ac, err := getMSCondition(conditions, v1alpha1.AvailableCondition) - if err != nil { - ac = v1alpha1.Condition{ - Type: v1alpha1.AvailableCondition, - Status: v1alpha1.ConditionUnknown, - Reason: NoReason, - LastTransitionTime: metav1.Now(), - } - } - - prometheusAvailable, err := getPrometheusCondition(prom.Status.Conditions, monv1.Available) - - if err != nil { - ac.Status = v1alpha1.ConditionUnknown - ac.Reason = PrometheusNotAvailable - ac.Message = CannotReadPrometheusConditions - ac.LastTransitionTime = metav1.Now() - return ac - } - // MonitoringStack status will not be updated if there is a difference between the Prometheus generation - // and the Prometheus ObservedGeneration. This can occur, for example, in the case of an invalid Prometheus configuration. - if prometheusAvailable.ObservedGeneration != prom.Generation { - return ac - } - - if prometheusAvailable.Status != monv1.ConditionTrue { - ac.Status = prometheusStatusToMSStatus(prometheusAvailable.Status) - if prometheusAvailable.Status == monv1.ConditionDegraded { - ac.Reason = PrometheusDegraded - } else { - ac.Reason = PrometheusNotAvailable - } - ac.Message = prometheusAvailable.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 -// Prometheus "Reconciled" condition -func updateReconciled(conditions []v1alpha1.Condition, prom monv1.Prometheus, generation int64, reconcileErr error) v1alpha1.Condition { - rc, cErr := getMSCondition(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 - } - prometheusReconciled, reconcileErr := getPrometheusCondition(prom.Status.Conditions, monv1.Reconciled) - - if reconcileErr != nil { - rc.Status = v1alpha1.ConditionUnknown - rc.Reason = PrometheusNotReconciled - rc.Message = CannotReadPrometheusConditions - rc.LastTransitionTime = metav1.Now() - return rc - } - - if prometheusReconciled.ObservedGeneration != prom.Generation { - return rc - } - - if prometheusReconciled.Status != monv1.ConditionTrue { - rc.Status = prometheusStatusToMSStatus(prometheusReconciled.Status) - rc.Reason = PrometheusNotReconciled - rc.Message = prometheusReconciled.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 getPrometheusCondition(prometheusConditions []monv1.Condition, t monv1.ConditionType) (*monv1.Condition, error) { - for _, c := range prometheusConditions { - if c.Type == t { - return &c, nil - } - } - return nil, fmt.Errorf("cannot find condition %v", t) -} - -func prometheusStatusToMSStatus(ps monv1.ConditionStatus) v1alpha1.ConditionStatus { - switch ps { - // Prometheus "Available" condition with status "Degraded" is reported as "Available" condition - // with status false - case monv1.ConditionDegraded: - return v1alpha1.ConditionFalse - case monv1.ConditionTrue: - return v1alpha1.ConditionTrue - case monv1.ConditionFalse: - return v1alpha1.ConditionFalse - case monv1.ConditionUnknown: - return v1alpha1.ConditionUnknown - default: - return v1alpha1.ConditionUnknown - } -} diff --git a/pkg/controllers/monitoring/monitoring-stack/conditions_test.go b/pkg/controllers/monitoring/monitoring-stack/conditions_test.go deleted file mode 100644 index bd30fad54..000000000 --- a/pkg/controllers/monitoring/monitoring-stack/conditions_test.go +++ /dev/null @@ -1,325 +0,0 @@ -package monitoringstack - -import ( - "testing" - - monv1 "github.com/rhobs/obo-prometheus-operator/pkg/apis/monitoring/v1" - "gotest.tools/v3/assert" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/rhobs/observability-operator/pkg/apis/monitoring/v1alpha1" -) - -func TestUpdateAvailable(t *testing.T) { - tt := []struct { - name string - prometheus monv1.Prometheus - previousConditions []v1alpha1.Condition - generation int64 - expectedResult v1alpha1.Condition - }{ - { - name: "conditions not changed when Prometheus Available", - previousConditions: []v1alpha1.Condition{ - { - Type: v1alpha1.AvailableCondition, - Status: v1alpha1.ConditionTrue, - ObservedGeneration: 1, - Reason: AvailableReason, - Message: AvailableMessage, - }, - }, - prometheus: monv1.Prometheus{ - ObjectMeta: metav1.ObjectMeta{ - Generation: 1, - }, - Status: monv1.PrometheusStatus{ - Conditions: []monv1.Condition{ - { - Type: monv1.Available, - Status: monv1.ConditionTrue, - ObservedGeneration: 1, - }, - }}}, - generation: 1, - expectedResult: v1alpha1.Condition{ - Type: v1alpha1.AvailableCondition, - Status: v1alpha1.ConditionTrue, - ObservedGeneration: 1, - Reason: AvailableReason, - Message: AvailableMessage, - }, - }, - { - name: "cannot read Prometheus conditions", - previousConditions: []v1alpha1.Condition{ - { - Type: v1alpha1.AvailableCondition, - Status: v1alpha1.ConditionTrue, - ObservedGeneration: 1, - Reason: AvailableReason, - Message: AvailableMessage, - }, - }, - generation: 1, - prometheus: monv1.Prometheus{}, - expectedResult: v1alpha1.Condition{ - Type: v1alpha1.AvailableCondition, - Status: v1alpha1.ConditionUnknown, - ObservedGeneration: 1, - Reason: PrometheusNotAvailable, - Message: CannotReadPrometheusConditions, - }, - }, - { - name: "degraded Prometheus conditions", - previousConditions: []v1alpha1.Condition{ - { - Type: v1alpha1.AvailableCondition, - Status: v1alpha1.ConditionTrue, - ObservedGeneration: 1, - Reason: AvailableReason, - Message: AvailableMessage, - }, - }, - generation: 1, - prometheus: monv1.Prometheus{ - ObjectMeta: metav1.ObjectMeta{ - Generation: 1, - }, - Status: monv1.PrometheusStatus{ - Conditions: []monv1.Condition{ - { - Type: monv1.Available, - Status: monv1.ConditionDegraded, - ObservedGeneration: 1, - }, - }}}, - expectedResult: v1alpha1.Condition{ - Type: v1alpha1.AvailableCondition, - Status: v1alpha1.ConditionFalse, - ObservedGeneration: 1, - Reason: PrometheusDegraded, - }, - }, - { - name: "Prometheus observed generation is different from the Prometheus generation", - previousConditions: []v1alpha1.Condition{ - { - Type: v1alpha1.AvailableCondition, - Status: v1alpha1.ConditionTrue, - ObservedGeneration: 2, - Reason: AvailableReason, - Message: AvailableMessage, - }, - }, - generation: 1, - prometheus: monv1.Prometheus{ - ObjectMeta: metav1.ObjectMeta{ - Generation: 3, - }, - Status: monv1.PrometheusStatus{ - Conditions: []monv1.Condition{ - { - Type: monv1.Available, - Status: monv1.ConditionFalse, - ObservedGeneration: 2, - }, - }}}, - expectedResult: v1alpha1.Condition{ - Type: v1alpha1.AvailableCondition, - Status: v1alpha1.ConditionTrue, - ObservedGeneration: 2, - Reason: AvailableReason, - Message: AvailableMessage, - }, - }, - } - - for _, test := range tt { - res := updateAvailable(test.previousConditions, test.prometheus, test.generation) - assert.Check(t, test.expectedResult.Equal(res), "%s - expected:\n %v\n and got:\n %v\n", test.name, test.expectedResult, res) - } -} - -func TestUpdateReconciled(t *testing.T) { - tt := []struct { - name string - prometheus monv1.Prometheus - previousConditions []v1alpha1.Condition - generation int64 - recError error - expectedResult v1alpha1.Condition - }{ - { - name: "conditions not changed when Prometheus Available", - previousConditions: []v1alpha1.Condition{ - { - Type: v1alpha1.ReconciledCondition, - Status: v1alpha1.ConditionTrue, - ObservedGeneration: 1, - Reason: ReconciledReason, - Message: SuccessfullyReconciledMessage, - }, - }, - recError: nil, - generation: 1, - prometheus: monv1.Prometheus{ - ObjectMeta: metav1.ObjectMeta{ - Generation: 1, - }, - Status: monv1.PrometheusStatus{ - Conditions: []monv1.Condition{ - { - Type: monv1.Reconciled, - Status: monv1.ConditionTrue, - ObservedGeneration: 1, - }, - }}}, - expectedResult: v1alpha1.Condition{ - Type: v1alpha1.ReconciledCondition, - Status: v1alpha1.ConditionTrue, - ObservedGeneration: 1, - Reason: ReconciledReason, - Message: SuccessfullyReconciledMessage, - }, - }, - { - name: "cannot read Prometheus conditions", - previousConditions: []v1alpha1.Condition{ - { - Type: v1alpha1.ReconciledCondition, - Status: v1alpha1.ConditionTrue, - ObservedGeneration: 1, - Reason: ReconciledReason, - Message: SuccessfullyReconciledMessage, - }, - }, - recError: nil, - generation: 1, - prometheus: monv1.Prometheus{}, - expectedResult: v1alpha1.Condition{ - Type: v1alpha1.ReconciledCondition, - Status: v1alpha1.ConditionUnknown, - ObservedGeneration: 1, - Reason: PrometheusNotReconciled, - Message: CannotReadPrometheusConditions, - }, - }, - { - name: "degraded Prometheus conditions", - previousConditions: []v1alpha1.Condition{ - { - Type: v1alpha1.ReconciledCondition, - Status: v1alpha1.ConditionTrue, - ObservedGeneration: 1, - Reason: ReconciledReason, - Message: SuccessfullyReconciledMessage, - }, - }, - recError: nil, - generation: 1, - prometheus: monv1.Prometheus{ - ObjectMeta: metav1.ObjectMeta{ - Generation: 1, - }, - Status: monv1.PrometheusStatus{ - Conditions: []monv1.Condition{ - { - Type: monv1.Reconciled, - Status: monv1.ConditionDegraded, - ObservedGeneration: 1, - }, - }}}, - expectedResult: v1alpha1.Condition{ - Type: v1alpha1.ReconciledCondition, - Status: v1alpha1.ConditionFalse, - ObservedGeneration: 1, - Reason: PrometheusNotReconciled, - }, - }, - { - name: "Prometheus observed generation is different from the Prometheus generation", - previousConditions: []v1alpha1.Condition{ - { - Type: v1alpha1.ReconciledCondition, - Status: v1alpha1.ConditionTrue, - ObservedGeneration: 2, - Reason: ReconciledReason, - Message: SuccessfullyReconciledMessage, - }, - }, - recError: nil, - generation: 1, - prometheus: monv1.Prometheus{ - ObjectMeta: metav1.ObjectMeta{ - Generation: 3, - }, - Status: monv1.PrometheusStatus{ - Conditions: []monv1.Condition{ - { - Type: monv1.Reconciled, - Status: monv1.ConditionFalse, - ObservedGeneration: 2, - }, - }}}, - expectedResult: v1alpha1.Condition{ - Type: v1alpha1.ReconciledCondition, - Status: v1alpha1.ConditionTrue, - ObservedGeneration: 2, - Reason: ReconciledReason, - Message: SuccessfullyReconciledMessage, - }, - }, - } - - for _, test := range tt { - res := updateReconciled(test.previousConditions, test.prometheus, test.generation, test.recError) - assert.Check(t, test.expectedResult.Equal(res), "%s - expected:\n %v\n and got:\n %v\n", test.name, test.expectedResult, res) - } -} - -func TestUpdateResourceDiscovery(t *testing.T) { - transitionTime := metav1.Now() - tt := []struct { - name string - msWithConditions v1alpha1.MonitoringStack - expectedResults v1alpha1.Condition - }{ - { - name: "set resource discovery true when ResourceSelector not nil", - msWithConditions: v1alpha1.MonitoringStack{ - Spec: v1alpha1.MonitoringStackSpec{ - ResourceSelector: &metav1.LabelSelector{}, - }, - }, - expectedResults: v1alpha1.Condition{ - Type: v1alpha1.ResourceDiscoveryCondition, - Status: v1alpha1.ConditionTrue, - Reason: NoReason, - Message: ResourceDiscoveryOnMessage, - }, - }, - { - name: "set resource discovery false when ResourceSelector is nil", - msWithConditions: v1alpha1.MonitoringStack{ - Spec: v1alpha1.MonitoringStackSpec{ - ResourceSelector: nil, - }, - }, - expectedResults: v1alpha1.Condition{ - Type: v1alpha1.ResourceDiscoveryCondition, - Status: v1alpha1.ConditionFalse, - Reason: ResourceSelectorIsNil, - Message: ResourceSelectorIsNilMessage, - LastTransitionTime: transitionTime, - }, - }, - } - - for _, test := range tt { - res := updateResourceDiscovery(&test.msWithConditions) - assert.Check(t, test.expectedResults.Equal(res), "%s - expected:\n %v\n and got:\n %v\n", test.name, test.expectedResults, res) - } - -} diff --git a/pkg/controllers/uiplugin/controller.go b/pkg/controllers/uiplugin/controller.go index 91cb9a1f2..cfecdcf6e 100644 --- a/pkg/controllers/uiplugin/controller.go +++ b/pkg/controllers/uiplugin/controller.go @@ -23,6 +23,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/predicate" + "github.com/rhobs/observability-operator/pkg/apis/shared" uiv1alpha1 "github.com/rhobs/observability-operator/pkg/apis/uiplugin/v1alpha1" "github.com/rhobs/observability-operator/pkg/reconciler" ) @@ -180,36 +181,36 @@ func (rm resourceManager) updateStatus(ctx context.Context, req ctrl.Request, pl logger := rm.logger.WithValues("plugin", req.NamespacedName) if recError != nil { - pl.Status.Conditions = []uiv1alpha1.Condition{ + pl.Status.Conditions = []shared.Condition{ { - Type: uiv1alpha1.ReconciledCondition, - Status: uiv1alpha1.ConditionFalse, + Type: shared.ReconciledCondition, + Status: shared.ConditionFalse, Reason: FailedToReconcileReason, Message: recError.Error(), ObservedGeneration: pl.Generation, LastTransitionTime: metav1.Now(), }, { - Type: uiv1alpha1.AvailableCondition, - Status: uiv1alpha1.ConditionFalse, + Type: shared.AvailableCondition, + Status: shared.ConditionFalse, Reason: FailedToReconcileReason, ObservedGeneration: pl.Generation, LastTransitionTime: metav1.Now(), }, } } else { - pl.Status.Conditions = []uiv1alpha1.Condition{ + pl.Status.Conditions = []shared.Condition{ { - Type: uiv1alpha1.ReconciledCondition, - Status: uiv1alpha1.ConditionTrue, + Type: shared.ReconciledCondition, + Status: shared.ConditionTrue, Reason: ReconciledReason, Message: ReconciledMessage, ObservedGeneration: pl.Generation, LastTransitionTime: metav1.Now(), }, { - Type: uiv1alpha1.AvailableCondition, - Status: uiv1alpha1.ConditionTrue, + Type: shared.AvailableCondition, + Status: shared.ConditionTrue, Reason: AvailableReason, ObservedGeneration: pl.Generation, LastTransitionTime: metav1.Now(), diff --git a/pkg/status/conditions.go b/pkg/status/conditions.go index e2235dd83..1b75a2409 100644 --- a/pkg/status/conditions.go +++ b/pkg/status/conditions.go @@ -3,11 +3,11 @@ 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" + + "github.com/rhobs/observability-operator/pkg/apis/shared" ) const ( @@ -24,13 +24,10 @@ const ( 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 - } +func UpdateConditions(stackObj shared.StatusReporter, operands []Operand, recError error) ([]shared.Condition, error) { + var availableCon shared.Condition + var reconciledCon shared.Condition + conditions := stackObj.Conditions() for _, opr := range operands { if opr.affectsAvailability { availableCon = updateAvailable(conditions, opr, stackObj.GetGeneration()) @@ -45,7 +42,7 @@ func UpdateConditions(stackObj client.Object, operands []Operand, recError error return nil, err } - return []v1alpha1.Condition{ + return []shared.Condition{ availableCon, reconciledCon, *resourceDiscoveryCon, @@ -55,7 +52,7 @@ func UpdateConditions(stackObj client.Object, operands []Operand, recError error // updateResourceDiscovery updates the ResourceDiscoveryCondition based on the // ResourceSelector in the MonitorinStack spec. A ResourceSelector of nil causes // the condition to be false, any other value sets the condition to true -func updateResourceDiscovery(stackObj client.Object) (*v1alpha1.Condition, error) { +func updateResourceDiscovery(stackObj shared.StatusReporter) (*shared.Condition, error) { unstrObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(stackObj) if err != nil { return nil, err @@ -65,18 +62,18 @@ func updateResourceDiscovery(stackObj client.Object) (*v1alpha1.Condition, error return nil, err } if rs == nil || !ok { - return &v1alpha1.Condition{ - Type: v1alpha1.ResourceDiscoveryCondition, - Status: v1alpha1.ConditionFalse, + return &shared.Condition{ + Type: shared.ResourceDiscoveryCondition, + Status: shared.ConditionFalse, Reason: ResourceSelectorIsNil, Message: ResourceSelectorIsNilMessage, LastTransitionTime: metav1.Now(), ObservedGeneration: stackObj.GetGeneration(), }, nil } else { - return &v1alpha1.Condition{ - Type: v1alpha1.ResourceDiscoveryCondition, - Status: v1alpha1.ConditionTrue, + return &shared.Condition{ + Type: shared.ResourceDiscoveryCondition, + Status: shared.ConditionTrue, Reason: NoReason, Message: ResourceDiscoveryOnMessage, LastTransitionTime: metav1.Now(), @@ -88,21 +85,22 @@ func updateResourceDiscovery(stackObj client.Object) (*v1alpha1.Condition, error // 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) +func updateAvailable(conditions []shared.Condition, opr Operand, generation int64) shared.Condition { + ac, err := getConditionByType(conditions, shared.AvailableCondition) if err != nil { - ac = v1alpha1.Condition{ - Type: v1alpha1.AvailableCondition, - Status: v1alpha1.ConditionUnknown, + ac = shared.Condition{ + Type: shared.AvailableCondition, + Status: shared.ConditionUnknown, Reason: NoReason, LastTransitionTime: metav1.Now(), + Message: err.Error(), } + return ac } operandAvailable, err := opr.getConditionByType(available) - if err != nil { - ac.Status = v1alpha1.ConditionUnknown + ac.Status = shared.ConditionUnknown ac.Reason = fmt.Sprintf("%sNotAvailable", opr.name) ac.Message = fmt.Sprintf("Cannot read %s status conditions", opr.name) ac.LastTransitionTime = metav1.Now() @@ -125,7 +123,7 @@ func updateAvailable(conditions []v1alpha1.Condition, opr Operand, generation in ac.LastTransitionTime = metav1.Now() return ac } - ac.Status = v1alpha1.ConditionTrue + ac.Status = shared.ConditionTrue ac.Reason = AvailableReason ac.Message = AvailableMessage ac.ObservedGeneration = generation @@ -135,18 +133,20 @@ func updateAvailable(conditions []v1alpha1.Condition, opr Operand, generation in // 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) +func updateReconciled(conditions []shared.Condition, opr Operand, generation int64, reconcileErr error) shared.Condition { + rc, cErr := getConditionByType(conditions, shared.ReconciledCondition) if cErr != nil { - rc = v1alpha1.Condition{ - Type: v1alpha1.ReconciledCondition, - Status: v1alpha1.ConditionUnknown, + rc = shared.Condition{ + Type: shared.ReconciledCondition, + Status: shared.ConditionUnknown, Reason: NoReason, LastTransitionTime: metav1.Now(), + Message: cErr.Error(), } + return rc } if reconcileErr != nil { - rc.Status = v1alpha1.ConditionFalse + rc.Status = shared.ConditionFalse rc.Message = reconcileErr.Error() rc.Reason = FailedToReconcileReason rc.LastTransitionTime = metav1.Now() @@ -155,7 +155,7 @@ func updateReconciled(conditions []v1alpha1.Condition, opr Operand, generation i operandReconciled, reconcileErr := opr.getConditionByType(reconciled) if reconcileErr != nil { - rc.Status = v1alpha1.ConditionUnknown + rc.Status = shared.ConditionUnknown rc.Reason = fmt.Sprintf("%sNotReconciled", opr.name) rc.Message = fmt.Sprintf("Cannot read %s status conditions", opr.name) rc.LastTransitionTime = metav1.Now() @@ -173,7 +173,7 @@ func updateReconciled(conditions []v1alpha1.Condition, opr Operand, generation i rc.LastTransitionTime = metav1.Now() return rc } - rc.Status = v1alpha1.ConditionTrue + rc.Status = shared.ConditionTrue rc.Reason = ReconciledReason rc.Message = SuccessfullyReconciledMessage rc.ObservedGeneration = generation @@ -181,61 +181,28 @@ func updateReconciled(conditions []v1alpha1.Condition, opr Operand, generation i return rc } -func getConditionByType(conditions []v1alpha1.Condition, t v1alpha1.ConditionType) (v1alpha1.Condition, error) { +func getConditionByType(conditions []shared.Condition, t shared.ConditionType) (shared.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 - + return shared.Condition{}, fmt.Errorf("condition type %v not found", t) } -func prometheusStatusToMSStatus(ps string) v1alpha1.ConditionStatus { +func prometheusStatusToMSStatus(ps shared.ConditionStatus) shared.ConditionStatus { switch ps { // Prometheus "Available" condition with status "Degraded" is reported as "Available" condition // with status false case "Degraded": - return v1alpha1.ConditionFalse + return shared.ConditionFalse case "True": - return v1alpha1.ConditionTrue + return shared.ConditionTrue case "False": - return v1alpha1.ConditionFalse + return shared.ConditionFalse case "Unknown": - return v1alpha1.ConditionUnknown + return shared.ConditionUnknown default: - return v1alpha1.ConditionUnknown + return shared.ConditionUnknown } } diff --git a/pkg/status/conditions_test.go b/pkg/status/conditions_test.go index b14540f7f..9a4e65e81 100644 --- a/pkg/status/conditions_test.go +++ b/pkg/status/conditions_test.go @@ -4,26 +4,27 @@ import ( "testing" monv1 "github.com/rhobs/obo-prometheus-operator/pkg/apis/monitoring/v1" - "github.com/rhobs/observability-operator/pkg/apis/monitoring/v1alpha1" "gotest.tools/v3/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/rhobs/observability-operator/pkg/apis/monitoring/v1alpha1" + shared "github.com/rhobs/observability-operator/pkg/apis/shared" ) func TestUpdateAvailable(t *testing.T) { tt := []struct { name string operand Operand - previousConditions []v1alpha1.Condition + previousConditions []shared.Condition generation int64 - expectedResult v1alpha1.Condition + expectedResult shared.Condition }{ { name: "conditions not changed when Prometheus Available", - previousConditions: []v1alpha1.Condition{ + previousConditions: []shared.Condition{ { - Type: v1alpha1.AvailableCondition, - Status: v1alpha1.ConditionTrue, + Type: shared.AvailableCondition, + Status: shared.ConditionTrue, ObservedGeneration: 1, Reason: AvailableReason, Message: AvailableMessage, @@ -47,9 +48,9 @@ func TestUpdateAvailable(t *testing.T) { }}}, }, generation: 1, - expectedResult: v1alpha1.Condition{ - Type: v1alpha1.AvailableCondition, - Status: v1alpha1.ConditionTrue, + expectedResult: shared.Condition{ + Type: shared.AvailableCondition, + Status: shared.ConditionTrue, ObservedGeneration: 1, Reason: AvailableReason, Message: AvailableMessage, @@ -57,10 +58,10 @@ func TestUpdateAvailable(t *testing.T) { }, { name: "cannot read Prometheus conditions", - previousConditions: []v1alpha1.Condition{ + previousConditions: []shared.Condition{ { - Type: v1alpha1.AvailableCondition, - Status: v1alpha1.ConditionTrue, + Type: shared.AvailableCondition, + Status: shared.ConditionTrue, ObservedGeneration: 1, Reason: AvailableReason, Message: AvailableMessage, @@ -73,9 +74,9 @@ func TestUpdateAvailable(t *testing.T) { affectsReconciled: true, Object: &monv1.Prometheus{}, }, - expectedResult: v1alpha1.Condition{ - Type: v1alpha1.AvailableCondition, - Status: v1alpha1.ConditionUnknown, + expectedResult: shared.Condition{ + Type: shared.AvailableCondition, + Status: shared.ConditionUnknown, ObservedGeneration: 1, Reason: "PrometheusNotAvailable", Message: "Cannot read Prometheus status conditions", @@ -83,10 +84,10 @@ func TestUpdateAvailable(t *testing.T) { }, { name: "degraded Prometheus conditions", - previousConditions: []v1alpha1.Condition{ + previousConditions: []shared.Condition{ { - Type: v1alpha1.AvailableCondition, - Status: v1alpha1.ConditionTrue, + Type: shared.AvailableCondition, + Status: shared.ConditionTrue, ObservedGeneration: 1, Reason: AvailableReason, Message: AvailableMessage, @@ -110,19 +111,19 @@ func TestUpdateAvailable(t *testing.T) { }, }}}, }, - expectedResult: v1alpha1.Condition{ - Type: v1alpha1.AvailableCondition, - Status: v1alpha1.ConditionFalse, + expectedResult: shared.Condition{ + Type: shared.AvailableCondition, + Status: shared.ConditionFalse, ObservedGeneration: 1, Reason: "PrometheusDegraded", }, }, { name: "Prometheus observed generation is different from the Prometheus generation", - previousConditions: []v1alpha1.Condition{ + previousConditions: []shared.Condition{ { - Type: v1alpha1.AvailableCondition, - Status: v1alpha1.ConditionTrue, + Type: shared.AvailableCondition, + Status: shared.ConditionTrue, ObservedGeneration: 2, Reason: AvailableReason, Message: AvailableMessage, @@ -146,9 +147,9 @@ func TestUpdateAvailable(t *testing.T) { }, }}}, }, - expectedResult: v1alpha1.Condition{ - Type: v1alpha1.AvailableCondition, - Status: v1alpha1.ConditionTrue, + expectedResult: shared.Condition{ + Type: shared.AvailableCondition, + Status: shared.ConditionTrue, ObservedGeneration: 2, Reason: AvailableReason, Message: AvailableMessage, @@ -166,17 +167,17 @@ func TestUpdateReconciled(t *testing.T) { tt := []struct { name string operand Operand - previousConditions []v1alpha1.Condition + previousConditions []shared.Condition generation int64 recError error - expectedResult v1alpha1.Condition + expectedResult shared.Condition }{ { name: "conditions not changed when Prometheus Available", - previousConditions: []v1alpha1.Condition{ + previousConditions: []shared.Condition{ { - Type: v1alpha1.ReconciledCondition, - Status: v1alpha1.ConditionTrue, + Type: shared.ReconciledCondition, + Status: shared.ConditionTrue, ObservedGeneration: 1, Reason: ReconciledReason, Message: SuccessfullyReconciledMessage, @@ -201,9 +202,9 @@ func TestUpdateReconciled(t *testing.T) { }, }}}, }, - expectedResult: v1alpha1.Condition{ - Type: v1alpha1.ReconciledCondition, - Status: v1alpha1.ConditionTrue, + expectedResult: shared.Condition{ + Type: shared.ReconciledCondition, + Status: shared.ConditionTrue, ObservedGeneration: 1, Reason: ReconciledReason, Message: SuccessfullyReconciledMessage, @@ -211,10 +212,10 @@ func TestUpdateReconciled(t *testing.T) { }, { name: "cannot read Prometheus status conditions", - previousConditions: []v1alpha1.Condition{ + previousConditions: []shared.Condition{ { - Type: v1alpha1.ReconciledCondition, - Status: v1alpha1.ConditionTrue, + Type: shared.ReconciledCondition, + Status: shared.ConditionTrue, ObservedGeneration: 1, Reason: ReconciledReason, Message: SuccessfullyReconciledMessage, @@ -228,9 +229,9 @@ func TestUpdateReconciled(t *testing.T) { affectsReconciled: true, Object: &monv1.Prometheus{}, }, - expectedResult: v1alpha1.Condition{ - Type: v1alpha1.ReconciledCondition, - Status: v1alpha1.ConditionUnknown, + expectedResult: shared.Condition{ + Type: shared.ReconciledCondition, + Status: shared.ConditionUnknown, ObservedGeneration: 1, Reason: "PrometheusNotReconciled", Message: "Cannot read Prometheus status conditions", @@ -238,10 +239,10 @@ func TestUpdateReconciled(t *testing.T) { }, { name: "degraded Prometheus conditions", - previousConditions: []v1alpha1.Condition{ + previousConditions: []shared.Condition{ { - Type: v1alpha1.ReconciledCondition, - Status: v1alpha1.ConditionTrue, + Type: shared.ReconciledCondition, + Status: shared.ConditionTrue, ObservedGeneration: 1, Reason: ReconciledReason, Message: SuccessfullyReconciledMessage, @@ -266,19 +267,19 @@ func TestUpdateReconciled(t *testing.T) { }, }}}, }, - expectedResult: v1alpha1.Condition{ - Type: v1alpha1.ReconciledCondition, - Status: v1alpha1.ConditionFalse, + expectedResult: shared.Condition{ + Type: shared.ReconciledCondition, + Status: shared.ConditionFalse, ObservedGeneration: 1, Reason: "PrometheusNotReconciled", }, }, { name: "Prometheus observed generation is different from the Prometheus generation", - previousConditions: []v1alpha1.Condition{ + previousConditions: []shared.Condition{ { - Type: v1alpha1.ReconciledCondition, - Status: v1alpha1.ConditionTrue, + Type: shared.ReconciledCondition, + Status: shared.ConditionTrue, ObservedGeneration: 2, Reason: ReconciledReason, Message: SuccessfullyReconciledMessage, @@ -303,9 +304,9 @@ func TestUpdateReconciled(t *testing.T) { }, }}}, }, - expectedResult: v1alpha1.Condition{ - Type: v1alpha1.ReconciledCondition, - Status: v1alpha1.ConditionTrue, + expectedResult: shared.Condition{ + Type: shared.ReconciledCondition, + Status: shared.ConditionTrue, ObservedGeneration: 2, Reason: ReconciledReason, Message: SuccessfullyReconciledMessage, @@ -324,7 +325,7 @@ func TestUpdateResourceDiscovery(t *testing.T) { tt := []struct { name string msWithConditions *v1alpha1.MonitoringStack - expectedResults v1alpha1.Condition + expectedResults shared.Condition }{ { name: "set resource discovery true when ResourceSelector not nil", @@ -333,9 +334,9 @@ func TestUpdateResourceDiscovery(t *testing.T) { ResourceSelector: &metav1.LabelSelector{}, }, }, - expectedResults: v1alpha1.Condition{ - Type: v1alpha1.ResourceDiscoveryCondition, - Status: v1alpha1.ConditionTrue, + expectedResults: shared.Condition{ + Type: shared.ResourceDiscoveryCondition, + Status: shared.ConditionTrue, Reason: NoReason, Message: ResourceDiscoveryOnMessage, }, @@ -347,9 +348,9 @@ func TestUpdateResourceDiscovery(t *testing.T) { ResourceSelector: nil, }, }, - expectedResults: v1alpha1.Condition{ - Type: v1alpha1.ResourceDiscoveryCondition, - Status: v1alpha1.ConditionFalse, + expectedResults: shared.Condition{ + Type: shared.ResourceDiscoveryCondition, + Status: shared.ConditionFalse, Reason: ResourceSelectorIsNil, Message: ResourceSelectorIsNilMessage, LastTransitionTime: transitionTime, @@ -364,11 +365,11 @@ func TestUpdateResourceDiscovery(t *testing.T) { } } -func TestGetConditionsFromObject(t *testing.T) { +/* func TestGetConditionsFromObject(t *testing.T) { tests := []struct { name string testObject client.Object - expectedConditions []v1alpha1.Condition + expectedConditions []shared.Condition }{ { name: "empty monitoring stack", @@ -379,10 +380,10 @@ func TestGetConditionsFromObject(t *testing.T) { name: "monitoring stack with some valid conditions", testObject: &v1alpha1.MonitoringStack{ Status: v1alpha1.MonitoringStackStatus{ - Conditions: []v1alpha1.Condition{ + Conditions: []shared.Condition{ { Type: available, - Status: v1alpha1.ConditionTrue, + Status: shared.ConditionTrue, Reason: AvailableReason, Message: AvailableMessage, ObservedGeneration: 1, @@ -390,10 +391,10 @@ func TestGetConditionsFromObject(t *testing.T) { }, }, }, - expectedConditions: []v1alpha1.Condition{ + expectedConditions: []shared.Condition{ { Type: available, - Status: v1alpha1.ConditionTrue, + Status: shared.ConditionTrue, Reason: AvailableReason, Message: AvailableMessage, ObservedGeneration: 1, @@ -410,3 +411,4 @@ func TestGetConditionsFromObject(t *testing.T) { }) } } +*/ diff --git a/pkg/status/operand.go b/pkg/status/operand.go index 168c1dec4..1e6b508d3 100644 --- a/pkg/status/operand.go +++ b/pkg/status/operand.go @@ -3,20 +3,12 @@ package status import ( "fmt" - 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" -) -type conditionHelper struct { - Type string - Status string - Reason string - ObservedGeneration int64 - Message string - LastTransitionTime metav1.Time -} + "github.com/rhobs/observability-operator/pkg/apis/shared" +) func convert[T comparable](v interface{}) T { var r T @@ -49,7 +41,7 @@ func NewOperand(obj client.Object, affectsStackAvailability bool, affectsStackRe // getConditionByType converts the operand object to unstructured and // then tries to find conidtion with provided type. -func (o *Operand) getConditionByType(ctype string) (*conditionHelper, error) { +func (o *Operand) getConditionByType(ctype string) (*shared.Condition, error) { unstrObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(o.Object) if err != nil { return nil, err @@ -71,12 +63,13 @@ func (o *Operand) getConditionByType(ctype string) (*conditionHelper, error) { if t, ok := cMap["type"]; ok { if t == ctype { - return &conditionHelper{ - Type: convert[string](t), - Status: convert[string](cMap["status"]), + oc := &shared.Condition{ + Type: shared.ConditionType(convert[string](t)), + Status: shared.ConditionStatus(convert[string](cMap["status"])), ObservedGeneration: convert[int64](cMap["observedGeneration"]), Message: convert[string](cMap["message"]), - }, nil + } + return oc, nil } } } diff --git a/test/e2e/framework/assertions.go b/test/e2e/framework/assertions.go index 7c56f6f17..9b066c09b 100644 --- a/test/e2e/framework/assertions.go +++ b/test/e2e/framework/assertions.go @@ -21,6 +21,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "github.com/rhobs/observability-operator/pkg/apis/monitoring/v1alpha1" + "github.com/rhobs/observability-operator/pkg/apis/shared" ) // default ForeverTestTimeout is 30, some test fail because they take more than 30s @@ -317,8 +318,8 @@ func (f *Framework) GetStackWhenAvailable(t *testing.T, name, namespace string) lastErr = err return false, nil } - availableC := getConditionByType(ms.Status.Conditions, v1alpha1.AvailableCondition) - if availableC != nil && availableC.Status == v1alpha1.ConditionTrue { + availableC := getConditionByType(ms.Status.Conditions, shared.AvailableCondition) + if availableC != nil && availableC.Status == shared.ConditionTrue { return true, nil } return false, nil @@ -348,7 +349,7 @@ func (f *Framework) AssertAlertmanagerAbsent(t *testing.T, name, namespace strin } } -func getConditionByType(conditions []v1alpha1.Condition, ctype v1alpha1.ConditionType) *v1alpha1.Condition { +func getConditionByType(conditions []shared.Condition, ctype shared.ConditionType) *shared.Condition { for _, c := range conditions { if c.Type == ctype { return &c diff --git a/test/e2e/monitoring_stack_controller_test.go b/test/e2e/monitoring_stack_controller_test.go index c6aaa001f..f7148ad7b 100644 --- a/test/e2e/monitoring_stack_controller_test.go +++ b/test/e2e/monitoring_stack_controller_test.go @@ -25,8 +25,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" stack "github.com/rhobs/observability-operator/pkg/apis/monitoring/v1alpha1" - monitoringstack "github.com/rhobs/observability-operator/pkg/controllers/monitoring/monitoring-stack" + "github.com/rhobs/observability-operator/pkg/apis/shared" operator "github.com/rhobs/observability-operator/pkg/operator" + "github.com/rhobs/observability-operator/pkg/status" "github.com/rhobs/observability-operator/test/e2e/framework" ) @@ -236,19 +237,19 @@ func reconcileStack(t *testing.T) { assert.Equal(t, expected.Retention, generated.Spec.Retention) availableMs := f.GetStackWhenAvailable(t, ms.Name, ms.Namespace) - availableC := getConditionByType(availableMs.Status.Conditions, stack.AvailableCondition) - assertCondition(t, availableC, monitoringstack.AvailableReason, stack.AvailableCondition, availableMs) - reconciledC := getConditionByType(availableMs.Status.Conditions, stack.ReconciledCondition) - assertCondition(t, reconciledC, monitoringstack.ReconciledReason, stack.ReconciledCondition, availableMs) + availableC := getConditionByType(availableMs.Status.Conditions, shared.AvailableCondition) + assertCondition(t, availableC, status.AvailableReason, shared.AvailableCondition, availableMs) + reconciledC := getConditionByType(availableMs.Status.Conditions, shared.ReconciledCondition) + assertCondition(t, reconciledC, status.ReconciledReason, shared.ReconciledCondition, availableMs) } -func assertCondition(t *testing.T, c *stack.Condition, reason string, ctype stack.ConditionType, ms stack.MonitoringStack) { +func assertCondition(t *testing.T, c *shared.Condition, reason string, ctype shared.ConditionType, ms stack.MonitoringStack) { assert.Check(t, c != nil, "failed to find %s status condition for %s monitoring stack", ctype, ms.Name) - assert.Check(t, c.Status == stack.ConditionTrue, "unexpected %s condition status", ctype) + assert.Check(t, c.Status == shared.ConditionTrue, "unexpected %s condition status", ctype) assert.Check(t, c.Reason == reason, "unexpected %s condition reason", ctype) } -func getConditionByType(conditions []stack.Condition, ctype stack.ConditionType) *stack.Condition { +func getConditionByType(conditions []shared.Condition, ctype shared.ConditionType) *shared.Condition { for _, c := range conditions { if c.Type == ctype { return &c