diff --git a/apis/datasciencecluster/v1/datasciencecluster_types.go b/apis/datasciencecluster/v1/datasciencecluster_types.go index 120ba1baf62..f181554ab35 100644 --- a/apis/datasciencecluster/v1/datasciencecluster_types.go +++ b/apis/datasciencecluster/v1/datasciencecluster_types.go @@ -31,7 +31,7 @@ import ( "github.com/opendatahub-io/opendatahub-operator/v2/components/workbenches" ) -// DataScienceCluster defines the desired state of the cluster. +// DataScienceClusterSpec defines the desired state of the cluster. type DataScienceClusterSpec struct { // Override and fine tune specific component configurations. // +operator-sdk:csv:customresourcedefinitions:type=spec,order=1 diff --git a/apis/features/v1/features_types.go b/apis/features/v1/features_types.go index fbe6e4bab27..f534be9309d 100644 --- a/apis/features/v1/features_types.go +++ b/apis/features/v1/features_types.go @@ -18,6 +18,13 @@ type FeatureTracker struct { Status FeatureTrackerStatus `json:"status,omitempty"` } +type OwnerType string + +const ( + ComponentType OwnerType = "Component" + DSCIType OwnerType = "DSCI" +) + func (s *FeatureTracker) ToOwnerReference() metav1.OwnerReference { return metav1.OwnerReference{ APIVersion: s.APIVersion, @@ -27,8 +34,16 @@ func (s *FeatureTracker) ToOwnerReference() metav1.OwnerReference { } } +// Source describes the type of object that created the related Feature to this FeatureTracker. +type Source struct { + Type OwnerType `json:"type,omitempty"` + Name string `json:"name,omitempty"` +} + // FeatureTrackerSpec defines the desired state of FeatureTracker. type FeatureTrackerSpec struct { + Source Source `json:"source,omitempty"` + AppNamespace string `json:"appNamespace,omitempty"` } // FeatureTrackerStatus defines the observed state of FeatureTracker. diff --git a/apis/features/v1/zz_generated.deepcopy.go b/apis/features/v1/zz_generated.deepcopy.go index 19bc376284c..3317ab13bc1 100644 --- a/apis/features/v1/zz_generated.deepcopy.go +++ b/apis/features/v1/zz_generated.deepcopy.go @@ -87,6 +87,7 @@ func (in *FeatureTrackerList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FeatureTrackerSpec) DeepCopyInto(out *FeatureTrackerSpec) { *out = *in + out.Source = in.Source } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FeatureTrackerSpec. @@ -113,3 +114,18 @@ func (in *FeatureTrackerStatus) DeepCopy() *FeatureTrackerStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Source) DeepCopyInto(out *Source) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Source. +func (in *Source) DeepCopy() *Source { + if in == nil { + return nil + } + out := new(Source) + in.DeepCopyInto(out) + return out +} diff --git a/components/kserve/kserve.go b/components/kserve/kserve.go index 99d14c2563e..8d01f0a253c 100644 --- a/components/kserve/kserve.go +++ b/components/kserve/kserve.go @@ -184,9 +184,10 @@ func (k *Kserve) configureServerless(instance *dsciv1.DSCInitializationSpec) err case operatorv1.Managed: // standard workflow to create CR switch instance.ServiceMesh.ManagementState { case operatorv1.Unmanaged, operatorv1.Removed: - return fmt.Errorf("ServiceMesh is need to set to 'Managaed' in DSCI CR, it is required by KServe serving field") + return fmt.Errorf("ServiceMesh is need to set to 'Managed' in DSCI CR, it is required by KServe serving field") } - serverlessInitializer := feature.NewFeaturesInitializer(instance, k.configureServerlessFeatures) + + serverlessInitializer := feature.ComponentFeaturesInitializer(k, instance, k.configureServerlessFeatures()) if err := serverlessInitializer.Prepare(); err != nil { return err @@ -200,7 +201,7 @@ func (k *Kserve) configureServerless(instance *dsciv1.DSCInitializationSpec) err } func (k *Kserve) removeServerlessFeatures(instance *dsciv1.DSCInitializationSpec) error { - serverlessInitializer := feature.NewFeaturesInitializer(instance, k.configureServerlessFeatures) + serverlessInitializer := feature.ComponentFeaturesInitializer(k, instance, k.configureServerlessFeatures()) if err := serverlessInitializer.Prepare(); err != nil { return err diff --git a/components/kserve/serverless_setup.go b/components/kserve/serverless_setup.go index 83d2b6cf877..db6ac0aafc2 100644 --- a/components/kserve/serverless_setup.go +++ b/components/kserve/serverless_setup.go @@ -14,50 +14,52 @@ const ( templatesDir = "templates/serverless" ) -func (k *Kserve) configureServerlessFeatures(s *feature.FeaturesInitializer) error { - servingDeployment, err := feature.CreateFeature("serverless-serving-deployment"). - For(s.DSCInitializationSpec). - Manifests( - path.Join(templatesDir, "serving-install"), - ). - WithData(PopulateComponentSettings(k)). - PreConditions( - serverless.EnsureServerlessOperatorInstalled, - serverless.EnsureServerlessAbsent, - servicemesh.EnsureServiceMeshInstalled, - feature.CreateNamespaceIfNotExists(knativeServingNamespace), - ). - PostConditions( - feature.WaitForPodsToBeReady(knativeServingNamespace), - ). - Load() - if err != nil { - return err - } - s.Features = append(s.Features, servingDeployment) +func (k *Kserve) configureServerlessFeatures() feature.DefinedFeatures { + return func(initializer *feature.FeaturesInitializer) error { + servingDeployment, err := feature.CreateFeature("serverless-serving-deployment"). + With(initializer.DSCInitializationSpec). + From(initializer.Source). + Manifests( + path.Join(templatesDir, "serving-install"), + ). + WithData(PopulateComponentSettings(k)). + PreConditions( + serverless.EnsureServerlessOperatorInstalled, + serverless.EnsureServerlessAbsent, + servicemesh.EnsureServiceMeshInstalled, + feature.CreateNamespaceIfNotExists(knativeServingNamespace), + ). + PostConditions( + feature.WaitForPodsToBeReady(knativeServingNamespace), + ). + Load() + if err != nil { + return err + } + initializer.Features = append(initializer.Features, servingDeployment) - servingIstioGateways, err := feature.CreateFeature("serverless-serving-gateways"). - For(s.DSCInitializationSpec). - PreConditions( - // Check serverless is installed - feature.WaitForResourceToBeCreated(knativeServingNamespace, gvr.KnativeServing), - ). - WithData( - serverless.ServingDefaultValues, - serverless.ServingIngressDomain, - PopulateComponentSettings(k), - ). - WithResources(serverless.ServingCertificateResource). - Manifests( - path.Join(templatesDir, "serving-istio-gateways"), - ). - Load() - if err != nil { - return err + servingIstioGateways, err := feature.CreateFeature("serverless-serving-gateways"). + With(initializer.DSCInitializationSpec). + From(initializer.Source). + PreConditions( + // Check serverless is installed + feature.WaitForResourceToBeCreated(knativeServingNamespace, gvr.KnativeServing)). + WithData( + serverless.ServingDefaultValues, + serverless.ServingIngressDomain, + PopulateComponentSettings(k), + ). + WithResources(serverless.ServingCertificateResource). + Manifests( + path.Join(templatesDir, "serving-istio-gateways"), + ). + Load() + if err != nil { + return err + } + initializer.Features = append(initializer.Features, servingIstioGateways) + return nil } - s.Features = append(s.Features, servingIstioGateways) - - return nil } func PopulateComponentSettings(k *Kserve) feature.Action { diff --git a/config/crd/bases/datasciencecluster.opendatahub.io_datascienceclusters.yaml b/config/crd/bases/datasciencecluster.opendatahub.io_datascienceclusters.yaml index c0e4a1e2562..580edd90538 100644 --- a/config/crd/bases/datasciencecluster.opendatahub.io_datascienceclusters.yaml +++ b/config/crd/bases/datasciencecluster.opendatahub.io_datascienceclusters.yaml @@ -36,7 +36,7 @@ spec: metadata: type: object spec: - description: DataScienceCluster defines the desired state of the cluster. + description: DataScienceClusterSpec defines the desired state of the cluster. properties: components: description: Override and fine tune specific component configurations. diff --git a/config/crd/bases/features.opendatahub.io_featuretrackers.yaml b/config/crd/bases/features.opendatahub.io_featuretrackers.yaml index dc544d8f79f..8e71f2448bc 100644 --- a/config/crd/bases/features.opendatahub.io_featuretrackers.yaml +++ b/config/crd/bases/features.opendatahub.io_featuretrackers.yaml @@ -41,6 +41,18 @@ spec: type: object spec: description: FeatureTrackerSpec defines the desired state of FeatureTracker. + properties: + appNamespace: + type: string + source: + description: Source describes the type of object that created the + related Feature to this FeatureTracker. + properties: + name: + type: string + type: + type: string + type: object type: object status: description: FeatureTrackerStatus defines the observed state of FeatureTracker. diff --git a/controllers/dscinitialization/servicemesh_setup.go b/controllers/dscinitialization/servicemesh_setup.go index e9250d57922..e39ed64bfe7 100644 --- a/controllers/dscinitialization/servicemesh_setup.go +++ b/controllers/dscinitialization/servicemesh_setup.go @@ -16,7 +16,7 @@ const templatesDir = "templates/servicemesh" func (r *DSCInitializationReconciler) configureServiceMesh(instance *dsciv1.DSCInitialization) error { switch instance.Spec.ServiceMesh.ManagementState { case operatorv1.Managed: - serviceMeshInitializer := feature.NewFeaturesInitializer(&instance.Spec, configureServiceMeshFeatures) + serviceMeshInitializer := feature.ClusterFeaturesInitializer(instance, configureServiceMeshFeatures()) if err := serviceMeshInitializer.Prepare(); err != nil { r.Log.Error(err, "failed configuring service mesh resources") r.Recorder.Eventf(instance, corev1.EventTypeWarning, "DSCInitializationReconcileError", "failed configuring service mesh resources") @@ -43,8 +43,7 @@ func (r *DSCInitializationReconciler) configureServiceMesh(instance *dsciv1.DSCI func (r *DSCInitializationReconciler) removeServiceMesh(instance *dsciv1.DSCInitialization) error { // on condition of Managed, do not handle Removed when set to Removed it trigger DSCI reconcile to cleanup if instance.Spec.ServiceMesh.ManagementState == operatorv1.Managed { - serviceMeshInitializer := feature.NewFeaturesInitializer(&instance.Spec, configureServiceMeshFeatures) - + serviceMeshInitializer := feature.ClusterFeaturesInitializer(instance, configureServiceMeshFeatures()) if err := serviceMeshInitializer.Prepare(); err != nil { r.Log.Error(err, "failed configuring service mesh resources") r.Recorder.Eventf(instance, corev1.EventTypeWarning, "DSCInitializationReconcileError", "failed configuring service mesh resources") @@ -63,42 +62,46 @@ func (r *DSCInitializationReconciler) removeServiceMesh(instance *dsciv1.DSCInit return nil } -func configureServiceMeshFeatures(s *feature.FeaturesInitializer) error { - serviceMeshSpec := s.ServiceMesh - - smcpCreation, errSmcp := feature.CreateFeature("mesh-control-plane-creation"). - For(s.DSCInitializationSpec). - Manifests( - path.Join(templatesDir, "base", "create-smcp.tmpl"), - ). - PreConditions( - servicemesh.EnsureServiceMeshOperatorInstalled, - feature.CreateNamespaceIfNotExists(serviceMeshSpec.ControlPlane.Namespace), - ). - PostConditions( - feature.WaitForPodsToBeReady(serviceMeshSpec.ControlPlane.Namespace), - ). - Load() - if errSmcp != nil { - return errSmcp - } - s.Features = append(s.Features, smcpCreation) +func configureServiceMeshFeatures() feature.DefinedFeatures { + return func(initializer *feature.FeaturesInitializer) error { + serviceMeshSpec := initializer.DSCInitializationSpec.ServiceMesh - if serviceMeshSpec.ControlPlane.MetricsCollection == "Istio" { - metricsCollection, errMetrics := feature.CreateFeature("mesh-metrics-collection"). - For(s.DSCInitializationSpec). + smcpCreation, errSmcp := feature.CreateFeature("mesh-control-plane-creation"). + With(initializer.DSCInitializationSpec). + From(initializer.Source). + Manifests( + path.Join(templatesDir, "base", "create-smcp.tmpl"), + ). PreConditions( - servicemesh.EnsureServiceMeshInstalled, + servicemesh.EnsureServiceMeshOperatorInstalled, + feature.CreateNamespaceIfNotExists(serviceMeshSpec.ControlPlane.Namespace), ). - Manifests( - path.Join(templatesDir, "metrics-collection"), + PostConditions( + feature.WaitForPodsToBeReady(serviceMeshSpec.ControlPlane.Namespace), ). Load() - if errMetrics != nil { - return errMetrics + + if errSmcp != nil { + return errSmcp + } + initializer.Features = append(initializer.Features, smcpCreation) + + if serviceMeshSpec.ControlPlane.MetricsCollection == "Istio" { + metricsCollection, errMetrics := feature.CreateFeature("mesh-metrics-collection"). + With(initializer.DSCInitializationSpec). + From(initializer.Source). + Manifests( + path.Join(templatesDir, "metrics-collection"), + ). + PreConditions( + servicemesh.EnsureServiceMeshInstalled, + ). + Load() + if errMetrics != nil { + return errMetrics + } + initializer.Features = append(initializer.Features, metricsCollection) } - s.Features = append(s.Features, metricsCollection) + return nil } - - return nil } diff --git a/pkg/feature/builder.go b/pkg/feature/builder.go index c84324f38bd..19cf04e45eb 100644 --- a/pkg/feature/builder.go +++ b/pkg/feature/builder.go @@ -27,24 +27,44 @@ type featureBuilder struct { fsys fs.FS } -func CreateFeature(name string) *featureBuilder { //nolint:golint,revive //No need to export featureBuilder. - return &featureBuilder{ +func CreateFeature(name string) *featureName { //nolint:golint,revive //No need to export featureBuilder. + return &featureName{ name: name, - fsys: embeddedFiles, } } -func (fb *featureBuilder) For(spec *v1.DSCInitializationSpec) *featureBuilder { +type featureName struct { + name string +} + +func (fn *featureName) With(spec *v1.DSCInitializationSpec) *featureSource { + return &featureSource{ + spec: spec, + name: fn.name, + } +} + +type featureSource struct { + spec *v1.DSCInitializationSpec + name string +} + +func (fo *featureSource) From(source featurev1.Source) *featureBuilder { createSpec := func(f *Feature) error { f.Spec = &Spec{ - ServiceMeshSpec: &spec.ServiceMesh, + ServiceMeshSpec: &fo.spec.ServiceMesh, Serving: &infrav1.ServingSpec{}, - AppNamespace: spec.ApplicationsNamespace, + Source: &source, + AppNamespace: fo.spec.ApplicationsNamespace, } return nil } + fb := &featureBuilder{ + name: fo.name, + fsys: embeddedFiles, + } // Ensures creation of .Spec object is always invoked first fb.builders = append([]partialBuilder{createSpec}, fb.builders...) diff --git a/pkg/feature/feature_tracker_handler.go b/pkg/feature/feature_tracker_handler.go index a8982059c34..673ad2f54b6 100644 --- a/pkg/feature/feature_tracker_handler.go +++ b/pkg/feature/feature_tracker_handler.go @@ -60,6 +60,10 @@ func (f *Feature) getFeatureTracker() (*featurev1.FeatureTracker, error) { ObjectMeta: metav1.ObjectMeta{ Name: f.Spec.AppNamespace + "-" + common.TrimToRFC1123Name(f.Name), }, + Spec: featurev1.FeatureTrackerSpec{ + Source: *f.Spec.Source, + AppNamespace: f.Spec.AppNamespace, + }, } err := f.Client.Get(context.Background(), client.ObjectKeyFromObject(tracker), tracker) diff --git a/pkg/feature/initializer.go b/pkg/feature/initializer.go index 029de8733a4..412bd62b9f4 100644 --- a/pkg/feature/initializer.go +++ b/pkg/feature/initializer.go @@ -4,19 +4,31 @@ import ( "github.com/hashicorp/go-multierror" v1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/dscinitialization/v1" + featurev1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/features/v1" + "github.com/opendatahub-io/opendatahub-operator/v2/components" ) type FeaturesInitializer struct { *v1.DSCInitializationSpec - definedFeatures DefinedFeatures + Source featurev1.Source Features []*Feature + definedFeatures DefinedFeatures } -type DefinedFeatures func(s *FeaturesInitializer) error +type DefinedFeatures func(initializer *FeaturesInitializer) error + +func ClusterFeaturesInitializer(dsci *v1.DSCInitialization, def DefinedFeatures) *FeaturesInitializer { + return &FeaturesInitializer{ + DSCInitializationSpec: &dsci.Spec, + Source: featurev1.Source{Type: featurev1.DSCIType, Name: dsci.Name}, + definedFeatures: def, + } +} -func NewFeaturesInitializer(spec *v1.DSCInitializationSpec, def DefinedFeatures) *FeaturesInitializer { +func ComponentFeaturesInitializer(component components.ComponentInterface, spec *v1.DSCInitializationSpec, def DefinedFeatures) *FeaturesInitializer { return &FeaturesInitializer{ DSCInitializationSpec: spec, + Source: featurev1.Source{Type: featurev1.ComponentType, Name: component.GetComponentName()}, definedFeatures: def, } } diff --git a/pkg/feature/types.go b/pkg/feature/types.go index 7b5da4cc7b8..4857bb1d243 100644 --- a/pkg/feature/types.go +++ b/pkg/feature/types.go @@ -16,6 +16,7 @@ type Spec struct { KnativeCertificateSecret string KnativeIngressDomain string Tracker *featurev1.FeatureTracker + Source *featurev1.Source } type OAuth struct { diff --git a/tests/envtestutil/utils.go b/tests/envtestutil/utils.go index 3a6d2396479..e24b286560e 100644 --- a/tests/envtestutil/utils.go +++ b/tests/envtestutil/utils.go @@ -4,6 +4,8 @@ import ( "fmt" "os" "path/filepath" + + featurev1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/features/v1" ) func FindProjectRoot() (string, error) { @@ -27,3 +29,11 @@ func FindProjectRoot() (string, error) { return "", fmt.Errorf("project root not found") } + +// NewSource creates an origin object with specified component and name. +func NewSource(component featurev1.OwnerType, name string) featurev1.Source { + return featurev1.Source{ + Type: component, + Name: name, + } +} diff --git a/tests/integration/features/features_int_test.go b/tests/integration/features/features_int_test.go index 218bedb6910..2efc2fb6232 100644 --- a/tests/integration/features/features_int_test.go +++ b/tests/integration/features/features_int_test.go @@ -12,8 +12,10 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" dscv1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/dscinitialization/v1" + featurev1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/features/v1" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/feature" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/gvr" "github.com/opendatahub-io/opendatahub-operator/v2/tests/envtestutil" @@ -48,9 +50,11 @@ var _ = Describe("feature preconditions", func() { namespace = envtestutil.AppendRandomNameTo(testFeatureName) dsciSpec := newDSCInitializationSpec(namespace) + source := envtestutil.NewSource(featurev1.DSCIType, "default") var err error testFeature, err = feature.CreateFeature(testFeatureName). - For(dsciSpec). + With(dsciSpec). + From(source). UsingConfig(envTest.Config). Load() Expect(err).ToNot(HaveOccurred()) @@ -88,10 +92,12 @@ var _ = Describe("feature preconditions", func() { var ( dsciSpec *dscv1.DSCInitializationSpec verificationFeature *feature.Feature + source featurev1.Source ) BeforeEach(func() { dsciSpec = newDSCInitializationSpec("default") + source = envtestutil.NewSource(featurev1.DSCIType, "default") }) It("should successfully check for existing CRD", func() { @@ -100,7 +106,8 @@ var _ = Describe("feature preconditions", func() { var err error verificationFeature, err = feature.CreateFeature("CRD verification"). - For(dsciSpec). + With(dsciSpec). + From(source). UsingConfig(envTest.Config). PreConditions(feature.EnsureCRDIsInstalled(name)). Load() @@ -119,7 +126,8 @@ var _ = Describe("feature preconditions", func() { var err error verificationFeature, err = feature.CreateFeature("CRD verification"). - For(dsciSpec). + With(dsciSpec). + From(source). UsingConfig(envTest.Config). PreConditions(feature.EnsureCRDIsInstalled(name)). Load() @@ -142,17 +150,20 @@ var _ = Describe("feature cleanup", func() { var ( namespace string dsciSpec *dscv1.DSCInitializationSpec + source featurev1.Source ) BeforeAll(func() { namespace = envtestutil.AppendRandomNameTo("feature-tracker-test") dsciSpec = newDSCInitializationSpec(namespace) + source = envtestutil.NewSource(featurev1.DSCIType, "default") }) It("should successfully create resource and associated feature tracker", func() { // given createConfigMap, err := feature.CreateFeature("create-cfg-map"). - For(dsciSpec). + With(dsciSpec). + From(source). UsingConfig(envTest.Config). PreConditions( feature.CreateNamespaceIfNotExists(namespace), @@ -177,7 +188,8 @@ var _ = Describe("feature cleanup", func() { // recreating feature struct again as it would happen in the reconcile // given createConfigMap, err := feature.CreateFeature("create-cfg-map"). - For(dsciSpec). + With(dsciSpec). + From(source). UsingConfig(envTest.Config). PreConditions( feature.CreateNamespaceIfNotExists(namespace), @@ -202,28 +214,53 @@ var _ = Describe("feature cleanup", func() { }) -func createTestSecret(namespace string) func(f *feature.Feature) error { - return func(f *feature.Feature) error { - secret := &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-secret", - Namespace: namespace, - OwnerReferences: []metav1.OwnerReference{ - f.AsOwnerReference(), - }, - }, - Data: map[string][]byte{ - "test": []byte("test"), - }, - } +var _ = Describe("feature trackers", func() { + Context("ensuring feature trackers indicate status and phase", func() { - _, err := f.Clientset.CoreV1(). - Secrets(namespace). - Create(context.TODO(), secret, metav1.CreateOptions{}) + var ( + dsciSpec *dscv1.DSCInitializationSpec + source featurev1.Source + ) - return err - } -} + BeforeEach(func() { + dsciSpec = newDSCInitializationSpec("default") + source = envtestutil.NewSource(featurev1.DSCIType, "default") + }) + + It("should correctly indicate source in the feature tracker", func() { + verificationFeature, err := feature.CreateFeature("empty-feature"). + With(dsciSpec). + From(source). + UsingConfig(envTest.Config). + Load() + Expect(err).ToNot(HaveOccurred()) + + // when + Expect(verificationFeature.Apply()).To(Succeed()) + + // then + featureTracker := getFeatureTracker("default-empty-feature") + Expect(featureTracker.Spec.Source.Name).To(Equal("default")) + Expect(featureTracker.Spec.Source.Type).To(Equal(featurev1.DSCIType)) + }) + + It("should correctly indicate app namespace in the feature tracker", func() { + verificationFeature, err := feature.CreateFeature("empty-feature"). + With(dsciSpec). + From(source). + UsingConfig(envTest.Config). + Load() + Expect(err).ToNot(HaveOccurred()) + + // when + Expect(verificationFeature.Apply()).To(Succeed()) + + // then + featureTracker := getFeatureTracker("default-empty-feature") + Expect(featureTracker.Spec.AppNamespace).To(Equal("default")) + }) + }) +}) var _ = Describe("Manifest sources", func() { Context("using various manifest sources", func() { @@ -231,12 +268,14 @@ var _ = Describe("Manifest sources", func() { var ( objectCleaner *envtestutil.Cleaner dsciSpec *dscv1.DSCInitializationSpec + source featurev1.Source namespace = "default" ) BeforeEach(func() { objectCleaner = envtestutil.CreateCleaner(envTestClient, envTest.Config, timeout, interval) dsciSpec = newDSCInitializationSpec(namespace) + source = envtestutil.NewSource(featurev1.DSCIType, "namespace") }) It("should be able to process an embedded template from the default location", func() { @@ -249,7 +288,8 @@ var _ = Describe("Manifest sources", func() { serviceMeshSpec.ControlPlane.Namespace = "service-ns" createService, err := feature.CreateFeature("create-control-plane"). - For(dsciSpec). + With(dsciSpec). + From(source). Manifests(path.Join(templatesDir, "serverless", "serving-istio-gateways", "local-gateway-svc.tmpl")). UsingConfig(envTest.Config). Load() @@ -272,7 +312,8 @@ var _ = Describe("Manifest sources", func() { defer objectCleaner.DeleteAll(ns) createGateway, err := feature.CreateFeature("create-gateway"). - For(dsciSpec). + With(dsciSpec). + From(source). Manifests(path.Join(templatesDir, "serverless", "serving-istio-gateways", "istio-local-gateway.yaml")). UsingConfig(envTest.Config). Load() @@ -290,7 +331,8 @@ var _ = Describe("Manifest sources", func() { It("should be able to process an embedded file from a non default location", func() { createNs, err := feature.CreateFeature("create-ns"). - For(dsciSpec). + With(dsciSpec). + From(source). ManifestSource(testEmbeddedFiles). Manifests(path.Join(templatesDir, "namespace.yaml")). UsingConfig(envTest.Config). @@ -319,7 +361,8 @@ metadata: Expect(err).ToNot(HaveOccurred()) createNs, err := feature.CreateFeature("create-ns"). - For(dsciSpec). + With(dsciSpec). + From(source). ManifestSource(os.DirFS(tempDir)). Manifests(path.Join("namespace.yaml")). // must be relative to root DirFS defined above UsingConfig(envTest.Config). @@ -338,6 +381,29 @@ metadata: }) }) +func createTestSecret(namespace string) func(f *feature.Feature) error { + return func(f *feature.Feature) error { + secret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-secret", + Namespace: namespace, + OwnerReferences: []metav1.OwnerReference{ + f.AsOwnerReference(), + }, + }, + Data: map[string][]byte{ + "test": []byte("test"), + }, + } + + _, err := f.Clientset.CoreV1(). + Secrets(namespace). + Create(context.TODO(), secret, metav1.CreateOptions{}) + + return err + } +} + func createNamespace(name string) *v1.Namespace { return &v1.Namespace{ ObjectMeta: metav1.ObjectMeta{ @@ -346,6 +412,17 @@ func createNamespace(name string) *v1.Namespace { } } +func getFeatureTracker(name string) *featurev1.FeatureTracker { + tracker := &featurev1.FeatureTracker{} + err := envTestClient.Get(context.Background(), client.ObjectKey{ + Name: name, + }, tracker) + + Expect(err).ToNot(HaveOccurred()) + + return tracker +} + func newDSCInitializationSpec(ns string) *dscv1.DSCInitializationSpec { spec := dscv1.DSCInitializationSpec{} spec.ApplicationsNamespace = ns diff --git a/tests/integration/features/features_suite_int_test.go b/tests/integration/features/features_suite_int_test.go index 8b7941f4f8e..1d991d17488 100644 --- a/tests/integration/features/features_suite_int_test.go +++ b/tests/integration/features/features_suite_int_test.go @@ -73,6 +73,9 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) Expect(config).NotTo(BeNil()) + err = featurev1.AddToScheme(testScheme) + Expect(err).NotTo(HaveOccurred()) + envTestClient, err = client.New(config, client.Options{Scheme: testScheme}) Expect(err).NotTo(HaveOccurred()) Expect(envTestClient).NotTo(BeNil()) diff --git a/tests/integration/features/serverless_feature_test.go b/tests/integration/features/serverless_feature_test.go index 550ba6bc543..c4a642c58c9 100644 --- a/tests/integration/features/serverless_feature_test.go +++ b/tests/integration/features/serverless_feature_test.go @@ -10,6 +10,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" + featurev1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/features/v1" infrav1 "github.com/opendatahub-io/opendatahub-operator/v2/infrastructure/v1" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/feature" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/feature/serverless" @@ -77,8 +78,10 @@ var _ = Describe("Serverless feature", func() { namespace := envtestutil.AppendRandomNameTo(testFeatureName) dsciSpec := newDSCInitializationSpec(namespace) + source := envtestutil.NewSource(featurev1.ComponentType, "kserve") testFeature, err = feature.CreateFeature(testFeatureName). - For(dsciSpec). + With(dsciSpec). + From(source). UsingConfig(envTest.Config). Load() diff --git a/tests/integration/features/servicemesh_feature_test.go b/tests/integration/features/servicemesh_feature_test.go index a9615388207..2b6fb4086bf 100644 --- a/tests/integration/features/servicemesh_feature_test.go +++ b/tests/integration/features/servicemesh_feature_test.go @@ -13,6 +13,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" + featurev1 "github.com/opendatahub-io/opendatahub-operator/v2/apis/features/v1" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/feature" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/feature/servicemesh" "github.com/opendatahub-io/opendatahub-operator/v2/pkg/gvr" @@ -22,6 +23,48 @@ import ( . "github.com/onsi/gomega" ) +const serviceMeshControlPlaneCRD = `apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + labels: + maistra-version: 2.4.2 + annotations: + service.beta.openshift.io/inject-cabundle: "true" + controller-gen.kubebuilder.io/version: v0.4.1 + name: servicemeshcontrolplanes.maistra.io +spec: + group: maistra.io + names: + categories: + - maistra-io + kind: ServiceMeshControlPlane + listKind: ServiceMeshControlPlaneList + plural: servicemeshcontrolplanes + shortNames: + - smcp + singular: servicemeshcontrolplane + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + type: object + x-kubernetes-preserve-unknown-fields: true + served: true + storage: false + subresources: + status: {} + - name: v2 + schema: + openAPIV3Schema: + type: object + x-kubernetes-preserve-unknown-fields: true + served: true + storage: true + subresources: + status: {} +` + var _ = Describe("Service Mesh feature", func() { var testFeature *feature.Feature var objectCleaner *envtestutil.Cleaner @@ -36,8 +79,10 @@ var _ = Describe("Service Mesh feature", func() { namespace := envtestutil.AppendRandomNameTo(testFeatureName) dsciSpec := newDSCInitializationSpec(namespace) + source := envtestutil.NewSource(featurev1.DSCIType, "default") testFeature, err = feature.CreateFeature(testFeatureName). - For(dsciSpec). + With(dsciSpec). + From(source). UsingConfig(envTest.Config). Load() @@ -45,58 +90,53 @@ var _ = Describe("Service Mesh feature", func() { }) Describe("preconditions", func() { - When("operator is not installed", func() { It("operator presence check should return an error", func() { - Expect(servicemesh.EnsureServiceMeshOperatorInstalled(testFeature)).ToNot(Succeed()) + Expect(servicemesh.EnsureServiceMeshOperatorInstalled(testFeature)).To(HaveOccurred()) }) }) - When("operator is installed", func() { - - It("should fail checking operator presence prerequisite when CRD not installed", func() { - Expect(servicemesh.EnsureServiceMeshOperatorInstalled(testFeature)).ToNot(Succeed()) + var smcpCrdObj *apiextensionsv1.CustomResourceDefinition + + BeforeEach(func() { + // Create SMCP the CRD + smcpCrdObj = &apiextensionsv1.CustomResourceDefinition{} + Expect(yaml.Unmarshal([]byte(serviceMeshControlPlaneCRD), smcpCrdObj)).ToNot(HaveOccurred()) + c, err := client.New(envTest.Config, client.Options{}) + Expect(err).ToNot(HaveOccurred()) + Expect(c.Create(context.TODO(), smcpCrdObj)).ToNot(HaveOccurred()) + + crdOptions := envtest.CRDInstallOptions{PollInterval: interval, MaxTime: timeout} + err = envtest.WaitForCRDs(envTest.Config, []*apiextensionsv1.CustomResourceDefinition{smcpCrdObj}, crdOptions) + Expect(err).ToNot(HaveOccurred()) }) - - It("should succeed checking operator presence prerequisite when CRD installed", func() { - // given - smcpCRD := installServiceMeshControlPlaneCRD() - defer objectCleaner.DeleteAll(smcpCRD) - - // then + AfterEach(func() { + // Delete SMCP CRD + objectCleaner.DeleteAll(smcpCrdObj) + }) + It("operator presence check should succeed", func() { Expect(servicemesh.EnsureServiceMeshOperatorInstalled(testFeature)).To(Succeed()) }) - It("should find installed Service Mesh Control Plane", func() { - // given - smcpCRD := installServiceMeshControlPlaneCRD() - defer objectCleaner.DeleteAll(smcpCRD) + c, err := client.New(envTest.Config, client.Options{}) + Expect(err).ToNot(HaveOccurred()) - // when ns := envtestutil.AppendRandomNameTo(testNamespacePrefix) nsResource := createNamespace(ns) - Expect(envTestClient.Create(context.Background(), nsResource)).To(Succeed()) + Expect(c.Create(context.Background(), nsResource)).To(Succeed()) defer objectCleaner.DeleteAll(nsResource) createServiceMeshControlPlane("test-name", ns) - // then testFeature.Spec.ControlPlane.Namespace = ns testFeature.Spec.ControlPlane.Name = "test-name" Expect(servicemesh.EnsureServiceMeshInstalled(testFeature)).To(Succeed()) }) - It("should fail to find Service Mesh Control Plane if not present", func() { - // given - smcpCRD := installServiceMeshControlPlaneCRD() - defer objectCleaner.DeleteAll(smcpCRD) - - // then Expect(servicemesh.EnsureServiceMeshInstalled(testFeature)).ToNot(Succeed()) }) }) }) - }) func getGateway(cfg *rest.Config, namespace, name string) (*unstructured.Unstructured, error) { @@ -117,63 +157,6 @@ func getGateway(cfg *rest.Config, namespace, name string) (*unstructured.Unstruc return gateway, nil } -func installServiceMeshControlPlaneCRD() *apiextensionsv1.CustomResourceDefinition { - // Create SMCP the CRD - smcpCrdObj := &apiextensionsv1.CustomResourceDefinition{} - Expect(yaml.Unmarshal([]byte(serviceMeshControlPlaneCRD), smcpCrdObj)).To(Succeed()) - c, err := client.New(envTest.Config, client.Options{}) - Expect(err).ToNot(HaveOccurred()) - - Expect(c.Create(context.TODO(), smcpCrdObj)).To(Succeed()) - - crdOptions := envtest.CRDInstallOptions{PollInterval: interval, MaxTime: timeout} - Expect(envtest.WaitForCRDs(envTest.Config, []*apiextensionsv1.CustomResourceDefinition{smcpCrdObj}, crdOptions)).To(Succeed()) - - return smcpCrdObj -} - -const serviceMeshControlPlaneCRD = `apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - labels: - maistra-version: 2.4.2 - annotations: - service.beta.openshift.io/inject-cabundle: "true" - controller-gen.kubebuilder.io/version: v0.4.1 - name: servicemeshcontrolplanes.maistra.io -spec: - group: maistra.io - names: - categories: - - maistra-io - kind: ServiceMeshControlPlane - listKind: ServiceMeshControlPlaneList - plural: servicemeshcontrolplanes - shortNames: - - smcp - singular: servicemeshcontrolplane - scope: Namespaced - versions: - - name: v1 - schema: - openAPIV3Schema: - type: object - x-kubernetes-preserve-unknown-fields: true - served: true - storage: false - subresources: - status: {} - - name: v2 - schema: - openAPIV3Schema: - type: object - x-kubernetes-preserve-unknown-fields: true - served: true - storage: true - subresources: - status: {} -` - func createServiceMeshControlPlane(name, namespace string) { serviceMeshControlPlane := &unstructured.Unstructured{ Object: map[string]interface{}{