Skip to content

Commit

Permalink
Add reconciliation for Kserve Raw (#274)
Browse files Browse the repository at this point in the history
Signed-off-by: Vedant Mahabaleshwarkar <vmahabal@redhat.com>
  • Loading branch information
VedantMahabaleshwarkar authored Dec 9, 2024
1 parent 6fef9bc commit c26e964
Show file tree
Hide file tree
Showing 12 changed files with 735 additions and 110 deletions.
23 changes: 19 additions & 4 deletions controllers/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ limitations under the License.

package constants

type IsvcDeploymentMode string

const (
InferenceServiceKind = "InferenceService"

Expand All @@ -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
Expand Down
16 changes: 12 additions & 4 deletions controllers/inferenceservice_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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
}

Expand All @@ -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
}
189 changes: 189 additions & 0 deletions controllers/kserve_raw_inferenceservice_controller_test.go
Original file line number Diff line number Diff line change
@@ -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())
})
})
})
Loading

0 comments on commit c26e964

Please sign in to comment.