From 21e6dfedf811fabf84f8d812edf727e53f528d6b Mon Sep 17 00:00:00 2001 From: "Ricardo M. Oliveira" Date: Fri, 5 Jul 2024 15:03:12 -0300 Subject: [PATCH] Expose API Server and Envoy endpoints and add a new Envoy condition Signed-off-by: Ricardo M. Oliveira --- api/v1alpha1/dspipeline_types.go | 14 ++++ api/v1alpha1/zz_generated.deepcopy.go | 33 ++++++++ ...b.io_datasciencepipelinesapplications.yaml | 17 +++++ .../overlays/make-deploy/kustomization.yaml | 4 +- .../v2/dspa-all-fields/dspa_all_fields.yaml | 13 ++++ controllers/apiserver.go | 45 +++++++++++ controllers/apiserver_test.go | 56 +++++++++++++- controllers/config/defaults.go | 1 + controllers/dspastatus/dspa_status.go | 16 ++++ controllers/dspipeline_controller.go | 46 +++++++++++ controllers/metrics.go | 11 +++ controllers/mlmd.go | 50 ++++++++++++ controllers/mlmd_test.go | 76 ++++++++++++++++++- 13 files changed, 378 insertions(+), 4 deletions(-) diff --git a/api/v1alpha1/dspipeline_types.go b/api/v1alpha1/dspipeline_types.go index 29cff1a58..3bb2f535b 100644 --- a/api/v1alpha1/dspipeline_types.go +++ b/api/v1alpha1/dspipeline_types.go @@ -381,9 +381,23 @@ type SecretKeyValue struct { } type DSPAStatus struct { + // +kubebuilder:validation:Optional + Components ComponentStatus `json:"components,omitempty"` Conditions []metav1.Condition `json:"conditions,omitempty"` } +type ComponentStatus struct { + // +kubebuilder:validation:Optional + Envoy ComponentDetailStatus `json:"envoy,omitempty"` + APIServer ComponentDetailStatus `json:"apiServer,omitempty"` +} + +type ComponentDetailStatus struct { + // +kubebuilder:validation:Optional + Url string `json:"url,omitempty"` + ExternalUrl string `json:"externalUrl,omitempty"` +} + //+kubebuilder:object:root=true //+kubebuilder:subresource:status //+kubebuilder:resource:shortName=dspa diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 8e531fee6..cfafeae67 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -76,6 +76,38 @@ func (in *CABundle) DeepCopy() *CABundle { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ComponentDetailStatus) DeepCopyInto(out *ComponentDetailStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentDetailStatus. +func (in *ComponentDetailStatus) DeepCopy() *ComponentDetailStatus { + if in == nil { + return nil + } + out := new(ComponentDetailStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ComponentStatus) DeepCopyInto(out *ComponentStatus) { + *out = *in + out.Envoy = in.Envoy + out.APIServer = in.APIServer +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentStatus. +func (in *ComponentStatus) DeepCopy() *ComponentStatus { + if in == nil { + return nil + } + out := new(ComponentStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DSPASpec) DeepCopyInto(out *DSPASpec) { *out = *in @@ -134,6 +166,7 @@ func (in *DSPASpec) DeepCopy() *DSPASpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DSPAStatus) DeepCopyInto(out *DSPAStatus) { *out = *in + out.Components = in.Components if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions *out = make([]v1.Condition, len(*in)) diff --git a/config/crd/bases/datasciencepipelinesapplications.opendatahub.io_datasciencepipelinesapplications.yaml b/config/crd/bases/datasciencepipelinesapplications.opendatahub.io_datasciencepipelinesapplications.yaml index d3c139b89..7d3e0376e 100644 --- a/config/crd/bases/datasciencepipelinesapplications.opendatahub.io_datasciencepipelinesapplications.yaml +++ b/config/crd/bases/datasciencepipelinesapplications.opendatahub.io_datasciencepipelinesapplications.yaml @@ -859,6 +859,23 @@ spec: type: object status: properties: + components: + properties: + apiServer: + properties: + externalUrl: + type: string + url: + type: string + type: object + envoy: + properties: + externalUrl: + type: string + url: + type: string + type: object + type: object conditions: items: description: "Condition contains details for one aspect of the current diff --git a/config/overlays/make-deploy/kustomization.yaml b/config/overlays/make-deploy/kustomization.yaml index f59b8ee7a..5a1488d0d 100644 --- a/config/overlays/make-deploy/kustomization.yaml +++ b/config/overlays/make-deploy/kustomization.yaml @@ -8,5 +8,5 @@ patchesStrategicMerge: - img_patch.yaml images: - name: controller - newName: quay.io/opendatahub/data-science-pipelines-operator - newTag: main + newName: quay.io/rmartine/dspo + newTag: dev diff --git a/config/samples/v2/dspa-all-fields/dspa_all_fields.yaml b/config/samples/v2/dspa-all-fields/dspa_all_fields.yaml index ab66c9474..dc896878b 100644 --- a/config/samples/v2/dspa-all-fields/dspa_all_fields.yaml +++ b/config/samples/v2/dspa-all-fields/dspa_all_fields.yaml @@ -183,6 +183,13 @@ spec: secretKey: somekey # example status fields status: + components: + envoy: + url: http://envoy.svc.cluster.local + externalUrl: https://envoy-dspa.example.com + apiServer: + url: http://envoy.svc.cluster.local + externalUrl: https://envoy-dspa.example.com conditions: - lastTransitionTime: '2024-03-14T22:04:25Z' message: Database connectivity successfully verified @@ -214,6 +221,12 @@ status: reason: MinimumReplicasAvailable status: 'True' type: ScheduledWorkflowReady + - lastTransitionTime: '2024-03-14T22:04:30Z' + message: 'Component [ds-pipeline-metadata-envoy] is minimally available.' + observedGeneration: 3 + reason: MinimumReplicasAvailable + status: 'True' + type: EnvoyReady - lastTransitionTime: '2024-03-14T22:06:37Z' message: All components are ready. observedGeneration: 3 diff --git a/controllers/apiserver.go b/controllers/apiserver.go index 33196d9ed..7c5f38f68 100644 --- a/controllers/apiserver.go +++ b/controllers/apiserver.go @@ -17,6 +17,7 @@ package controllers import ( "context" + "fmt" dspav1alpha1 "github.com/opendatahub-io/data-science-pipelines-operator/api/v1alpha1" v1 "github.com/openshift/api/route/v1" @@ -86,3 +87,47 @@ func (r *DSPAReconciler) ReconcileAPIServer(ctx context.Context, dsp *dspav1alph log.Info("Finished applying APIServer Resources") return nil } + +func (r *DSPAReconciler) GetAPIServerServiceHostname(ctx context.Context, dsp *dspav1alpha1.DataSciencePipelinesApplication) (string, error) { + service := &corev1.Service{} + namespacedNamed := types.NamespacedName{Name: "ds-pipeline-" + dsp.Name, Namespace: dsp.Namespace} + err := r.Get(ctx, namespacedNamed, service) + if err != nil { + return "", err + } + + // Loop over all Service ports, if a secured port is found + // set port and scheme to its secured ones and skip the loop + serviceScheme := "" + servicePort := "" + for i := 0; i < len(service.Spec.Ports); i++ { + servicePort = fmt.Sprintf("%d", service.Spec.Ports[i].Port) + if servicePort == "8443" || servicePort == "443" { + // If a secured port is found, just set scheme to 'https://' and skip the loop + serviceScheme = "https://" + break + } else { + serviceScheme = "http://" + } + } + + return serviceScheme + service.Name + "." + service.Namespace + ".svc.cluster.local:" + servicePort, nil +} + +func (r *DSPAReconciler) GetAPIServerRouteHostname(ctx context.Context, dsp *dspav1alpha1.DataSciencePipelinesApplication) (string, error) { + route := &v1.Route{} + namespacedNamed := types.NamespacedName{Name: "ds-pipeline-" + dsp.Name, Namespace: dsp.Namespace} + err := r.Get(ctx, namespacedNamed, route) + if err != nil { + return "", err + } + + serviceScheme := "" + if route.Spec.TLS != nil { + serviceScheme = "https://" + } else { + serviceScheme = "http://" + } + + return serviceScheme + route.Spec.Host, nil +} diff --git a/controllers/apiserver_test.go b/controllers/apiserver_test.go index 89f24273c..8d11138b3 100644 --- a/controllers/apiserver_test.go +++ b/controllers/apiserver_test.go @@ -18,9 +18,10 @@ limitations under the License. package controllers import ( - "github.com/opendatahub-io/data-science-pipelines-operator/controllers/config" "testing" + "github.com/opendatahub-io/data-science-pipelines-operator/controllers/config" + dspav1alpha1 "github.com/opendatahub-io/data-science-pipelines-operator/api/v1alpha1" "github.com/stretchr/testify/assert" appsv1 "k8s.io/api/apps/v1" @@ -120,3 +121,56 @@ func TestDontDeployAPIServer(t *testing.T) { assert.False(t, created) assert.Nil(t, err) } + +func TestApiServerEndpoints(t *testing.T) { + testNamespace := "testnamespace" + testDSPAName := "testdspa" + expectedAPIServerName := apiServerDefaultResourceNamePrefix + testDSPAName + + // Construct DSPASpec with deployed APIServer + dspa := &dspav1alpha1.DataSciencePipelinesApplication{ + Spec: dspav1alpha1.DSPASpec{ + APIServer: &dspav1alpha1.APIServer{ + Deploy: true, + }, + MLMD: &dspav1alpha1.MLMD{}, + Database: &dspav1alpha1.Database{ + DisableHealthCheck: false, + MariaDB: &dspav1alpha1.MariaDB{ + Deploy: true, + }, + }, + ObjectStorage: &dspav1alpha1.ObjectStorage{ + DisableHealthCheck: false, + Minio: &dspav1alpha1.Minio{ + Deploy: false, + Image: "someimage", + }, + }, + }, + } + + // Enrich DSPA with name+namespace + dspa.Name = testDSPAName + dspa.Namespace = testNamespace + + // Create Context, Fake Controller and Params + ctx, params, reconciler := CreateNewTestObjects() + err := params.ExtractParams(ctx, dspa, reconciler.Client, reconciler.Log) + assert.Nil(t, err) + + // Assert APIServer Deployment doesn't yet exist + deployment := &appsv1.Deployment{} + created, err := reconciler.IsResourceCreated(ctx, deployment, expectedAPIServerName, testNamespace) + assert.False(t, created) + assert.Nil(t, err) + + // Run test reconciliation + err = reconciler.ReconcileAPIServer(ctx, dspa, params) + assert.Nil(t, err) + + dspa_created := &dspav1alpha1.DataSciencePipelinesApplication{} + created, err = reconciler.IsResourceCreated(ctx, dspa, testDSPAName, testNamespace) + assert.NotNil(t, dspa_created.Status.Components.APIServer.Url) + assert.NotNil(t, dspa_created.Status.Components.APIServer.ExternalUrl) +} diff --git a/controllers/config/defaults.go b/controllers/config/defaults.go index a7dbe37d5..65666725b 100644 --- a/controllers/config/defaults.go +++ b/controllers/config/defaults.go @@ -134,6 +134,7 @@ const ( APIServerReady = "APIServerReady" PersistenceAgentReady = "PersistenceAgentReady" ScheduledWorkflowReady = "ScheduledWorkflowReady" + EnvoyReady = "EnvoyReady" CrReady = "Ready" ) diff --git a/controllers/dspastatus/dspa_status.go b/controllers/dspastatus/dspa_status.go index f8e2b0d7f..04cc74b68 100644 --- a/controllers/dspastatus/dspa_status.go +++ b/controllers/dspastatus/dspa_status.go @@ -2,6 +2,7 @@ package dspastatus import ( "fmt" + dspav1alpha1 "github.com/opendatahub-io/data-science-pipelines-operator/api/v1alpha1" "github.com/opendatahub-io/data-science-pipelines-operator/controllers/config" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -20,6 +21,8 @@ type DSPAStatus interface { SetScheduledWorkflowStatus(scheduledWorkflowReady metav1.Condition) + SetEnvoyStatus(envoyReady metav1.Condition) + GetConditions() []metav1.Condition } @@ -29,6 +32,7 @@ func NewDSPAStatus(dspa *dspav1alpha1.DataSciencePipelinesApplication) DSPAStatu apiServerCondition := BuildUnknownCondition(config.APIServerReady) persistenceAgentCondition := BuildUnknownCondition(config.PersistenceAgentReady) scheduledWorkflowReadyCondition := BuildUnknownCondition(config.ScheduledWorkflowReady) + envoyReadyCondition := BuildUnknownCondition(config.EnvoyReady) return &dspaStatus{ dspa: dspa, @@ -37,6 +41,7 @@ func NewDSPAStatus(dspa *dspav1alpha1.DataSciencePipelinesApplication) DSPAStatu apiServerReady: &apiServerCondition, persistenceAgentReady: &persistenceAgentCondition, scheduledWorkflowReady: &scheduledWorkflowReadyCondition, + envoyReady: &envoyReadyCondition, } } @@ -47,6 +52,7 @@ type dspaStatus struct { apiServerReady *metav1.Condition persistenceAgentReady *metav1.Condition scheduledWorkflowReady *metav1.Condition + envoyReady *metav1.Condition } func (s *dspaStatus) SetDatabaseNotReady(err error, reason string) { @@ -90,6 +96,10 @@ func (s *dspaStatus) SetScheduledWorkflowStatus(scheduledWorkflowReady metav1.Co s.scheduledWorkflowReady = &scheduledWorkflowReady } +func (s *dspaStatus) SetEnvoyStatus(envoyReady metav1.Condition) { + s.envoyReady = &envoyReady +} + func (s *dspaStatus) GetConditions() []metav1.Condition { componentConditions := []metav1.Condition{ *s.getDatabaseAvailableCondition(), @@ -97,6 +107,7 @@ func (s *dspaStatus) GetConditions() []metav1.Condition { *s.getApiServerReadyCondition(), *s.getPersistenceAgentReadyCondition(), *s.getScheduledWorkflowReadyCondition(), + *s.getEnvoyReadyCondition(), } allReady := true @@ -134,6 +145,7 @@ func (s *dspaStatus) GetConditions() []metav1.Condition { *s.apiServerReady, *s.persistenceAgentReady, *s.scheduledWorkflowReady, + *s.envoyReady, crReady, } @@ -167,6 +179,10 @@ func (s *dspaStatus) getScheduledWorkflowReadyCondition() *metav1.Condition { return s.scheduledWorkflowReady } +func (s *dspaStatus) getEnvoyReadyCondition() *metav1.Condition { + return s.envoyReady +} + func BuildTrueCondition(conditionType string, message string) metav1.Condition { condition := metav1.Condition{} condition.Type = conditionType diff --git a/controllers/dspipeline_controller.go b/controllers/dspipeline_controller.go index a8b0a1a82..f7e356fd8 100644 --- a/controllers/dspipeline_controller.go +++ b/controllers/dspipeline_controller.go @@ -19,6 +19,7 @@ package controllers import ( "context" "fmt" + "github.com/opendatahub-io/data-science-pipelines-operator/controllers/dspastatus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -304,7 +305,10 @@ func (r *DSPAReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl. err = r.ReconcileMLMD(dspa, params) if err != nil { + r.setStatusAsNotReady(config.EnvoyReady, err, dspaStatus.SetEnvoyStatus) return ctrl.Result{}, err + } else { + r.setStatus(ctx, "ds-pipeline-metadata-envoy-"+dspa.Name, config.EnvoyReady, dspa, dspaStatus.SetEnvoyStatus, log) } err = r.ReconcileWorkflowController(dspa, params) @@ -324,6 +328,7 @@ func (r *DSPAReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl. util.GetConditionByType(config.APIServerReady, conditions): APIServerReadyMetric, util.GetConditionByType(config.PersistenceAgentReady, conditions): PersistenceAgentReadyMetric, util.GetConditionByType(config.ScheduledWorkflowReady, conditions): ScheduledWorkflowReadyMetric, + util.GetConditionByType(config.EnvoyReady, conditions): EnvoyReadyMetric, util.GetConditionByType(config.CrReady, conditions): CrReadyMetric, } r.PublishMetrics(dspa, metricsMap) @@ -355,6 +360,7 @@ func (r *DSPAReconciler) setStatus(ctx context.Context, resourceName string, con func (r *DSPAReconciler) updateStatus(ctx context.Context, dspa *dspav1alpha1.DataSciencePipelinesApplication, dspaStatus dspastatus.DSPAStatus, log logr.Logger, req ctrl.Request) { r.refreshDspa(ctx, dspa, req, log) + dspa.Status.Components = r.GetComponents(ctx, dspa) dspa.Status.Conditions = dspaStatus.GetConditions() err := r.Status().Update(ctx, dspa) if err != nil { @@ -501,6 +507,46 @@ func (r *DSPAReconciler) PublishMetrics(dspa *dspav1alpha1.DataSciencePipelinesA } } +func (r *DSPAReconciler) GetComponents(ctx context.Context, dspa *dspav1alpha1.DataSciencePipelinesApplication) dspav1alpha1.ComponentStatus { + log := r.Log.WithValues("namespace", dspa.Namespace).WithValues("dspa_name", dspa.Name) + log.Info("Updating components endpoints") + + envoyUrl, err := r.GetEnvoyServiceHostname(ctx, dspa) + if err != nil { + log.Info(err.Error()) + } + + envoyExternalUrl, err := r.GetEnvoyRouteHostname(ctx, dspa) + if err != nil { + log.Info(err.Error()) + } + + envoyComponent := &dspav1alpha1.ComponentDetailStatus{ + Url: envoyUrl, + ExternalUrl: envoyExternalUrl, + } + + apiServerUrl, err := r.GetAPIServerServiceHostname(ctx, dspa) + if err != nil { + log.Info(err.Error()) + } + + apiServerExternalUrl, err := r.GetAPIServerRouteHostname(ctx, dspa) + if err != nil { + log.Info(err.Error()) + } + + apiServerComponent := &dspav1alpha1.ComponentDetailStatus{ + Url: apiServerUrl, + ExternalUrl: apiServerExternalUrl, + } + + return dspav1alpha1.ComponentStatus{ + Envoy: *envoyComponent, + APIServer: *apiServerComponent, + } +} + // SetupWithManager sets up the controller with the Manager. func (r *DSPAReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). diff --git a/controllers/metrics.go b/controllers/metrics.go index 22a45e99b..d4b25465e 100644 --- a/controllers/metrics.go +++ b/controllers/metrics.go @@ -73,6 +73,16 @@ var ( "dspa_namespace", }, ) + EnvoyReadyMetric = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "data_science_pipelines_application_envoy_ready", + Help: "Data Science Pipelines Application - Envoy Ready Status", + }, + []string{ + "dspa_name", + "dspa_namespace", + }, + ) CrReadyMetric = prometheus.NewGaugeVec( prometheus.GaugeOpts{ Name: "data_science_pipelines_application_ready", @@ -92,5 +102,6 @@ func InitMetrics() { APIServerReadyMetric, PersistenceAgentReadyMetric, ScheduledWorkflowReadyMetric, + EnvoyReadyMetric, CrReadyMetric) } diff --git a/controllers/mlmd.go b/controllers/mlmd.go index c9f0f3bc6..bc348a8e5 100644 --- a/controllers/mlmd.go +++ b/controllers/mlmd.go @@ -16,7 +16,13 @@ limitations under the License. package controllers import ( + "context" + "fmt" + dspav1alpha1 "github.com/opendatahub-io/data-science-pipelines-operator/api/v1alpha1" + v1 "github.com/openshift/api/route/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" ) const ( @@ -72,3 +78,47 @@ func (r *DSPAReconciler) ReconcileMLMD(dsp *dspav1alpha1.DataSciencePipelinesApp log.Info("Finished applying MLMD Resources") return nil } + +func (r *DSPAReconciler) GetEnvoyServiceHostname(ctx context.Context, dsp *dspav1alpha1.DataSciencePipelinesApplication) (string, error) { + service := &corev1.Service{} + namespacedNamed := types.NamespacedName{Name: "ds-pipeline-md-" + dsp.Name, Namespace: dsp.Namespace} + err := r.Get(ctx, namespacedNamed, service) + if err != nil { + return "", err + } + + // Loop over all Service ports, if a secured port is found + // set port and scheme to its secured ones and skip the loop + serviceScheme := "" + servicePort := "" + for i := 0; i < len(service.Spec.Ports); i++ { + servicePort = fmt.Sprintf("%d", service.Spec.Ports[i].Port) + if servicePort == "8443" || servicePort == "443" { + // If a secured port is found, just set scheme to 'https://' and skip the loop + serviceScheme = "https://" + break + } else { + serviceScheme = "http://" + } + } + + return serviceScheme + service.Name + "." + service.Namespace + ".svc.cluster.local:" + servicePort, nil +} + +func (r *DSPAReconciler) GetEnvoyRouteHostname(ctx context.Context, dsp *dspav1alpha1.DataSciencePipelinesApplication) (string, error) { + route := &v1.Route{} + namespacedNamed := types.NamespacedName{Name: "ds-pipeline-md-" + dsp.Name, Namespace: dsp.Namespace} + err := r.Get(ctx, namespacedNamed, route) + if err != nil { + return "", err + } + + serviceScheme := "" + if route.Spec.TLS != nil { + serviceScheme = "https://" + } else { + serviceScheme = "http://" + } + + return serviceScheme + route.Spec.Host, nil +} diff --git a/controllers/mlmd_test.go b/controllers/mlmd_test.go index 3f416af9d..339ba621b 100644 --- a/controllers/mlmd_test.go +++ b/controllers/mlmd_test.go @@ -18,9 +18,10 @@ limitations under the License. package controllers import ( - v1 "github.com/openshift/api/route/v1" "testing" + v1 "github.com/openshift/api/route/v1" + dspav1alpha1 "github.com/opendatahub-io/data-science-pipelines-operator/api/v1alpha1" "github.com/stretchr/testify/assert" appsv1 "k8s.io/api/apps/v1" @@ -811,3 +812,76 @@ func TestDontDeployEnvoyRouteV2(t *testing.T) { assert.False(t, created) assert.Nil(t, err) } + +func TestGetEndpointsMLMDV2(t *testing.T) { + testNamespace := "testnamespace" + testDSPAName := "testdspa" + expectedMLMDEnvoyName := "ds-pipeline-metadata-envoy-testdspa" + expectedMLMDEnvoyRouteName := "ds-pipeline-md-testdspa" + + // Construct DSPA Spec with MLMD Enabled + dspa := &dspav1alpha1.DataSciencePipelinesApplication{ + Spec: dspav1alpha1.DSPASpec{ + DSPVersion: "v2", + APIServer: &dspav1alpha1.APIServer{}, + MLMD: &dspav1alpha1.MLMD{ + Deploy: true, + }, + Database: &dspav1alpha1.Database{ + DisableHealthCheck: false, + MariaDB: &dspav1alpha1.MariaDB{ + Deploy: true, + }, + }, + ObjectStorage: &dspav1alpha1.ObjectStorage{ + DisableHealthCheck: false, + Minio: &dspav1alpha1.Minio{ + Deploy: false, + Image: "someimage", + }, + }, + }, + } + + // Enrich DSPA with name+namespace + dspa.Namespace = testNamespace + dspa.Name = testDSPAName + + // Create Context, Fake Controller and Params + ctx, params, reconciler := CreateNewTestObjects() + err := params.ExtractParams(ctx, dspa, reconciler.Client, reconciler.Log) + assert.Nil(t, err) + + // Ensure MLMD-Envoy resources doesn't yet exist + deployment := &appsv1.Deployment{} + created, err := reconciler.IsResourceCreated(ctx, deployment, expectedMLMDEnvoyName, testNamespace) + assert.False(t, created) + assert.Nil(t, err) + + // Ensure MLMD-Envoy route doesn't yet exist + route := &v1.Route{} + created, err = reconciler.IsResourceCreated(ctx, route, expectedMLMDEnvoyRouteName, testNamespace) + assert.False(t, created) + assert.Nil(t, err) + + // Run test reconciliation + err = reconciler.ReconcileMLMD(dspa, params) + assert.Nil(t, err) + + // Ensure MLMD-Envoy resources now exists + deployment = &appsv1.Deployment{} + created, err = reconciler.IsResourceCreated(ctx, deployment, expectedMLMDEnvoyName, testNamespace) + assert.True(t, created) + assert.Nil(t, err) + + // Ensure MLMD-Envoy route now exists + route = &v1.Route{} + created, err = reconciler.IsResourceCreated(ctx, route, expectedMLMDEnvoyRouteName, testNamespace) + assert.True(t, created) + assert.Nil(t, err) + + dspa_created := &dspav1alpha1.DataSciencePipelinesApplication{} + created, err = reconciler.IsResourceCreated(ctx, dspa, testDSPAName, testNamespace) + assert.NotNil(t, dspa_created.Status.Components.Envoy.Url) + assert.NotNil(t, dspa_created.Status.Components.Envoy.ExternalUrl) +}