diff --git a/controllers/constants/constants.go b/controllers/constants/constants.go index 321a18c7..dee82832 100644 --- a/controllers/constants/constants.go +++ b/controllers/constants/constants.go @@ -15,6 +15,8 @@ limitations under the License. package constants +type IsvcDeploymentMode string + const ( InferenceServiceKind = "InferenceService" @@ -24,13 +26,26 @@ const ( IstioIngressServiceHTTPPortName = "http2" IstioIngressServiceHTTPSPortName = "https" IstioSidecarInjectAnnotationName = "sidecar.istio.io/inject" + KserveNetworkVisibility = "networking.kserve.io/visibility" + KserveGroupAnnotation = "serving.kserve.io/inferenceservice" - LabelAuthGroup = "security.opendatahub.io/authorization-group" - LabelEnableAuthODH = "security.opendatahub.io/enable-auth" - LabelEnableAuth = "enable-auth" - LabelEnableRoute = "enable-route" + LabelAuthGroup = "security.opendatahub.io/authorization-group" + LabelEnableAuthODH = "security.opendatahub.io/enable-auth" + LabelEnableAuth = "enable-auth" + LabelEnableRoute = "enable-route" + LabelEnableKserveRawRoute = "exposed" CapabilityServiceMeshAuthorization = "CapabilityServiceMeshAuthorization" + + ModelMeshServiceAccountName = "modelmesh-serving-sa" + KserveServiceAccountName = "default" +) + +// isvc modes +var ( + Serverless IsvcDeploymentMode = "Serverless" + RawDeployment IsvcDeploymentMode = "RawDeployment" + ModelMesh IsvcDeploymentMode = "ModelMesh" ) // model registry diff --git a/controllers/inferenceservice_controller.go b/controllers/inferenceservice_controller.go index 600c940c..8c90dadb 100644 --- a/controllers/inferenceservice_controller.go +++ b/controllers/inferenceservice_controller.go @@ -22,6 +22,7 @@ import ( kservev1alpha1 "github.com/kserve/kserve/pkg/apis/serving/v1alpha1" kservev1beta1 "github.com/kserve/kserve/pkg/apis/serving/v1beta1" authorinov1beta2 "github.com/kuadrant/authorino/api/v1beta2" + "github.com/opendatahub-io/odh-model-controller/controllers/constants" "github.com/opendatahub-io/odh-model-controller/controllers/reconcilers" "github.com/opendatahub-io/odh-model-controller/controllers/utils" routev1 "github.com/openshift/api/route/v1" @@ -96,13 +97,13 @@ func (r *OpenshiftInferenceServiceReconciler) Reconcile(ctx context.Context, req return ctrl.Result{}, err } switch IsvcDeploymentMode { - case utils.ModelMesh: + case constants.ModelMesh: log.Info("Reconciling InferenceService for ModelMesh") err = r.mmISVCReconciler.Reconcile(ctx, log, isvc) - case utils.Serverless: + case constants.Serverless: log.Info("Reconciling InferenceService for Kserve in mode Serverless") err = r.kserveServerlessISVCReconciler.Reconcile(ctx, log, isvc) - case utils.RawDeployment: + case constants.RawDeployment: log.Info("Reconciling InferenceService for Kserve in mode RawDeployment") err = r.kserveRawISVCReconciler.Reconcile(ctx, log, isvc) } @@ -203,10 +204,14 @@ func (r *OpenshiftInferenceServiceReconciler) onDeletion(ctx context.Context, lo if err != nil { log.V(1).Error(err, "Could not determine deployment mode for ISVC. Some resources related to the inferenceservice might not be deleted.") } - if IsvcDeploymentMode == utils.Serverless { + if IsvcDeploymentMode == constants.Serverless { log.V(1).Info("Deleting kserve inference resource (Serverless Mode)") return r.kserveServerlessISVCReconciler.OnDeletionOfKserveInferenceService(ctx, log, inferenceService) } + if IsvcDeploymentMode == constants.RawDeployment { + log.V(1).Info("Deleting kserve inference resource (RawDeployment Mode)") + return r.kserveRawISVCReconciler.OnDeletionOfKserveInferenceService(ctx, log, inferenceService) + } return nil } @@ -217,5 +222,8 @@ func (r *OpenshiftInferenceServiceReconciler) DeleteResourcesIfNoIsvcExists(ctx if err := r.mmISVCReconciler.DeleteModelMeshResourcesIfNoMMIsvcExists(ctx, log, namespace); err != nil { return err } + if err := r.kserveRawISVCReconciler.CleanupNamespaceIfNoKserveIsvcExists(ctx, log, namespace); err != nil { + return err + } return nil } diff --git a/controllers/kserve_raw_inferenceservice_controller_test.go b/controllers/kserve_raw_inferenceservice_controller_test.go new file mode 100644 index 00000000..e8d7cf9e --- /dev/null +++ b/controllers/kserve_raw_inferenceservice_controller_test.go @@ -0,0 +1,189 @@ +package controllers + +import ( + "errors" + + kservev1alpha1 "github.com/kserve/kserve/pkg/apis/serving/v1alpha1" + kservev1beta1 "github.com/kserve/kserve/pkg/apis/serving/v1beta1" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/opendatahub-io/odh-model-controller/controllers/constants" + routev1 "github.com/openshift/api/route/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + apierrs "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" +) + +var _ = Describe("The KServe Raw reconciler", func() { + var testNs string + createServingRuntime := func(namespace, path string) *kservev1alpha1.ServingRuntime { + servingRuntime := &kservev1alpha1.ServingRuntime{} + err := convertToStructuredResource(path, servingRuntime) + Expect(err).NotTo(HaveOccurred()) + servingRuntime.SetNamespace(namespace) + if err := cli.Create(ctx, servingRuntime); err != nil && !apierrs.IsAlreadyExists(err) { + Expect(err).NotTo(HaveOccurred()) + } + return servingRuntime + } + + createInferenceService := func(namespace, name string, path string) *kservev1beta1.InferenceService { + inferenceService := &kservev1beta1.InferenceService{} + err := convertToStructuredResource(path, inferenceService) + Expect(err).NotTo(HaveOccurred()) + inferenceService.SetNamespace(namespace) + if len(name) != 0 { + inferenceService.Name = name + } + inferenceService.Annotations = map[string]string{} + inferenceService.Annotations["serving.kserve.io/deploymentMode"] = "RawDeployment" + return inferenceService + } + + BeforeEach(func() { + testNs = Namespaces.Create(cli).Name + + inferenceServiceConfig := &corev1.ConfigMap{} + Expect(convertToStructuredResource(InferenceServiceConfigPath1, inferenceServiceConfig)).To(Succeed()) + if err := cli.Create(ctx, inferenceServiceConfig); err != nil && !apierrs.IsAlreadyExists(err) { + Fail(err.Error()) + } + + }) + + When("deploying a Kserve RawDeployment model", func() { + It("it should create a default clusterrolebinding for auth", func() { + _ = createServingRuntime(testNs, KserveServingRuntimePath1) + inferenceService := createInferenceService(testNs, KserveOvmsInferenceServiceName, KserveInferenceServicePath1) + if err := cli.Create(ctx, inferenceService); err != nil && !apierrs.IsAlreadyExists(err) { + Expect(err).NotTo(HaveOccurred()) + } + + crb := &rbacv1.ClusterRoleBinding{} + Eventually(func() error { + key := types.NamespacedName{Name: inferenceService.Namespace + "-" + constants.KserveServiceAccountName + "-auth-delegator", + Namespace: inferenceService.Namespace} + return cli.Get(ctx, key, crb) + }, timeout, interval).ShouldNot(HaveOccurred()) + + route := &routev1.Route{} + Eventually(func() error { + key := types.NamespacedName{Name: inferenceService.Name, Namespace: inferenceService.Namespace} + return cli.Get(ctx, key, route) + }, timeout, interval).Should(HaveOccurred()) + }) + It("it should create a custom rolebinding if isvc has a SA defined", func() { + serviceAccountName := "custom-sa" + _ = createServingRuntime(testNs, KserveServingRuntimePath1) + inferenceService := createInferenceService(testNs, KserveOvmsInferenceServiceName, KserveInferenceServicePath1) + inferenceService.Spec.Predictor.ServiceAccountName = serviceAccountName + if err := cli.Create(ctx, inferenceService); err != nil && !apierrs.IsAlreadyExists(err) { + Expect(err).NotTo(HaveOccurred()) + } + + crb := &rbacv1.ClusterRoleBinding{} + Eventually(func() error { + key := types.NamespacedName{Name: inferenceService.Namespace + "-" + serviceAccountName + "-auth-delegator", + Namespace: inferenceService.Namespace} + return cli.Get(ctx, key, crb) + }, timeout, interval).ShouldNot(HaveOccurred()) + }) + It("it should create a route if isvc has the label to expose route", func() { + inferenceService := createInferenceService(testNs, KserveOvmsInferenceServiceName, KserveInferenceServicePath1) + inferenceService.Labels = map[string]string{} + inferenceService.Labels[constants.KserveNetworkVisibility] = constants.LabelEnableKserveRawRoute + // The service is manually created before the isvc otherwise the unit test risks running into a race condition + // where the reconcile loop finishes before the service is created, leading to no route being created. + isvcService := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: KserveOvmsInferenceServiceName + "-predictor", + Namespace: inferenceService.Namespace, + Annotations: map[string]string{ + "openshift.io/display-name": KserveOvmsInferenceServiceName, + "serving.kserve.io/deploymentMode": "RawDeployment", + }, + Labels: map[string]string{ + "app": "isvc." + KserveOvmsInferenceServiceName + "-predictor", + "component": "predictor", + "serving.kserve.io/inferenceservice": KserveOvmsInferenceServiceName, + }, + }, + Spec: corev1.ServiceSpec{ + ClusterIP: "None", + IPFamilies: []corev1.IPFamily{"IPv4"}, + Ports: []corev1.ServicePort{ + { + Name: "https", + Protocol: corev1.ProtocolTCP, + Port: 8888, + TargetPort: intstr.FromString("https"), + }, + }, + ClusterIPs: []string{"None"}, + Selector: map[string]string{ + "app": "isvc." + KserveOvmsInferenceServiceName + "-predictor", + }, + }, + } + if err := cli.Create(ctx, isvcService); err != nil && !apierrs.IsAlreadyExists(err) { + Expect(err).NotTo(HaveOccurred()) + } + service := &corev1.Service{} + Eventually(func() error { + key := types.NamespacedName{Name: isvcService.Name, Namespace: isvcService.Namespace} + return cli.Get(ctx, key, service) + }, timeout, interval).Should(Succeed()) + + _ = createServingRuntime(testNs, KserveServingRuntimePath1) + if err := cli.Create(ctx, inferenceService); err != nil && !apierrs.IsAlreadyExists(err) { + Expect(err).NotTo(HaveOccurred()) + } + + route := &routev1.Route{} + Eventually(func() error { + key := types.NamespacedName{Name: inferenceService.Name, Namespace: inferenceService.Namespace} + return cli.Get(ctx, key, route) + }, timeout, interval).ShouldNot(HaveOccurred()) + }) + }) + When("deleting a Kserve RawDeployment model", func() { + It("the associated route should be deleted", func() { + _ = createServingRuntime(testNs, KserveServingRuntimePath1) + inferenceService := createInferenceService(testNs, KserveOvmsInferenceServiceName, KserveInferenceServicePath1) + if err := cli.Create(ctx, inferenceService); err != nil && !apierrs.IsAlreadyExists(err) { + Expect(err).NotTo(HaveOccurred()) + } + + Expect(cli.Delete(ctx, inferenceService)).Should(Succeed()) + + route := &routev1.Route{} + Eventually(func() error { + key := types.NamespacedName{Name: inferenceService.Name, Namespace: inferenceService.Namespace} + return cli.Get(ctx, key, route) + }, timeout, interval).Should(HaveOccurred()) + }) + }) + When("namespace no longer has any RawDeployment models", func() { + It("should delete the default clusterrolebinding", func() { + _ = createServingRuntime(testNs, KserveServingRuntimePath1) + inferenceService := createInferenceService(testNs, KserveOvmsInferenceServiceName, KserveInferenceServicePath1) + if err := cli.Create(ctx, inferenceService); err != nil && !apierrs.IsAlreadyExists(err) { + Expect(err).NotTo(HaveOccurred()) + } + Expect(cli.Delete(ctx, inferenceService)).Should(Succeed()) + crb := &rbacv1.ClusterRoleBinding{} + Eventually(func() error { + namespacedNamed := types.NamespacedName{Name: testNs + "-" + constants.KserveServiceAccountName + "-auth-delegator", Namespace: WorkingNamespace} + err := cli.Get(ctx, namespacedNamed, crb) + if apierrs.IsNotFound(err) { + return nil + } else { + return errors.New("crb deletion not detected") + } + }, timeout, interval).ShouldNot(HaveOccurred()) + }) + }) +}) diff --git a/controllers/reconcilers/kserve_raw_clusterrolebinding_reconciler.go b/controllers/reconcilers/kserve_raw_clusterrolebinding_reconciler.go new file mode 100644 index 00000000..38f45626 --- /dev/null +++ b/controllers/reconcilers/kserve_raw_clusterrolebinding_reconciler.go @@ -0,0 +1,126 @@ +/* + +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. +*/ + +package reconcilers + +import ( + "context" + + "github.com/go-logr/logr" + kservev1beta1 "github.com/kserve/kserve/pkg/apis/serving/v1beta1" + "github.com/opendatahub-io/odh-model-controller/controllers/constants" + "github.com/opendatahub-io/odh-model-controller/controllers/processors" + "github.com/opendatahub-io/odh-model-controller/controllers/resources" + v1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var _ SubResourceReconciler = (*KserveRawClusterRoleBindingReconciler)(nil) + +type KserveRawClusterRoleBindingReconciler struct { + client client.Client + clusterRoleBindingHandler resources.ClusterRoleBindingHandler + deltaProcessor processors.DeltaProcessor + serviceAccountName string +} + +func NewKserveRawClusterRoleBindingReconciler(client client.Client) *KserveRawClusterRoleBindingReconciler { + return &KserveRawClusterRoleBindingReconciler{ + client: client, + clusterRoleBindingHandler: resources.NewClusterRoleBindingHandler(client), + deltaProcessor: processors.NewDeltaProcessor(), + serviceAccountName: constants.KserveServiceAccountName, + } +} + +func (r *KserveRawClusterRoleBindingReconciler) Reconcile(ctx context.Context, log logr.Logger, isvc *kservev1beta1.InferenceService) error { + log.V(1).Info("Reconciling ClusterRoleBinding for InferenceService") + // Create Desired resource + desiredResource, err := r.createDesiredResource(isvc) + if err != nil { + return err + } + + // Get Existing resource + existingResource, err := r.getExistingResource(ctx, log, isvc) + if err != nil { + return err + } + + // Process Delta + if err = r.processDelta(ctx, log, desiredResource, existingResource); err != nil { + return err + } + return nil +} + +func (r *KserveRawClusterRoleBindingReconciler) Cleanup(ctx context.Context, log logr.Logger, isvcNs string) error { + log.V(1).Info("Deleting ClusterRoleBinding object for target namespace") + crbName := r.clusterRoleBindingHandler.GetClusterRoleBindingName(isvcNs, r.serviceAccountName) + return r.clusterRoleBindingHandler.DeleteClusterRoleBinding(ctx, types.NamespacedName{Name: crbName, Namespace: isvcNs}) +} + +func (r *KserveRawClusterRoleBindingReconciler) Delete(ctx context.Context, log logr.Logger, isvc *kservev1beta1.InferenceService) error { + log.V(1).Info("Custom CRB Deletion logic triggered") + if isvc.Spec.Predictor.ServiceAccountName == "" { + // isvc has default SA, no op + return nil + } + serviceAccount := isvc.Spec.Predictor.ServiceAccountName + // Get isvc list in namespace and filter for isvcs with the same SA + inferenceServiceList := &kservev1beta1.InferenceServiceList{} + if err := r.client.List(ctx, inferenceServiceList, client.InNamespace(isvc.Namespace)); err != nil { + log.V(1).Info("Error getting InferenceServiceList for CRB cleanup") + return err + } + for i := len(inferenceServiceList.Items) - 1; i >= 0; i-- { + inferenceService := inferenceServiceList.Items[i] + if inferenceService.Spec.Predictor.ServiceAccountName != serviceAccount { + inferenceServiceList.Items = append(inferenceServiceList.Items[:i], inferenceServiceList.Items[i+1:]...) + } + } + log.V(1).Info("len of isvclist", "len", len(inferenceServiceList.Items)) + // If there are no isvcs left with this SA, then the current isvc was the last one, so it is deleted + if len(inferenceServiceList.Items) == 0 { + crbName := r.clusterRoleBindingHandler.GetClusterRoleBindingName(isvc.Namespace, serviceAccount) + return r.clusterRoleBindingHandler.DeleteClusterRoleBinding(ctx, types.NamespacedName{Name: crbName, Namespace: isvc.Namespace}) + } + // There are other isvcs with the same SA still present in the namespace, so do not delete yet + return nil +} + +func (r *KserveRawClusterRoleBindingReconciler) createDesiredResource(isvc *kservev1beta1.InferenceService) (*v1.ClusterRoleBinding, error) { + isvcSA := r.serviceAccountName + if isvc.Spec.Predictor.ServiceAccountName != "" { + isvcSA = isvc.Spec.Predictor.ServiceAccountName + } + desiredClusterRoleBindingName := r.clusterRoleBindingHandler.GetClusterRoleBindingName(isvc.Namespace, isvcSA) + desiredClusterRoleBinding := r.clusterRoleBindingHandler.CreateDesiredClusterRoleBinding(desiredClusterRoleBindingName, isvcSA, isvc.Namespace) + return desiredClusterRoleBinding, nil +} + +func (r *KserveRawClusterRoleBindingReconciler) getExistingResource(ctx context.Context, log logr.Logger, isvc *kservev1beta1.InferenceService) (*v1.ClusterRoleBinding, error) { + isvcSA := r.serviceAccountName + if isvc.Spec.Predictor.ServiceAccountName != r.serviceAccountName { + isvcSA = isvc.Spec.Predictor.ServiceAccountName + } + crbName := r.clusterRoleBindingHandler.GetClusterRoleBindingName(isvc.Namespace, isvcSA) + return r.clusterRoleBindingHandler.FetchClusterRoleBinding(ctx, log, types.NamespacedName{Name: crbName, Namespace: isvc.Namespace}) +} + +func (r *KserveRawClusterRoleBindingReconciler) processDelta(ctx context.Context, log logr.Logger, desiredCRB *v1.ClusterRoleBinding, existingCRB *v1.ClusterRoleBinding) (err error) { + return r.clusterRoleBindingHandler.ProcessDelta(ctx, log, desiredCRB, existingCRB, r.deltaProcessor) +} diff --git a/controllers/reconcilers/kserve_raw_inferenceservice_reconciler.go b/controllers/reconcilers/kserve_raw_inferenceservice_reconciler.go index 33857c81..2ac70587 100644 --- a/controllers/reconcilers/kserve_raw_inferenceservice_reconciler.go +++ b/controllers/reconcilers/kserve_raw_inferenceservice_reconciler.go @@ -18,6 +18,10 @@ package reconcilers import ( "context" + "github.com/hashicorp/go-multierror" + "github.com/opendatahub-io/odh-model-controller/controllers/constants" + "github.com/opendatahub-io/odh-model-controller/controllers/utils" + "github.com/go-logr/logr" kservev1beta1 "github.com/kserve/kserve/pkg/apis/serving/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -26,16 +30,65 @@ import ( var _ Reconciler = (*KserveRawInferenceServiceReconciler)(nil) type KserveRawInferenceServiceReconciler struct { - client client.Client + client client.Client + subResourceReconcilers []SubResourceReconciler } func NewKServeRawInferenceServiceReconciler(client client.Client) *KserveRawInferenceServiceReconciler { + + subResourceReconciler := []SubResourceReconciler{ + NewKserveRawClusterRoleBindingReconciler(client), + NewKserveRawRouteReconciler(client), + } + return &KserveRawInferenceServiceReconciler{ - client: client, + client: client, + subResourceReconcilers: subResourceReconciler, + } +} + +func (r *KserveRawInferenceServiceReconciler) Reconcile(ctx context.Context, log logr.Logger, isvc *kservev1beta1.InferenceService) error { + var reconcileErrors *multierror.Error + for _, reconciler := range r.subResourceReconcilers { + reconcileErrors = multierror.Append(reconcileErrors, reconciler.Reconcile(ctx, log, isvc)) + } + + return reconcileErrors.ErrorOrNil() +} + +func (r *KserveRawInferenceServiceReconciler) OnDeletionOfKserveInferenceService(ctx context.Context, log logr.Logger, isvc *kservev1beta1.InferenceService) error { + var deleteErrors *multierror.Error + for _, reconciler := range r.subResourceReconcilers { + deleteErrors = multierror.Append(deleteErrors, reconciler.Delete(ctx, log, isvc)) } + + return deleteErrors.ErrorOrNil() } -func (r *KserveRawInferenceServiceReconciler) Reconcile(_ context.Context, log logr.Logger, _ *kservev1beta1.InferenceService) error { - log.V(1).Info("No Reconciliation to be done for inferenceservice as it is using RawDeployment mode") - return nil +func (r *KserveRawInferenceServiceReconciler) CleanupNamespaceIfNoKserveIsvcExists(ctx context.Context, log logr.Logger, namespace string) error { + inferenceServiceList := &kservev1beta1.InferenceServiceList{} + if err := r.client.List(ctx, inferenceServiceList, client.InNamespace(namespace)); err != nil { + return err + } + + for i := len(inferenceServiceList.Items) - 1; i >= 0; i-- { + inferenceService := inferenceServiceList.Items[i] + isvcDeploymentMode, err := utils.GetDeploymentModeForIsvc(ctx, r.client, &inferenceService) + if err != nil { + return err + } + if isvcDeploymentMode != constants.RawDeployment { + inferenceServiceList.Items = append(inferenceServiceList.Items[:i], inferenceServiceList.Items[i+1:]...) + } + } + + // If there are no Kserve Raw InferenceServices in the namespace, delete namespace-scoped resources needed for Kserve Raw + var cleanupErrors *multierror.Error + if len(inferenceServiceList.Items) == 0 { + for _, reconciler := range r.subResourceReconcilers { + cleanupErrors = multierror.Append(cleanupErrors, reconciler.Cleanup(ctx, log, namespace)) + } + } + + return cleanupErrors.ErrorOrNil() } diff --git a/controllers/reconcilers/kserve_raw_route_reconciler.go b/controllers/reconcilers/kserve_raw_route_reconciler.go new file mode 100644 index 00000000..b7284c60 --- /dev/null +++ b/controllers/reconcilers/kserve_raw_route_reconciler.go @@ -0,0 +1,215 @@ +/* + +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. +*/ + +package reconcilers + +import ( + "context" + "strconv" + + "github.com/go-logr/logr" + kservev1beta1 "github.com/kserve/kserve/pkg/apis/serving/v1beta1" + "github.com/opendatahub-io/odh-model-controller/controllers/comparators" + "github.com/opendatahub-io/odh-model-controller/controllers/constants" + "github.com/opendatahub-io/odh-model-controller/controllers/processors" + "github.com/opendatahub-io/odh-model-controller/controllers/resources" + v1 "github.com/openshift/api/route/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/utils/pointer" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var _ SubResourceReconciler = (*KserveRawRouteReconciler)(nil) + +type KserveRawRouteReconciler struct { + client client.Client + routeHandler resources.RouteHandler + deltaProcessor processors.DeltaProcessor +} + +func NewKserveRawRouteReconciler(client client.Client) *KserveRawRouteReconciler { + return &KserveRawRouteReconciler{ + client: client, + routeHandler: resources.NewRouteHandler(client), + deltaProcessor: processors.NewDeltaProcessor(), + } +} + +func (r *KserveRawRouteReconciler) Reconcile(ctx context.Context, log logr.Logger, isvc *kservev1beta1.InferenceService) error { + log.V(1).Info("Reconciling Generic Route for Kserve Raw InferenceService") + + // Create Desired resource + desiredResource, err := r.createDesiredResource(ctx, log, isvc) + if err != nil { + return err + } + + // Get Existing resource + existingResource, err := r.getExistingResource(ctx, log, isvc) + if err != nil { + return err + } + + // Process Delta + if err = r.processDelta(ctx, log, desiredResource, existingResource); err != nil { + return err + } + return nil +} + +func (r *KserveRawRouteReconciler) Delete(ctx context.Context, log logr.Logger, isvc *kservev1beta1.InferenceService) error { + // No OP as route will be deleted by the ownerref + return nil +} + +func (r *KserveRawRouteReconciler) Cleanup(_ context.Context, _ logr.Logger, _ string) error { + // NOOP - resources are deleted together with ISVCs + return nil +} + +func (r *KserveRawRouteReconciler) createDesiredResource(ctx context.Context, log logr.Logger, isvc *kservev1beta1.InferenceService) (*v1.Route, error) { + var err error + enableAuth := false + if enableAuth, err = strconv.ParseBool(isvc.Labels[constants.LabelEnableAuthODH]); err != nil { + enableAuth = false + } + createRoute := false + if val, ok := isvc.Labels[constants.KserveNetworkVisibility]; ok && val == constants.LabelEnableKserveRawRoute { + createRoute = true + } + if !createRoute { + log.Info("InferenceService does not have label '" + constants.KserveNetworkVisibility + "' annotation" + + " set to '" + constants.LabelEnableKserveRawRoute + "'. Skipping route creation") + return nil, nil + } + + // Fetch the service with the label "serving.kserve.io/inferenceservice=isvc.Name" in the isvc namespace + serviceList := &corev1.ServiceList{} + labelSelector := client.MatchingLabels{constants.KserveGroupAnnotation: isvc.Name} + err = r.client.List(ctx, serviceList, client.InNamespace(isvc.Namespace), labelSelector) + if err != nil || len(serviceList.Items) == 0 { + log.Error(err, "Failed to fetch service for InferenceService", "InferenceService", isvc.Name) + return nil, err + } + var servicePort int32 + var targetService corev1.Service + for _, service := range serviceList.Items { + if val, ok := service.Labels["component"]; ok { + if val == "transformer" { + targetService = service + break + } + if val == "predictor" { + targetService = service + } + } + } + + for _, port := range targetService.Spec.Ports { + if !enableAuth { + if port.Name == "http" || port.Name == targetService.Name { + servicePort = port.Port + } + } else { + if port.Name == "https" { + servicePort = port.Port + } + } + } + + desiredRoute := &v1.Route{ + ObjectMeta: metav1.ObjectMeta{ + Name: isvc.Name, + Namespace: isvc.Namespace, + Labels: map[string]string{ + "inferenceservice-name": isvc.Name, + }, + }, + Spec: v1.RouteSpec{ + To: v1.RouteTargetReference{ + Kind: "Service", + Name: targetService.Name, + Weight: pointer.Int32(100), + }, + Port: &v1.RoutePort{ + TargetPort: intstr.FromInt32(servicePort), + }, + WildcardPolicy: v1.WildcardPolicyNone, + }, + Status: v1.RouteStatus{ + Ingress: []v1.RouteIngress{}, + }, + } + + if enableAuth { + desiredRoute.Spec.TLS = &v1.TLSConfig{ + Termination: v1.TLSTerminationReencrypt, + InsecureEdgeTerminationPolicy: v1.InsecureEdgeTerminationPolicyRedirect, + } + } else { + desiredRoute.Spec.TLS = &v1.TLSConfig{ + Termination: v1.TLSTerminationEdge, + InsecureEdgeTerminationPolicy: v1.InsecureEdgeTerminationPolicyRedirect, + } + } + if err = ctrl.SetControllerReference(isvc, desiredRoute, r.client.Scheme()); err != nil { + return nil, err + } + + return desiredRoute, nil +} + +func (r *KserveRawRouteReconciler) getExistingResource(ctx context.Context, log logr.Logger, isvc *kservev1beta1.InferenceService) (*v1.Route, error) { + return r.routeHandler.FetchRoute(ctx, log, types.NamespacedName{Name: isvc.Name, Namespace: isvc.Namespace}) +} + +func (r *KserveRawRouteReconciler) processDelta(ctx context.Context, log logr.Logger, desiredRoute *v1.Route, existingRoute *v1.Route) (err error) { + comparator := comparators.GetKServeRouteComparator() + delta := r.deltaProcessor.ComputeDelta(comparator, desiredRoute, existingRoute) + + if !delta.HasChanges() { + log.V(1).Info("No delta found") + return nil + } + + if delta.IsAdded() { + log.V(1).Info("Delta found", "create", desiredRoute.GetName()) + if err = r.client.Create(ctx, desiredRoute); err != nil { + return + } + } + if delta.IsUpdated() { + log.V(1).Info("Delta found", "update", existingRoute.GetName()) + rp := existingRoute.DeepCopy() + rp.Labels = desiredRoute.Labels + rp.Annotations = desiredRoute.Annotations + rp.Spec = desiredRoute.Spec + + if err = r.client.Update(ctx, rp); err != nil { + return + } + } + if delta.IsRemoved() { + log.V(1).Info("Delta found", "delete", existingRoute.GetName()) + if err = r.client.Delete(ctx, existingRoute); err != nil { + return + } + } + return nil +} diff --git a/controllers/reconcilers/kserve_serverless_inferenceservice_reconciler.go b/controllers/reconcilers/kserve_serverless_inferenceservice_reconciler.go index 00365f0d..956592cf 100644 --- a/controllers/reconcilers/kserve_serverless_inferenceservice_reconciler.go +++ b/controllers/reconcilers/kserve_serverless_inferenceservice_reconciler.go @@ -18,10 +18,10 @@ package reconcilers import ( "context" - "github.com/hashicorp/go-multierror" - "github.com/go-logr/logr" + "github.com/hashicorp/go-multierror" kservev1beta1 "github.com/kserve/kserve/pkg/apis/serving/v1beta1" + constants "github.com/opendatahub-io/odh-model-controller/controllers/constants" "github.com/opendatahub-io/odh-model-controller/controllers/utils" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -88,7 +88,7 @@ func (r *KserveServerlessInferenceServiceReconciler) CleanupNamespaceIfNoKserveI if err != nil { return err } - if isvcDeploymentMode != utils.Serverless { + if isvcDeploymentMode != constants.Serverless { inferenceServiceList.Items = append(inferenceServiceList.Items[:i], inferenceServiceList.Items[i+1:]...) } } diff --git a/controllers/reconcilers/mm_clusterrolebinding_reconciler.go b/controllers/reconcilers/mm_clusterrolebinding_reconciler.go index 81b25c93..922f981d 100644 --- a/controllers/reconcilers/mm_clusterrolebinding_reconciler.go +++ b/controllers/reconcilers/mm_clusterrolebinding_reconciler.go @@ -17,13 +17,13 @@ package reconcilers import ( "context" + "github.com/go-logr/logr" kservev1beta1 "github.com/kserve/kserve/pkg/apis/serving/v1beta1" - "github.com/opendatahub-io/odh-model-controller/controllers/comparators" + "github.com/opendatahub-io/odh-model-controller/controllers/constants" "github.com/opendatahub-io/odh-model-controller/controllers/processors" "github.com/opendatahub-io/odh-model-controller/controllers/resources" v1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -35,6 +35,7 @@ type ModelMeshClusterRoleBindingReconciler struct { client client.Client clusterRoleBindingHandler resources.ClusterRoleBindingHandler deltaProcessor processors.DeltaProcessor + serviceAccountName string } func NewModelMeshClusterRoleBindingReconciler(client client.Client) *ModelMeshClusterRoleBindingReconciler { @@ -42,6 +43,7 @@ func NewModelMeshClusterRoleBindingReconciler(client client.Client) *ModelMeshCl client: client, clusterRoleBindingHandler: resources.NewClusterRoleBindingHandler(client), deltaProcessor: processors.NewDeltaProcessor(), + serviceAccountName: constants.ModelMeshServiceAccountName, } } @@ -68,69 +70,21 @@ func (r *ModelMeshClusterRoleBindingReconciler) Reconcile(ctx context.Context, l func (r *ModelMeshClusterRoleBindingReconciler) Cleanup(ctx context.Context, log logr.Logger, isvcNs string) error { log.V(1).Info("Deleting ClusterRoleBinding object for target namespace") - return r.clusterRoleBindingHandler.DeleteClusterRoleBinding(ctx, types.NamespacedName{Name: getClusterRoleBindingName(isvcNs), Namespace: isvcNs}) + crbName := r.clusterRoleBindingHandler.GetClusterRoleBindingName(isvcNs, r.serviceAccountName) + return r.clusterRoleBindingHandler.DeleteClusterRoleBinding(ctx, types.NamespacedName{Name: crbName, Namespace: isvcNs}) } func (r *ModelMeshClusterRoleBindingReconciler) createDesiredResource(isvc *kservev1beta1.InferenceService) (*v1.ClusterRoleBinding, error) { - desiredClusterRoleBinding := &v1.ClusterRoleBinding{ - ObjectMeta: metav1.ObjectMeta{ - Name: getClusterRoleBindingName(isvc.Namespace), - Namespace: isvc.Namespace, - }, - Subjects: []v1.Subject{ - { - Kind: "ServiceAccount", - Namespace: isvc.Namespace, - Name: modelMeshServiceAccountName, - }, - }, - RoleRef: v1.RoleRef{ - APIGroup: "rbac.authorization.k8s.io", - Kind: "ClusterRole", - Name: "system:auth-delegator", - }, - } + desiredClusterRoleBindingName := r.clusterRoleBindingHandler.GetClusterRoleBindingName(isvc.Namespace, r.serviceAccountName) + desiredClusterRoleBinding := r.clusterRoleBindingHandler.CreateDesiredClusterRoleBinding(desiredClusterRoleBindingName, r.serviceAccountName, isvc.Namespace) return desiredClusterRoleBinding, nil } func (r *ModelMeshClusterRoleBindingReconciler) getExistingResource(ctx context.Context, log logr.Logger, isvc *kservev1beta1.InferenceService) (*v1.ClusterRoleBinding, error) { - return r.clusterRoleBindingHandler.FetchClusterRoleBinding(ctx, log, types.NamespacedName{Name: getClusterRoleBindingName(isvc.Namespace), Namespace: isvc.Namespace}) + crbName := r.clusterRoleBindingHandler.GetClusterRoleBindingName(isvc.Namespace, r.serviceAccountName) + return r.clusterRoleBindingHandler.FetchClusterRoleBinding(ctx, log, types.NamespacedName{Name: crbName, Namespace: isvc.Namespace}) } func (r *ModelMeshClusterRoleBindingReconciler) processDelta(ctx context.Context, log logr.Logger, desiredCRB *v1.ClusterRoleBinding, existingCRB *v1.ClusterRoleBinding) (err error) { - comparator := comparators.GetClusterRoleBindingComparator() - delta := r.deltaProcessor.ComputeDelta(comparator, desiredCRB, existingCRB) - - if !delta.HasChanges() { - log.V(1).Info("No delta found") - return nil - } - - if delta.IsAdded() { - log.V(1).Info("Delta found", "create", desiredCRB.GetName()) - if err = r.client.Create(ctx, desiredCRB); err != nil { - return - } - } - if delta.IsUpdated() { - log.V(1).Info("Delta found", "update", existingCRB.GetName()) - rp := existingCRB.DeepCopy() - rp.RoleRef = desiredCRB.RoleRef - rp.Subjects = desiredCRB.Subjects - - if err = r.client.Update(ctx, rp); err != nil { - return - } - } - if delta.IsRemoved() { - log.V(1).Info("Delta found", "delete", existingCRB.GetName()) - if err = r.client.Delete(ctx, existingCRB); err != nil { - return - } - } - return nil -} - -func getClusterRoleBindingName(isvcNamespace string) string { - return isvcNamespace + "-" + modelMeshServiceAccountName + "-auth-delegator" + return r.clusterRoleBindingHandler.ProcessDelta(ctx, log, desiredCRB, existingCRB, r.deltaProcessor) } diff --git a/controllers/reconcilers/mm_inferenceservice_reconciler.go b/controllers/reconcilers/mm_inferenceservice_reconciler.go index e44bd98f..521d6b5c 100644 --- a/controllers/reconcilers/mm_inferenceservice_reconciler.go +++ b/controllers/reconcilers/mm_inferenceservice_reconciler.go @@ -17,7 +17,9 @@ package reconcilers import ( "context" + "github.com/hashicorp/go-multierror" + "github.com/opendatahub-io/odh-model-controller/controllers/constants" "github.com/go-logr/logr" kservev1beta1 "github.com/kserve/kserve/pkg/apis/serving/v1beta1" @@ -37,7 +39,7 @@ func NewModelMeshInferenceServiceReconciler(client client.Client) *ModelMeshInfe client: client, subResourceReconcilers: []SubResourceReconciler{ NewModelMeshRouteReconciler(client), - NewModelMeshServiceAccountReconciler(client), + NewServiceAccountReconciler(client, constants.ModelMeshServiceAccountName), NewModelMeshClusterRoleBindingReconciler(client), }, } @@ -64,7 +66,7 @@ func (r *ModelMeshInferenceServiceReconciler) DeleteModelMeshResourcesIfNoMMIsvc if err != nil { return err } - if isvcDeploymentMode != utils.ModelMesh { + if isvcDeploymentMode != constants.ModelMesh { inferenceServiceList.Items = append(inferenceServiceList.Items[:i], inferenceServiceList.Items[i+1:]...) } } diff --git a/controllers/reconcilers/mm_serviceaccount_reconciler.go b/controllers/reconcilers/serviceaccount_reconciler.go similarity index 69% rename from controllers/reconcilers/mm_serviceaccount_reconciler.go rename to controllers/reconcilers/serviceaccount_reconciler.go index 3902753d..11c00925 100644 --- a/controllers/reconcilers/mm_serviceaccount_reconciler.go +++ b/controllers/reconcilers/serviceaccount_reconciler.go @@ -17,6 +17,7 @@ package reconcilers import ( "context" + "github.com/go-logr/logr" "github.com/opendatahub-io/odh-model-controller/controllers/comparators" "github.com/opendatahub-io/odh-model-controller/controllers/processors" @@ -30,28 +31,26 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -const ( - modelMeshServiceAccountName = "modelmesh-serving-sa" -) - -var _ SubResourceReconciler = (*ModelMeshServiceAccountReconciler)(nil) +var _ SubResourceReconciler = (*ServiceAccountReconciler)(nil) -type ModelMeshServiceAccountReconciler struct { +type ServiceAccountReconciler struct { SingleResourcePerNamespace client client.Client serviceAccountHandler resources.ServiceAccountHandler deltaProcessor processors.DeltaProcessor + serviceAccountName string } -func NewModelMeshServiceAccountReconciler(client client.Client) *ModelMeshServiceAccountReconciler { - return &ModelMeshServiceAccountReconciler{ +func NewServiceAccountReconciler(client client.Client, serviceAccountName string) *ServiceAccountReconciler { + return &ServiceAccountReconciler{ client: client, serviceAccountHandler: resources.NewServiceAccountHandler(client), deltaProcessor: processors.NewDeltaProcessor(), + serviceAccountName: serviceAccountName, } } -func (r *ModelMeshServiceAccountReconciler) Reconcile(ctx context.Context, log logr.Logger, isvc *kservev1beta1.InferenceService) error { +func (r *ServiceAccountReconciler) Reconcile(ctx context.Context, log logr.Logger, isvc *kservev1beta1.InferenceService) error { log.V(1).Info("Reconciling ServiceAccount for InferenceService") // Create Desired resource desiredResource, err := r.createDesiredResource(isvc) @@ -72,26 +71,26 @@ func (r *ModelMeshServiceAccountReconciler) Reconcile(ctx context.Context, log l return nil } -func (r *ModelMeshServiceAccountReconciler) Cleanup(ctx context.Context, log logr.Logger, isvcNs string) error { +func (r *ServiceAccountReconciler) Cleanup(ctx context.Context, log logr.Logger, isvcNs string) error { log.V(1).Info("Deleting ServiceAccount object for target namespace") - return r.serviceAccountHandler.DeleteServiceAccount(ctx, types.NamespacedName{Name: modelMeshServiceAccountName, Namespace: isvcNs}) + return r.serviceAccountHandler.DeleteServiceAccount(ctx, types.NamespacedName{Name: r.serviceAccountName, Namespace: isvcNs}) } -func (r *ModelMeshServiceAccountReconciler) createDesiredResource(isvc *kservev1beta1.InferenceService) (*corev1.ServiceAccount, error) { +func (r *ServiceAccountReconciler) createDesiredResource(isvc *kservev1beta1.InferenceService) (*corev1.ServiceAccount, error) { desiredSA := &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ - Name: modelMeshServiceAccountName, + Name: r.serviceAccountName, Namespace: isvc.Namespace, }, } return desiredSA, nil } -func (r *ModelMeshServiceAccountReconciler) getExistingResource(ctx context.Context, log logr.Logger, isvc *kservev1beta1.InferenceService) (*corev1.ServiceAccount, error) { - return r.serviceAccountHandler.FetchServiceAccount(ctx, log, types.NamespacedName{Name: modelMeshServiceAccountName, Namespace: isvc.Namespace}) +func (r *ServiceAccountReconciler) getExistingResource(ctx context.Context, log logr.Logger, isvc *kservev1beta1.InferenceService) (*corev1.ServiceAccount, error) { + return r.serviceAccountHandler.FetchServiceAccount(ctx, log, types.NamespacedName{Name: r.serviceAccountName, Namespace: isvc.Namespace}) } -func (r *ModelMeshServiceAccountReconciler) processDelta(ctx context.Context, log logr.Logger, desiredSA *corev1.ServiceAccount, existingSA *corev1.ServiceAccount) (err error) { +func (r *ServiceAccountReconciler) processDelta(ctx context.Context, log logr.Logger, desiredSA *corev1.ServiceAccount, existingSA *corev1.ServiceAccount) (err error) { comparator := comparators.GetServiceAccountComparator() delta := r.deltaProcessor.ComputeDelta(comparator, desiredSA, existingSA) diff --git a/controllers/resources/clusterrolebinding.go b/controllers/resources/clusterrolebinding.go index fcac4e45..bdccfa24 100644 --- a/controllers/resources/clusterrolebinding.go +++ b/controllers/resources/clusterrolebinding.go @@ -18,9 +18,13 @@ package resources import ( "context" "fmt" + "github.com/go-logr/logr" + "github.com/opendatahub-io/odh-model-controller/controllers/comparators" + "github.com/opendatahub-io/odh-model-controller/controllers/processors" v1 "k8s.io/api/rbac/v1" "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" ) @@ -28,6 +32,9 @@ import ( type ClusterRoleBindingHandler interface { FetchClusterRoleBinding(ctx context.Context, log logr.Logger, key types.NamespacedName) (*v1.ClusterRoleBinding, error) DeleteClusterRoleBinding(ctx context.Context, key types.NamespacedName) error + CreateDesiredClusterRoleBinding(crbName string, serviceAccountName string, namespace string) *v1.ClusterRoleBinding + ProcessDelta(ctx context.Context, log logr.Logger, desiredCRB *v1.ClusterRoleBinding, existingCRB *v1.ClusterRoleBinding, deltaProcessor processors.DeltaProcessor) (err error) + GetClusterRoleBindingName(isvcNamespace string, serviceAccountName string) string } type clusterRoleBindingHandler struct { @@ -67,3 +74,66 @@ func (r *clusterRoleBindingHandler) DeleteClusterRoleBinding(ctx context.Context } return nil } + +func (r *clusterRoleBindingHandler) CreateDesiredClusterRoleBinding(crbName string, serviceAccountName string, namespace string) *v1.ClusterRoleBinding { + desiredClusterRoleBinding := &v1.ClusterRoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: crbName, + Namespace: namespace, + }, + Subjects: []v1.Subject{ + { + Kind: "ServiceAccount", + Namespace: namespace, + Name: serviceAccountName, + }, + }, + RoleRef: v1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "ClusterRole", + Name: "system:auth-delegator", + }, + } + return desiredClusterRoleBinding +} + +func (r *clusterRoleBindingHandler) ProcessDelta(ctx context.Context, log logr.Logger, desiredCRB *v1.ClusterRoleBinding, existingCRB *v1.ClusterRoleBinding, deltaProcessor processors.DeltaProcessor) (err error) { + comparator := comparators.GetClusterRoleBindingComparator() + delta := deltaProcessor.ComputeDelta(comparator, desiredCRB, existingCRB) + + if !delta.HasChanges() { + log.V(1).Info("No delta found") + return nil + } + + if delta.IsAdded() { + log.V(1).Info("Delta found", "create", desiredCRB.GetName()) + if err = r.client.Create(ctx, desiredCRB); err != nil { + if errors.IsAlreadyExists(err) { + return nil + } + return err + } + } + if delta.IsUpdated() { + log.V(1).Info("Delta found", "update", existingCRB.GetName()) + rp := existingCRB.DeepCopy() + rp.RoleRef = desiredCRB.RoleRef + rp.Subjects = desiredCRB.Subjects + + if err = r.client.Update(ctx, rp); err != nil { + return + } + } + if delta.IsRemoved() { + log.V(1).Info("Delta found", "delete", existingCRB.GetName()) + if err = r.client.Delete(ctx, existingCRB); err != nil { + return + } + } + return nil +} + +func (r *clusterRoleBindingHandler) GetClusterRoleBindingName(isvcNamespace string, serviceAccountName string) string { + return isvcNamespace + "-" + serviceAccountName + "-auth-delegator" +} diff --git a/controllers/utils/utils.go b/controllers/utils/utils.go index a05e41ed..7fde9682 100644 --- a/controllers/utils/utils.go +++ b/controllers/utils/utils.go @@ -30,13 +30,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) -type IsvcDeploymentMode string - var ( - Serverless IsvcDeploymentMode = "Serverless" - RawDeployment IsvcDeploymentMode = "RawDeployment" - ModelMesh IsvcDeploymentMode = "ModelMesh" - gvResourcesCache map[string]*metav1.APIResourceList _appNamespace *string @@ -48,18 +42,18 @@ const ( KServeWithServiceMeshComponent = "kserve-service-mesh" ) -func GetDeploymentModeForIsvc(ctx context.Context, cli client.Client, isvc *kservev1beta1.InferenceService) (IsvcDeploymentMode, error) { +func GetDeploymentModeForIsvc(ctx context.Context, cli client.Client, isvc *kservev1beta1.InferenceService) (constants.IsvcDeploymentMode, error) { // If ISVC specifically sets deployment mode using an annotation, return bool depending on value value, exists := isvc.Annotations[inferenceServiceDeploymentModeAnnotation] if exists { switch value { - case string(ModelMesh): - return ModelMesh, nil - case string(Serverless): - return Serverless, nil - case string(RawDeployment): - return RawDeployment, nil + case string(constants.ModelMesh): + return constants.ModelMesh, nil + case string(constants.Serverless): + return constants.Serverless, nil + case string(constants.RawDeployment): + return constants.RawDeployment, nil default: return "", fmt.Errorf("the deployment mode '%s' of the Inference Service is invalid", value) } @@ -80,12 +74,12 @@ func GetDeploymentModeForIsvc(ctx context.Context, cli client.Client, isvc *kser } defaultDeploymentMode := deployData["defaultDeploymentMode"] switch defaultDeploymentMode { - case string(ModelMesh): - return ModelMesh, nil - case string(Serverless): - return Serverless, nil - case string(RawDeployment): - return RawDeployment, nil + case string(constants.ModelMesh): + return constants.ModelMesh, nil + case string(constants.Serverless): + return constants.Serverless, nil + case string(constants.RawDeployment): + return constants.RawDeployment, nil default: return "", fmt.Errorf("the deployment mode '%s' of the Inference Service is invalid", defaultDeploymentMode) }