diff --git a/config/crds/v1/all-crds.yaml b/config/crds/v1/all-crds.yaml index 5120bb3ce58..714591570d9 100644 --- a/config/crds/v1/all-crds.yaml +++ b/config/crds/v1/all-crds.yaml @@ -7303,6 +7303,15 @@ spec: description: MonitoringAssociationStatus is the status of any auto-linking to monitoring Elasticsearch clusters. type: object + observedGeneration: + description: ObservedGeneration is the most recent generation observed + for this Kibana instance. It corresponds to the metadata generation, + which is updated on mutation by the API Server. If the generation + observed in status diverges from the generation in metadata, the + Kibana controller has not yet processed the changes contained in + the Kibana specification. + format: int64 + type: integer selector: description: Selector is the label selector used to find all pods. type: string diff --git a/config/crds/v1/bases/kibana.k8s.elastic.co_kibanas.yaml b/config/crds/v1/bases/kibana.k8s.elastic.co_kibanas.yaml index 035ea3a12c6..897457680df 100644 --- a/config/crds/v1/bases/kibana.k8s.elastic.co_kibanas.yaml +++ b/config/crds/v1/bases/kibana.k8s.elastic.co_kibanas.yaml @@ -7748,6 +7748,15 @@ spec: description: MonitoringAssociationStatus is the status of any auto-linking to monitoring Elasticsearch clusters. type: object + observedGeneration: + description: ObservedGeneration is the most recent generation observed + for this Kibana instance. It corresponds to the metadata generation, + which is updated on mutation by the API Server. If the generation + observed in status diverges from the generation in metadata, the + Kibana controller has not yet processed the changes contained in + the Kibana specification. + format: int64 + type: integer selector: description: Selector is the label selector used to find all pods. type: string diff --git a/deploy/eck-operator/charts/eck-operator-crds/templates/all-crds.yaml b/deploy/eck-operator/charts/eck-operator-crds/templates/all-crds.yaml index 12f38860b6c..e0ade8daf4a 100644 --- a/deploy/eck-operator/charts/eck-operator-crds/templates/all-crds.yaml +++ b/deploy/eck-operator/charts/eck-operator-crds/templates/all-crds.yaml @@ -7345,6 +7345,15 @@ spec: description: MonitoringAssociationStatus is the status of any auto-linking to monitoring Elasticsearch clusters. type: object + observedGeneration: + description: ObservedGeneration is the most recent generation observed + for this Kibana instance. It corresponds to the metadata generation, + which is updated on mutation by the API Server. If the generation + observed in status diverges from the generation in metadata, the + Kibana controller has not yet processed the changes contained in + the Kibana specification. + format: int64 + type: integer selector: description: Selector is the label selector used to find all pods. type: string diff --git a/pkg/apis/kibana/v1/kibana_types.go b/pkg/apis/kibana/v1/kibana_types.go index 45baf9dd528..214047767b4 100644 --- a/pkg/apis/kibana/v1/kibana_types.go +++ b/pkg/apis/kibana/v1/kibana_types.go @@ -131,15 +131,25 @@ type LogsMonitoring struct { // KibanaStatus defines the observed state of Kibana type KibanaStatus struct { commonv1.DeploymentStatus `json:",inline"` + // AssociationStatus is the status of any auto-linking to Elasticsearch clusters. // This field is deprecated and will be removed in a future release. Use ElasticsearchAssociationStatus instead. AssociationStatus commonv1.AssociationStatus `json:"associationStatus,omitempty"` + // ElasticsearchAssociationStatus is the status of any auto-linking to Elasticsearch clusters. ElasticsearchAssociationStatus commonv1.AssociationStatus `json:"elasticsearchAssociationStatus,omitempty"` + // EnterpriseSearchAssociationStatus is the status of any auto-linking to Enterprise Search. EnterpriseSearchAssociationStatus commonv1.AssociationStatus `json:"enterpriseSearchAssociationStatus,omitempty"` + // MonitoringAssociationStatus is the status of any auto-linking to monitoring Elasticsearch clusters. MonitoringAssociationStatus commonv1.AssociationStatusMap `json:"monitoringAssociationStatus,omitempty"` + + // ObservedGeneration is the most recent generation observed for this Kibana instance. + // It corresponds to the metadata generation, which is updated on mutation by the API Server. + // If the generation observed in status diverges from the generation in metadata, the Kibana + // controller has not yet processed the changes contained in the Kibana specification. + ObservedGeneration int64 `json:"observedGeneration,omitempty"` } // IsMarkedForDeletion returns true if the Kibana is going to be deleted diff --git a/pkg/controller/kibana/controller.go b/pkg/controller/kibana/controller.go index 08f80ab7c2d..228ab247ebf 100644 --- a/pkg/controller/kibana/controller.go +++ b/pkg/controller/kibana/controller.go @@ -56,9 +56,8 @@ func Add(mgr manager.Manager, params operator.Parameters) error { // newReconciler returns a new reconcile.Reconciler func newReconciler(mgr manager.Manager, params operator.Parameters) *ReconcileKibana { - client := mgr.GetClient() return &ReconcileKibana{ - Client: client, + Client: mgr.GetClient(), recorder: mgr.GetEventRecorderFor(controllerName), dynamicWatches: watches.NewDynamicWatches(), params: params, @@ -162,29 +161,40 @@ func (r *ReconcileKibana) Reconcile(ctx context.Context, request reconcile.Reque } func (r *ReconcileKibana) doReconcile(ctx context.Context, request reconcile.Request, kb *kbv1.Kibana) (reconcile.Result, error) { + var ( + err error + result reconcile.Result + ) + state := NewState(request, kb) + + // defer the updating of status to ensure that the status is updated regardless of the outcome of the reconciliation. + defer func() { + statusErr := r.updateStatus(ctx, state) + if statusErr != nil && apierrors.IsConflict(statusErr) { + log.V(1).Info("Conflict while updating status", "namespace", kb.Namespace, "kibana_name", kb.Name) + result = reconcile.Result{Requeue: true} + } else if statusErr != nil { + log.Error(statusErr, "Error while updating status", "namespace", kb.Namespace, "kibana_name", kb.Name) + err = statusErr + } + }() + // Run validation in case the webhook is disabled if err := r.validate(ctx, kb); err != nil { - return reconcile.Result{}, err + return result, err } - driver, err := newDriver(r, r.dynamicWatches, r.recorder, kb, r.params.IPFamily) + var driver *driver + driver, err = newDriver(r, r.dynamicWatches, r.recorder, kb, r.params.IPFamily) if err != nil { return reconcile.Result{}, tracing.CaptureError(ctx, err) } - state := NewState(request, kb) results := driver.Reconcile(ctx, &state, kb, r.params) - // update status - err = r.updateStatus(ctx, state) - if err != nil && apierrors.IsConflict(err) { - log.V(1).Info("Conflict while updating status", "namespace", kb.Namespace, "kibana_name", kb.Name) - return reconcile.Result{Requeue: true}, nil - } - - res, err := results.WithError(err).Aggregate() + result, err = results.WithError(err).Aggregate() k8s.EmitErrorEvent(r.recorder, err, kb, events.EventReconciliationError, "Reconciliation error: %v", err) - return res, err + return result, err } func (r *ReconcileKibana) validate(ctx context.Context, kb *kbv1.Kibana) error { @@ -240,5 +250,7 @@ type State struct { // NewState creates a new reconcile state based on the given request and Kibana resource with the resource // state reset to empty. func NewState(request reconcile.Request, kb *kbv1.Kibana) State { - return State{Request: request, Kibana: kb, originalKibana: kb.DeepCopy()} + newKibana := kb.DeepCopy() + newKibana.Status.ObservedGeneration = kb.Generation + return State{Request: request, Kibana: newKibana, originalKibana: kb.DeepCopy()} } diff --git a/pkg/controller/kibana/controller_test.go b/pkg/controller/kibana/controller_test.go new file mode 100644 index 00000000000..958855ac3bf --- /dev/null +++ b/pkg/controller/kibana/controller_test.go @@ -0,0 +1,393 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License 2.0; +// you may not use this file except in compliance with the Elastic License 2.0. + +package kibana + +import ( + "context" + "encoding/json" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + commonv1 "github.com/elastic/cloud-on-k8s/pkg/apis/common/v1" + esv1 "github.com/elastic/cloud-on-k8s/pkg/apis/elasticsearch/v1" + kibanav1 "github.com/elastic/cloud-on-k8s/pkg/apis/kibana/v1" + "github.com/elastic/cloud-on-k8s/pkg/controller/common" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/operator" + "github.com/elastic/cloud-on-k8s/pkg/controller/common/watches" + "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" +) + +func TestReconcileKibana_Reconcile(t *testing.T) { + sampleElasticsearch := esv1.Elasticsearch{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-es", + Namespace: "test", + }, + Spec: esv1.ElasticsearchSpec{ + Version: "7.17.0", + }, + } + sampleKibana := kibanav1.Kibana{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-kibana", + Namespace: "test", + Generation: 2, + }, + Spec: kibanav1.KibanaSpec{ + Version: "7.17.0", + Count: 1, + }, + Status: kibanav1.KibanaStatus{ + ObservedGeneration: 1, + }, + } + defaultRequest := reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "test-kibana", + Namespace: "test", + }, + } + type fields struct { + Client k8s.Client + recorder record.EventRecorder + dynamicWatches watches.DynamicWatches + params operator.Parameters + iteration uint64 + } + type args struct { + ctx context.Context + request reconcile.Request + } + tests := []struct { + name string + fields fields + request reconcile.Request + want reconcile.Result + wantErr bool + validate func(*testing.T, fields) + }{ + { + name: "unmanaged kibana instance does increment observedGeneration", + fields: fields{ + Client: k8s.NewFakeClient( + &sampleElasticsearch, + withAnnotations(&sampleKibana, map[string]string{common.ManagedAnnotation: "false"}), + ), + recorder: record.NewFakeRecorder(100), + dynamicWatches: watches.NewDynamicWatches(), + params: operator.Parameters{}, + }, + request: defaultRequest, + want: reconcile.Result{}, + wantErr: false, + validate: func(t *testing.T, f fields) { + var kibana kibanav1.Kibana + err := f.Client.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "test-kibana"}, &kibana) + require.NoError(t, err) + require.Equal(t, kibanav1.KibanaStatus{ + DeploymentStatus: commonv1.DeploymentStatus{ + Selector: "", + Count: 0, + AvailableNodes: 0, + Version: "", + Health: commonv1.DeploymentHealth(""), + }, + ObservedGeneration: 1, + }, kibana.Status) + }, + }, + { + name: "kibana instance with legacy finalizer has finalizer removed and increments observedGeneration", + fields: fields{ + Client: k8s.NewFakeClient( + &sampleElasticsearch, + withFinalizers(&sampleKibana, []string{"finalizer.elasticsearch.k8s.elastic.co/secure-settings-secret"}), + ), + recorder: record.NewFakeRecorder(100), + dynamicWatches: watches.NewDynamicWatches(), + params: operator.Parameters{}, + }, + request: defaultRequest, + want: reconcile.Result{}, + wantErr: false, + validate: func(t *testing.T, f fields) { + var kibana kibanav1.Kibana + err := f.Client.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "test-kibana"}, &kibana) + require.NoError(t, err) + require.Len(t, kibana.ObjectMeta.Finalizers, 0) + require.Equal(t, kibanav1.KibanaStatus{ + DeploymentStatus: commonv1.DeploymentStatus{ + Selector: "common.k8s.elastic.co/type=kibana,kibana.k8s.elastic.co/name=test-kibana", + Count: 0, + AvailableNodes: 0, + Version: "", + Health: commonv1.RedHealth, + }, + ObservedGeneration: 2, + }, kibana.Status) + }, + }, + { + name: "kibana instance with validation issues returns error and increments observedGeneration", + fields: fields{ + Client: k8s.NewFakeClient( + &sampleElasticsearch, + withName(&sampleKibana, "superlongkibananamecausesvalidationissues"), + ), + recorder: record.NewFakeRecorder(100), + dynamicWatches: watches.NewDynamicWatches(), + params: operator.Parameters{}, + }, + request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "test", + Name: "superlongkibananamecausesvalidationissues", + }, + }, + want: reconcile.Result{}, + wantErr: true, + validate: func(t *testing.T, f fields) { + var kibana kibanav1.Kibana + err := f.Client.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "superlongkibananamecausesvalidationissues"}, &kibana) + require.NoError(t, err) + require.Equal(t, kibanav1.KibanaStatus{ + DeploymentStatus: commonv1.DeploymentStatus{ + Selector: "", + Count: 0, + AvailableNodes: 0, + Version: "", + Health: commonv1.DeploymentHealth(""), + }, + ObservedGeneration: 2, + }, kibana.Status) + }, + }, + { + name: "sample kibana instance returns no error and increments observedGeneration", + fields: fields{ + Client: k8s.NewFakeClient( + &sampleElasticsearch, + &sampleKibana, + ), + recorder: record.NewFakeRecorder(100), + dynamicWatches: watches.NewDynamicWatches(), + params: operator.Parameters{}, + }, + request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "test", + Name: "test-kibana", + }, + }, + want: reconcile.Result{}, + wantErr: false, + validate: func(t *testing.T, f fields) { + var kibana kibanav1.Kibana + err := f.Client.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "test-kibana"}, &kibana) + require.NoError(t, err) + require.Equal(t, kibanav1.KibanaStatus{ + DeploymentStatus: commonv1.DeploymentStatus{ + Selector: "common.k8s.elastic.co/type=kibana,kibana.k8s.elastic.co/name=test-kibana", + Count: 0, + AvailableNodes: 0, + Version: "", + Health: commonv1.RedHealth, + }, + ObservedGeneration: 2, + }, kibana.Status) + }, + }, + { + name: "sample kibana instance with es association with version that is not allowed returns no error, increments observedGeneration", + fields: fields{ + Client: k8s.NewFakeClient( + &sampleElasticsearch, + withAssociationConf(withESReference(&sampleKibana, commonv1.ObjectSelector{Name: "test-es", Namespace: "test"}), commonv1.AssociationConf{ + AuthSecretName: "test-es-elastic-user", + AuthSecretKey: "elastic", + CASecretName: "ca-secret", + CACertProvided: false, + URL: "https://test-es:9200", + // This will be considered an invalid version, as it's considered 'not reported yet'. + Version: "", + }), + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-es-elastic-user", + Namespace: "test", + }, + Data: map[string][]byte{ + "elastic": []byte("password"), + }, + }, + ), + recorder: record.NewFakeRecorder(100), + dynamicWatches: watches.NewDynamicWatches(), + params: operator.Parameters{}, + }, + request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "test", + Name: "test-kibana", + }, + }, + want: reconcile.Result{}, + wantErr: false, + validate: func(t *testing.T, f fields) { + var kibana kibanav1.Kibana + err := f.Client.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "test-kibana"}, &kibana) + require.NoError(t, err) + require.Equal(t, kibanav1.KibanaStatus{ + DeploymentStatus: commonv1.DeploymentStatus{ + Selector: "", + Count: 0, + AvailableNodes: 0, + Version: "", + Health: commonv1.DeploymentHealth(""), + }, + ObservedGeneration: 2, + }, kibana.Status) + }, + }, + { + name: "sample kibana instance with es association with valid version returns no error, increments observedGeneration, but does not updates ES association", + fields: fields{ + Client: k8s.NewFakeClient( + &sampleElasticsearch, + withTLSDisabled(withAssociationConf(withESReference(&sampleKibana, commonv1.ObjectSelector{Name: "test-es", Namespace: "test"}), commonv1.AssociationConf{ + AuthSecretName: "test-es-elastic-user", + AuthSecretKey: "elastic", + CASecretName: "ca-secret", + CACertProvided: false, + URL: "https://test-es:9200", + Version: "7.17.0", + })), + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-es-elastic-user", + Namespace: "test", + }, + Data: map[string][]byte{ + "elastic": []byte("password"), + }, + }, + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ca-secret", + Namespace: "test", + }, + Data: map[string][]byte{ + "ca.crt": []byte("fake data"), + }, + }, + ), + recorder: record.NewFakeRecorder(100), + dynamicWatches: watches.NewDynamicWatches(), + params: operator.Parameters{}, + }, + request: reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: "test", + Name: "test-kibana", + }, + }, + want: reconcile.Result{}, + wantErr: false, + validate: func(t *testing.T, f fields) { + var kibana kibanav1.Kibana + err := f.Client.Get(context.Background(), types.NamespacedName{Namespace: "test", Name: "test-kibana"}, &kibana) + require.NoError(t, err) + require.Equal(t, kibanav1.KibanaStatus{ + DeploymentStatus: commonv1.DeploymentStatus{ + Selector: "common.k8s.elastic.co/type=kibana,kibana.k8s.elastic.co/name=test-kibana", + Count: 0, + AvailableNodes: 0, + Version: "", + Health: commonv1.RedHealth, + }, + ObservedGeneration: 2, + }, kibana.Status) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &ReconcileKibana{ + Client: tt.fields.Client, + recorder: tt.fields.recorder, + dynamicWatches: tt.fields.dynamicWatches, + params: tt.fields.params, + } + got, err := r.Reconcile(context.Background(), tt.request) + if (err != nil) != tt.wantErr { + t.Errorf("ReconcileKibana.Reconcile() error = %v, wantErr %v", err, tt.wantErr) + return + } + // RequeueAfter is ignored here, as certificate reconciler sets this to expiration of the generated certificates. + if !cmp.Equal(got, tt.want, cmpopts.IgnoreFields(reconcile.Result{}, "RequeueAfter")) { + t.Errorf("ReconcileKibana.Reconcile() = %v, want %v", got, tt.want) + } + tt.validate(t, tt.fields) + }) + } +} + +func withAnnotations(kibana *kibanav1.Kibana, annotations map[string]string) *kibanav1.Kibana { + obj := kibana.DeepCopy() + obj.ObjectMeta.Annotations = annotations + return obj +} + +func withFinalizers(kibana *kibanav1.Kibana, finalizers []string) *kibanav1.Kibana { + obj := kibana.DeepCopy() + obj.ObjectMeta.Finalizers = finalizers + return obj +} + +func withName(kibana *kibanav1.Kibana, name string) *kibanav1.Kibana { + obj := kibana.DeepCopy() + obj.ObjectMeta.Name = name + return obj +} + +func withESReference(kibana *kibanav1.Kibana, selector commonv1.ObjectSelector) *kibanav1.Kibana { + obj := kibana.DeepCopy() + obj.Spec.ElasticsearchRef = selector + return obj +} + +func withAssociationConf(kibana *kibanav1.Kibana, conf commonv1.AssociationConf) *kibanav1.Kibana { + obj := kibana.DeepCopy() + association := kibanav1.KibanaEsAssociation{ + Kibana: obj, + } + association.SetAssociationConf( + &conf, + ) + b, _ := json.Marshal(&conf) + association.SetAnnotations(map[string]string{ + association.AssociationConfAnnotationName(): string(b), + }) + associated := association.Associated() + return associated.(*kibanav1.Kibana) +} + +func withTLSDisabled(kibana *kibanav1.Kibana) *kibanav1.Kibana { + obj := kibana.DeepCopy() + obj.Spec.HTTP.TLS = commonv1.TLSOptions{ + SelfSignedCertificate: &commonv1.SelfSignedCertificate{ + Disabled: true, + }, + } + return obj +} diff --git a/test/e2e/test/generation/generation.go b/test/e2e/test/generation/generation.go new file mode 100644 index 00000000000..c1cf5a6e73d --- /dev/null +++ b/test/e2e/test/generation/generation.go @@ -0,0 +1,96 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License 2.0; +// you may not use this file except in compliance with the Elastic License 2.0. + +package generation + +import ( + "context" + "errors" + "fmt" + + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + kibanav1 "github.com/elastic/cloud-on-k8s/pkg/apis/kibana/v1" + "github.com/elastic/cloud-on-k8s/test/e2e/test" +) + +func getGeneration(obj client.Object, k *test.K8sClient) (int64, error) { + if err := k.Client.Get(context.Background(), types.NamespacedName{Namespace: obj.GetNamespace(), Name: obj.GetName()}, obj); err != nil { + return 0, err + } + + return obj.GetGeneration(), nil +} + +func getObservedGeneration(obj client.Object, k *test.K8sClient) (int64, error) { + if err := k.Client.Get(context.Background(), types.NamespacedName{Namespace: obj.GetNamespace(), Name: obj.GetName()}, obj); err != nil { + return 0, err + } + + switch v := obj.(type) { + case *kibanav1.Kibana: + return v.Status.ObservedGeneration, nil + default: + return 0, fmt.Errorf("unsupported type while retrieving status.observedGeneration: %T", v) + } +} + +// RetrieveAgentGenerationsStep stores the current metadata.Generation, and status.ObservedGeneration into the given fields. +func RetrieveAgentGenerationsStep(obj client.Object, k *test.K8sClient, generation, observedGeneration *int64) test.Step { + return test.Step{ + Name: "Retrieve Objects metadata.Generation, and status.ObservedGeneration for comparison purpose", + Test: test.Eventually(func() error { + objGeneration, err := getGeneration(obj, k) + if err != nil { + return err + } + *generation = objGeneration + objObservedGeneration, err := getObservedGeneration(obj, k) + if err != nil { + return err + } + *observedGeneration = objObservedGeneration + return nil + }), + } +} + +// CompareObjectGenerationsStep compares the current object's metadata.generation, and status.observedGeneration +// and fails if they don't match expectations. +func CompareObjectGenerationsStep( + obj client.Object, + k *test.K8sClient, + isMutated bool, + previousObjectGeneration, previousObjectObservedGeneration int64) test.Step { + return test.Step{ + Name: "metadata.generation and status.observedGeneration may have been incremented from previous state, and should be equal", + Test: test.Eventually(func() error { + newObjectGeneration, err := getGeneration(obj, k) + if err != nil { + return err + } + if newObjectGeneration == 0 { + return errors.New("expected object's metadata.generation to not be empty") + } + newObjectObservedGeneration, err := getObservedGeneration(obj, k) + if err != nil { + return err + } + if newObjectObservedGeneration == 0 { + return errors.New("expected object's status.observedGeneration to not be empty") + } + if isMutated && newObjectGeneration <= previousObjectGeneration { + return errors.New("expected object's metadata.generation to be incremented") + } + if isMutated && newObjectObservedGeneration <= previousObjectObservedGeneration { + return errors.New("expected object's status.observedGeneration to be incremented") + } + if newObjectGeneration != newObjectObservedGeneration { + return fmt.Errorf("expected object's metadata.generation and status.observedGeneration to be equal; generation: %d, observedGeneration: %d", newObjectGeneration, newObjectObservedGeneration) + } + return nil + }), + } +} diff --git a/test/e2e/test/kibana/steps_mutation.go b/test/e2e/test/kibana/steps_mutation.go index 3ea07532e26..4a48d361816 100644 --- a/test/e2e/test/kibana/steps_mutation.go +++ b/test/e2e/test/kibana/steps_mutation.go @@ -10,13 +10,18 @@ import ( kbv1 "github.com/elastic/cloud-on-k8s/pkg/apis/kibana/v1" "github.com/elastic/cloud-on-k8s/pkg/utils/k8s" "github.com/elastic/cloud-on-k8s/test/e2e/test" + "github.com/elastic/cloud-on-k8s/test/e2e/test/generation" ) func (b Builder) MutationTestSteps(k *test.K8sClient) test.StepList { + isMutated := b.MutatedFrom != nil + var agentGenerationBeforeMutation, agentObservedGenerationBeforeMutation int64 return test.AnnotatePodsWithBuilderHash(b, b.MutatedFrom, k). + WithStep(generation.RetrieveAgentGenerationsStep(&b.Kibana, k, &agentGenerationBeforeMutation, &agentObservedGenerationBeforeMutation)). WithSteps(b.UpgradeTestSteps(k)). WithSteps(b.CheckK8sTestSteps(k)). - WithSteps(b.CheckStackTestSteps(k)) + WithSteps(b.CheckStackTestSteps(k)). + WithStep(generation.CompareObjectGenerationsStep(&b.Kibana, k, isMutated, agentGenerationBeforeMutation, agentObservedGenerationBeforeMutation)) } func (b Builder) MutationReversalTestContext() test.ReversalTestContext {