diff --git a/docs/samples/v1beta1/advanced/cluster_local.yaml b/docs/samples/v1beta1/advanced/cluster_local.yaml new file mode 100644 index 00000000000..c3f9c53a967 --- /dev/null +++ b/docs/samples/v1beta1/advanced/cluster_local.yaml @@ -0,0 +1,10 @@ +apiVersion: "serving.kubeflow.org/v1beta1" +kind: "InferenceService" +metadata: + name: "sklearn-iris-local" + labels: + serving.knative.dev/visibility: cluster-local +spec: + predictor: + sklearn: + storageUri: "gs://kfserving-samples/models/sklearn/iris" diff --git a/hack/quick_install.sh b/hack/quick_install.sh index 9178245ba77..01a606f7be2 100755 --- a/hack/quick_install.sh +++ b/hack/quick_install.sh @@ -1,7 +1,7 @@ set -e export ISTIO_VERSION=1.6.2 -export KNATIVE_VERSION=v0.15.0 +export KNATIVE_VERSION=v0.18.0 export KFSERVING_VERSION=v0.4.1 curl -L https://git.io/getLatestIstio | sh - cd istio-${ISTIO_VERSION} diff --git a/pkg/apis/serving/v1beta1/openapi_generated.go b/pkg/apis/serving/v1beta1/openapi_generated.go index fda2eb61f51..d40dc91f8be 100644 --- a/pkg/apis/serving/v1beta1/openapi_generated.go +++ b/pkg/apis/serving/v1beta1/openapi_generated.go @@ -2984,7 +2984,6 @@ func schema_pkg_apis_serving_v1beta1_ONNXRuntimeSpec(ref common.ReferenceCallbac }, }, }, - }, }, Dependencies: []string{ @@ -3242,7 +3241,6 @@ func schema_pkg_apis_serving_v1beta1_PMMLSpec(ref common.ReferenceCallback) comm }, }, }, - }, }, Dependencies: []string{ @@ -3900,7 +3898,6 @@ func schema_pkg_apis_serving_v1beta1_PredictorExtensionSpec(ref common.Reference }, }, }, - }, }, Dependencies: []string{ @@ -4686,7 +4683,6 @@ func schema_pkg_apis_serving_v1beta1_SKLearnSpec(ref common.ReferenceCallback) c }, }, }, - }, }, Dependencies: []string{ @@ -4944,7 +4940,6 @@ func schema_pkg_apis_serving_v1beta1_TFServingSpec(ref common.ReferenceCallback) }, }, }, - }, }, Dependencies: []string{ @@ -5209,7 +5204,6 @@ func schema_pkg_apis_serving_v1beta1_TorchServeSpec(ref common.ReferenceCallback }, }, }, - }, }, Dependencies: []string{ @@ -5926,7 +5920,6 @@ func schema_pkg_apis_serving_v1beta1_TritonSpec(ref common.ReferenceCallback) co }, }, }, - }, }, Dependencies: []string{ @@ -6184,7 +6177,6 @@ func schema_pkg_apis_serving_v1beta1_XGBoostSpec(ref common.ReferenceCallback) c }, }, }, - }, }, Dependencies: []string{ diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go index 294ed12aa82..17a1e9bce86 100644 --- a/pkg/constants/constants.go +++ b/pkg/constants/constants.go @@ -204,6 +204,10 @@ var ( StorageInitializerSourceUriInternalAnnotationKey, "kubectl.kubernetes.io/last-applied-configuration", } + + RevisionTemplateLabelDisallowedList = []string{ + VisibilityLabel, + } ) func (e InferenceServiceComponent) String() string { diff --git a/pkg/controller/v1beta1/inferenceservice/controller_test.go b/pkg/controller/v1beta1/inferenceservice/controller_test.go index 5ceef8a3fce..8486a170456 100644 --- a/pkg/controller/v1beta1/inferenceservice/controller_test.go +++ b/pkg/controller/v1beta1/inferenceservice/controller_test.go @@ -217,12 +217,12 @@ var _ = Describe("v1beta1 inference service controller", func() { expectedVirtualService := &v1alpha3.VirtualService{ Spec: istiov1alpha3.VirtualService{ Gateways: []string{ - constants.KnativeIngressGateway, constants.KnativeLocalGateway, + constants.KnativeIngressGateway, }, Hosts: []string{ - constants.InferenceServiceHostName(serviceKey.Name, serviceKey.Namespace, domain), network.GetServiceHostname(serviceKey.Name, serviceKey.Namespace), + constants.InferenceServiceHostName(serviceKey.Name, serviceKey.Namespace, domain), }, Http: []*istiov1alpha3.HTTPRoute{ { @@ -257,6 +257,7 @@ var _ = Describe("v1beta1 inference service controller", func() { Host: network.GetServiceHostname("cluster-local-gateway", "istio-system"), Port: &istiov1alpha3.PortSelector{Number: constants.CommonDefaultHttpPort}, }, + Weight: 100, }, }, }, diff --git a/pkg/controller/v1beta1/inferenceservice/reconcilers/ingress/ingress_reconciler.go b/pkg/controller/v1beta1/inferenceservice/reconcilers/ingress/ingress_reconciler.go index e5723cb61e7..06bf26564b7 100644 --- a/pkg/controller/v1beta1/inferenceservice/reconcilers/ingress/ingress_reconciler.go +++ b/pkg/controller/v1beta1/inferenceservice/reconcilers/ingress/ingress_reconciler.go @@ -159,7 +159,7 @@ func (r *IngressReconciler) reconcileExternalService(isvc *v1beta1.InferenceServ return nil } -func (ir *IngressReconciler) createHTTPRouteDestination(targetHost, namespace string, gatewayService string) *istiov1alpha3.HTTPRouteDestination { +func createHTTPRouteDestination(targetHost, namespace string, gatewayService string) *istiov1alpha3.HTTPRouteDestination { httpRouteDestination := &istiov1alpha3.HTTPRouteDestination{ Headers: &istiov1alpha3.Headers{ Request: &istiov1alpha3.Headers_HeaderOperations{ @@ -174,11 +174,12 @@ func (ir *IngressReconciler) createHTTPRouteDestination(targetHost, namespace st Number: constants.CommonDefaultHttpPort, }, }, + Weight: 100, } return httpRouteDestination } -func (ir *IngressReconciler) createHTTPMatchRequest(prefix, targetHost, internalHost string, isInternal bool) []*istiov1alpha3.HTTPMatchRequest { +func createHTTPMatchRequest(prefix, targetHost, internalHost string, isInternal bool, config *v1beta1.IngressConfig) []*istiov1alpha3.HTTPMatchRequest { var uri *istiov1alpha3.StringMatch if prefix != "" { uri = &istiov1alpha3.StringMatch{ @@ -207,13 +208,18 @@ func (ir *IngressReconciler) createHTTPMatchRequest(prefix, targetHost, internal Regex: constants.HostRegExp(targetHost), }, }, - Gateways: []string{ir.ingressConfig.IngressGateway}, + Gateways: []string{config.IngressGateway}, }) } return matchRequests } -func (ir *IngressReconciler) Reconcile(isvc *v1beta1.InferenceService) error { +func createIngress(isvc *v1beta1.InferenceService, config *v1beta1.IngressConfig) *v1alpha3.VirtualService { + serviceHost := getServiceHost(isvc) + if serviceHost == "" { + return nil + } + if !isvc.Status.IsConditionReady(v1beta1.PredictorReady) { isvc.Status.SetCondition(v1beta1.IngressReady, &apis.Condition{ Type: v1beta1.IngressReady, @@ -222,11 +228,6 @@ func (ir *IngressReconciler) Reconcile(isvc *v1beta1.InferenceService) error { }) return nil } - serviceHost := getServiceHost(isvc) - serviceUrl := getServiceUrl(isvc) - if serviceHost == "" || serviceUrl == "" { - return nil - } backend := constants.DefaultPredictorServiceName(isvc.Name) if isvc.Spec.Transformer != nil { @@ -242,7 +243,7 @@ func (ir *IngressReconciler) Reconcile(isvc *v1beta1.InferenceService) error { } isInternal := false //if service is labelled with cluster local or knative domain is configured as internal - if val, ok := isvc.Labels[constants.VisibilityLabel]; ok && val == "ClusterLocal" { + if val, ok := isvc.Labels[constants.VisibilityLabel]; ok && val == "cluster-local" { isInternal = true } serviceInternalHostName := network.GetServiceHostname(isvc.Name, isvc.Namespace) @@ -261,45 +262,63 @@ func (ir *IngressReconciler) Reconcile(isvc *v1beta1.InferenceService) error { return nil } explainerRouter := istiov1alpha3.HTTPRoute{ - Match: ir.createHTTPMatchRequest(constants.ExplainPrefix(), serviceHost, - network.GetServiceHostname(isvc.Name, isvc.Namespace), isInternal), + Match: createHTTPMatchRequest(constants.ExplainPrefix(), serviceHost, + network.GetServiceHostname(isvc.Name, isvc.Namespace), isInternal, config), Route: []*istiov1alpha3.HTTPRouteDestination{ - ir.createHTTPRouteDestination(constants.DefaultExplainerServiceName(isvc.Name), isvc.Namespace, constants.LocalGatewayHost), + createHTTPRouteDestination(constants.DefaultExplainerServiceName(isvc.Name), isvc.Namespace, constants.LocalGatewayHost), }, } httpRoutes = append(httpRoutes, &explainerRouter) } // Add predict route httpRoutes = append(httpRoutes, &istiov1alpha3.HTTPRoute{ - Match: ir.createHTTPMatchRequest("", serviceHost, - network.GetServiceHostname(isvc.Name, isvc.Namespace), isInternal), + Match: createHTTPMatchRequest("", serviceHost, + network.GetServiceHostname(isvc.Name, isvc.Namespace), isInternal, config), Route: []*istiov1alpha3.HTTPRouteDestination{ - ir.createHTTPRouteDestination(backend, isvc.Namespace, constants.LocalGatewayHost), + createHTTPRouteDestination(backend, isvc.Namespace, constants.LocalGatewayHost), }, }) - - //Create external service which points to local gateway - if err := ir.reconcileExternalService(isvc); err != nil { - return errors.Wrapf(err, "fails to reconcile external name service") + hosts := []string{ + network.GetServiceHostname(isvc.Name, isvc.Namespace), + } + gateways := []string{ + constants.KnativeLocalGateway, + } + if !isInternal { + hosts = append(hosts, serviceHost) + gateways = append(gateways, config.IngressGateway) } - //Create ingress desiredIngress := &v1alpha3.VirtualService{ ObjectMeta: metav1.ObjectMeta{ Name: isvc.Name, Namespace: isvc.Namespace, }, Spec: istiov1alpha3.VirtualService{ - Hosts: []string{ - serviceHost, - network.GetServiceHostname(isvc.Name, isvc.Namespace), - }, - Gateways: []string{ - ir.ingressConfig.IngressGateway, - constants.KnativeLocalGateway, - }, - Http: httpRoutes, + Hosts: hosts, + Gateways: gateways, + Http: httpRoutes, }, } + return desiredIngress +} + +func (ir *IngressReconciler) Reconcile(isvc *v1beta1.InferenceService) error { + serviceHost := getServiceHost(isvc) + serviceUrl := getServiceUrl(isvc) + if serviceHost == "" || serviceUrl == "" { + return nil + } + //Create ingress + desiredIngress := createIngress(isvc, ir.ingressConfig) + if desiredIngress == nil { + return nil + } + + //Create external service which points to local gateway + if err := ir.reconcileExternalService(isvc); err != nil { + return errors.Wrapf(err, "fails to reconcile external name service") + } + if err := controllerutil.SetControllerReference(isvc, desiredIngress, ir.scheme); err != nil { return errors.Wrapf(err, "fails to set owner reference for ingress") } diff --git a/pkg/controller/v1beta1/inferenceservice/reconcilers/ingress/ingress_reconciler_test.go b/pkg/controller/v1beta1/inferenceservice/reconcilers/ingress/ingress_reconciler_test.go new file mode 100644 index 00000000000..37fb0b03bd3 --- /dev/null +++ b/pkg/controller/v1beta1/inferenceservice/reconcilers/ingress/ingress_reconciler_test.go @@ -0,0 +1,537 @@ +/* +Copyright 2020 kubeflow.org. + +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 ingress + +import ( + "github.com/google/go-cmp/cmp" + "github.com/kubeflow/kfserving/pkg/apis/serving/v1beta1" + "github.com/kubeflow/kfserving/pkg/constants" + istiov1alpha3 "istio.io/api/networking/v1alpha3" + "istio.io/client-go/pkg/apis/networking/v1alpha3" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/network" + "testing" +) + +func TestCreateVirtualService(t *testing.T) { + serviceName := "my-model" + namespace := "test" + domain := "example.com" + serviceHostName := constants.InferenceServiceHostName(serviceName, namespace, domain) + serviceInternalHostName := network.GetServiceHostname(serviceName, namespace) + predictorHostname := constants.InferenceServiceHostName(constants.DefaultPredictorServiceName(serviceName), namespace, domain) + transformerHostname := constants.InferenceServiceHostName(constants.DefaultTransformerServiceName(serviceName), namespace, domain) + explainerHostname := constants.InferenceServiceHostName(constants.DefaultExplainerServiceName(serviceName), namespace, domain) + predictorRouteMatch := []*istiov1alpha3.HTTPMatchRequest{ + { + Authority: &istiov1alpha3.StringMatch{ + MatchType: &istiov1alpha3.StringMatch_Regex{ + Regex: constants.HostRegExp(network.GetServiceHostname(serviceName, namespace)), + }, + }, + Gateways: []string{constants.KnativeLocalGateway}, + }, + { + Authority: &istiov1alpha3.StringMatch{ + MatchType: &istiov1alpha3.StringMatch_Regex{ + Regex: constants.HostRegExp(constants.InferenceServiceHostName(serviceName, namespace, domain)), + }, + }, + Gateways: []string{constants.KnativeIngressGateway}, + }, + } + cases := []struct { + name string + componentStatus *v1beta1.InferenceServiceStatus + expectedService *v1alpha3.VirtualService + }{{ + name: "nil status should not be ready", + componentStatus: nil, + expectedService: nil, + }, { + name: "predictor missing url", + componentStatus: &v1beta1.InferenceServiceStatus{ + Components: map[v1beta1.ComponentType]v1beta1.ComponentStatusSpec{ + v1beta1.PredictorComponent: {}, + }, + }, + expectedService: nil, + }, { + name: "found predictor status", + componentStatus: &v1beta1.InferenceServiceStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{ + { + Type: v1beta1.PredictorReady, + Status: corev1.ConditionTrue, + }, + }, + }, + Components: map[v1beta1.ComponentType]v1beta1.ComponentStatusSpec{ + v1beta1.PredictorComponent: { + URL: &apis.URL{ + Scheme: "http", + Host: predictorHostname, + }, + Address: &duckv1.Addressable{ + URL: &apis.URL{ + Scheme: "http", + Host: network.GetServiceHostname(constants.DefaultPredictorServiceName(serviceName), namespace), + }, + }, + }, + }, + }, + expectedService: &v1alpha3.VirtualService{ + ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, + Spec: istiov1alpha3.VirtualService{ + Hosts: []string{serviceInternalHostName, serviceHostName}, + Gateways: []string{constants.KnativeLocalGateway, constants.KnativeIngressGateway}, + Http: []*istiov1alpha3.HTTPRoute{ + { + Match: predictorRouteMatch, + Route: []*istiov1alpha3.HTTPRouteDestination{ + { + Destination: &istiov1alpha3.Destination{Host: constants.LocalGatewayHost, Port: &istiov1alpha3.PortSelector{Number: constants.CommonDefaultHttpPort}}, + Weight: 100, + Headers: &istiov1alpha3.Headers{ + Request: &istiov1alpha3.Headers_HeaderOperations{Set: map[string]string{ + "Host": network.GetServiceHostname(constants.DefaultPredictorServiceName(serviceName), namespace)}}, + }, + }, + }, + }, + }, + }, + }, + }, { + name: "local cluster predictor", + componentStatus: &v1beta1.InferenceServiceStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{ + { + Type: v1beta1.PredictorReady, + Status: corev1.ConditionTrue, + }, + }, + }, + Components: map[v1beta1.ComponentType]v1beta1.ComponentStatusSpec{ + v1beta1.PredictorComponent: { + URL: &apis.URL{ + Scheme: "http", + Host: network.GetServiceHostname(constants.DefaultPredictorServiceName(serviceName), namespace), + }, + Address: &duckv1.Addressable{ + URL: &apis.URL{ + Scheme: "http", + Host: network.GetServiceHostname(constants.DefaultPredictorServiceName(serviceName), namespace), + }, + }, + }, + }, + }, + expectedService: &v1alpha3.VirtualService{ + ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, + Spec: istiov1alpha3.VirtualService{ + Hosts: []string{serviceInternalHostName}, + Gateways: []string{constants.KnativeLocalGateway}, + Http: []*istiov1alpha3.HTTPRoute{ + { + Match: []*istiov1alpha3.HTTPMatchRequest{ + { + Authority: &istiov1alpha3.StringMatch{ + MatchType: &istiov1alpha3.StringMatch_Regex{ + Regex: constants.HostRegExp(network.GetServiceHostname(serviceName, namespace)), + }, + }, + Gateways: []string{constants.KnativeLocalGateway}, + }, + }, + Route: []*istiov1alpha3.HTTPRouteDestination{ + { + Destination: &istiov1alpha3.Destination{Host: constants.LocalGatewayHost, Port: &istiov1alpha3.PortSelector{Number: constants.CommonDefaultHttpPort}}, + Weight: 100, + Headers: &istiov1alpha3.Headers{ + Request: &istiov1alpha3.Headers_HeaderOperations{Set: map[string]string{ + "Host": network.GetServiceHostname(constants.DefaultPredictorServiceName(serviceName), namespace)}}, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "nil transformer status fails with status unknown", + componentStatus: &v1beta1.InferenceServiceStatus{ + Components: map[v1beta1.ComponentType]v1beta1.ComponentStatusSpec{ + v1beta1.TransformerComponent: {}, + v1beta1.PredictorComponent: { + URL: &apis.URL{ + Scheme: "http", + Host: predictorHostname, + }, + Address: &duckv1.Addressable{ + URL: &apis.URL{ + Scheme: "http", + Host: network.GetServiceHostname(serviceName, namespace), + }, + }, + }, + }, + }, + expectedService: nil, + }, { + name: "found transformer and predictor status", + componentStatus: &v1beta1.InferenceServiceStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{ + { + Type: v1beta1.PredictorReady, + Status: corev1.ConditionTrue, + }, + { + Type: v1beta1.TransformerReady, + Status: corev1.ConditionTrue, + }, + }, + }, + Components: map[v1beta1.ComponentType]v1beta1.ComponentStatusSpec{ + v1beta1.TransformerComponent: { + URL: &apis.URL{ + Scheme: "http", + Host: transformerHostname, + }, + Address: &duckv1.Addressable{ + URL: &apis.URL{ + Scheme: "http", + Host: network.GetServiceHostname(constants.DefaultTransformerServiceName(serviceName), namespace), + }, + }, + }, + v1beta1.PredictorComponent: { + URL: &apis.URL{ + Scheme: "http", + Host: predictorHostname, + }, + Address: &duckv1.Addressable{ + URL: &apis.URL{ + Scheme: "http", + Host: network.GetServiceHostname(constants.DefaultPredictorServiceName(serviceName), namespace), + }, + }, + }, + }, + }, + expectedService: &v1alpha3.VirtualService{ + ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, + Spec: istiov1alpha3.VirtualService{ + Hosts: []string{serviceInternalHostName, serviceHostName}, + Gateways: []string{constants.KnativeLocalGateway, constants.KnativeIngressGateway}, + Http: []*istiov1alpha3.HTTPRoute{ + { + Match: predictorRouteMatch, + Route: []*istiov1alpha3.HTTPRouteDestination{ + { + Destination: &istiov1alpha3.Destination{Host: constants.LocalGatewayHost, Port: &istiov1alpha3.PortSelector{Number: constants.CommonDefaultHttpPort}}, + Weight: 100, + Headers: &istiov1alpha3.Headers{ + Request: &istiov1alpha3.Headers_HeaderOperations{Set: map[string]string{ + "Host": network.GetServiceHostname(constants.DefaultTransformerServiceName(serviceName), namespace), + }}, + }, + }, + }, + }, + }, + }, + }, + }, { + name: "found transformer and predictor status", + componentStatus: &v1beta1.InferenceServiceStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{ + { + Type: v1beta1.PredictorReady, + Status: corev1.ConditionTrue, + }, + { + Type: v1beta1.TransformerReady, + Status: corev1.ConditionTrue, + }, + }, + }, + Components: map[v1beta1.ComponentType]v1beta1.ComponentStatusSpec{ + v1beta1.TransformerComponent: { + URL: &apis.URL{ + Scheme: "http", + Host: transformerHostname, + }, + Address: &duckv1.Addressable{ + URL: &apis.URL{ + Scheme: "http", + Host: network.GetServiceHostname(constants.DefaultTransformerServiceName(serviceName), namespace), + }, + }, + }, + v1beta1.PredictorComponent: { + URL: &apis.URL{ + Scheme: "http", + Host: predictorHostname, + }, + Address: &duckv1.Addressable{ + URL: &apis.URL{ + Scheme: "http", + Host: network.GetServiceHostname(constants.DefaultPredictorServiceName(serviceName), namespace), + }, + }, + }, + }, + }, + expectedService: &v1alpha3.VirtualService{ + ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, + Spec: istiov1alpha3.VirtualService{ + Hosts: []string{serviceInternalHostName, serviceHostName}, + Gateways: []string{constants.KnativeLocalGateway, constants.KnativeIngressGateway}, + Http: []*istiov1alpha3.HTTPRoute{ + { + Match: predictorRouteMatch, + Route: []*istiov1alpha3.HTTPRouteDestination{ + { + Destination: &istiov1alpha3.Destination{Host: constants.LocalGatewayHost, Port: &istiov1alpha3.PortSelector{Number: constants.CommonDefaultHttpPort}}, + Weight: 100, + Headers: &istiov1alpha3.Headers{ + Request: &istiov1alpha3.Headers_HeaderOperations{Set: map[string]string{ + "Host": network.GetServiceHostname(constants.DefaultTransformerServiceName(serviceName), namespace), + }}, + }, + }, + }, + }, + }, + }, + }, + }, { + name: "nil explainer status fails with status unknown", + componentStatus: &v1beta1.InferenceServiceStatus{ + Components: map[v1beta1.ComponentType]v1beta1.ComponentStatusSpec{ + v1beta1.ExplainerComponent: {}, + v1beta1.PredictorComponent: { + URL: &apis.URL{ + Scheme: "http", + Host: predictorHostname, + }, + Address: &duckv1.Addressable{ + URL: &apis.URL{ + Scheme: "http", + Host: network.GetServiceHostname(serviceName, namespace), + }, + }, + }, + }, + }, + expectedService: nil, + }, { + name: "found explainer and predictor status", + componentStatus: &v1beta1.InferenceServiceStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{ + { + Type: v1beta1.PredictorReady, + Status: corev1.ConditionTrue, + }, + { + Type: v1beta1.ExplainerReady, + Status: corev1.ConditionTrue, + }, + }, + }, + Components: map[v1beta1.ComponentType]v1beta1.ComponentStatusSpec{ + v1beta1.ExplainerComponent: { + URL: &apis.URL{ + Scheme: "http", + Host: explainerHostname, + }, + Address: &duckv1.Addressable{ + URL: &apis.URL{ + Scheme: "http", + Host: network.GetServiceHostname(constants.DefaultExplainerServiceName(serviceName), namespace), + }, + }, + }, + v1beta1.PredictorComponent: { + URL: &apis.URL{ + Scheme: "http", + Host: predictorHostname, + }, + Address: &duckv1.Addressable{ + URL: &apis.URL{ + Scheme: "http", + Host: network.GetServiceHostname(serviceName, namespace), + }, + }, + }, + }, + }, + expectedService: &v1alpha3.VirtualService{ + ObjectMeta: metav1.ObjectMeta{Name: serviceName, Namespace: namespace}, + Spec: istiov1alpha3.VirtualService{ + Hosts: []string{serviceInternalHostName, serviceHostName}, + Gateways: []string{constants.KnativeLocalGateway, constants.KnativeIngressGateway}, + Http: []*istiov1alpha3.HTTPRoute{ + { + Match: []*istiov1alpha3.HTTPMatchRequest{ + { + Uri: &istiov1alpha3.StringMatch{ + MatchType: &istiov1alpha3.StringMatch_Regex{ + Regex: constants.ExplainPrefix(), + }, + }, + Authority: &istiov1alpha3.StringMatch{ + MatchType: &istiov1alpha3.StringMatch_Regex{ + Regex: constants.HostRegExp(network.GetServiceHostname(serviceName, namespace)), + }, + }, + Gateways: []string{constants.KnativeLocalGateway}, + }, + { + Uri: &istiov1alpha3.StringMatch{ + MatchType: &istiov1alpha3.StringMatch_Regex{ + Regex: constants.ExplainPrefix(), + }, + }, + Authority: &istiov1alpha3.StringMatch{ + MatchType: &istiov1alpha3.StringMatch_Regex{ + Regex: constants.HostRegExp(constants.InferenceServiceHostName(serviceName, namespace, domain)), + }, + }, + Gateways: []string{constants.KnativeIngressGateway}, + }, + }, + Route: []*istiov1alpha3.HTTPRouteDestination{ + { + Destination: &istiov1alpha3.Destination{Host: constants.LocalGatewayHost, Port: &istiov1alpha3.PortSelector{Number: constants.CommonDefaultHttpPort}}, + Weight: 100, + Headers: &istiov1alpha3.Headers{ + Request: &istiov1alpha3.Headers_HeaderOperations{Set: map[string]string{ + "Host": network.GetServiceHostname(constants.DefaultExplainerServiceName(serviceName), namespace)}, + }, + }, + }, + }, + }, + { + Match: predictorRouteMatch, + Route: []*istiov1alpha3.HTTPRouteDestination{ + { + Destination: &istiov1alpha3.Destination{Host: constants.LocalGatewayHost, Port: &istiov1alpha3.PortSelector{Number: constants.CommonDefaultHttpPort}}, + Weight: 100, + Headers: &istiov1alpha3.Headers{ + Request: &istiov1alpha3.Headers_HeaderOperations{Set: map[string]string{ + "Host": network.GetServiceHostname(constants.DefaultPredictorServiceName(serviceName), namespace)}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + testIsvc := &v1beta1.InferenceService{ + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + }, + Spec: v1beta1.InferenceServiceSpec{ + Predictor: v1beta1.PredictorSpec{}, + }, + } + if tc.componentStatus != nil { + testIsvc.Status = *tc.componentStatus + } + if _, ok := testIsvc.Status.Components[v1beta1.TransformerComponent]; ok { + testIsvc.Spec.Transformer = &v1beta1.TransformerSpec{} + } + if _, ok := testIsvc.Status.Components[v1beta1.ExplainerComponent]; ok { + testIsvc.Spec.Explainer = &v1beta1.ExplainerSpec{} + } + ingressConfig := &v1beta1.IngressConfig{ + IngressGateway: constants.KnativeIngressGateway, + IngressServiceName: "someIngressServiceName", + } + + actualService := createIngress(testIsvc, ingressConfig) + if diff := cmp.Diff(tc.expectedService, actualService); diff != "" { + t.Errorf("Test %q unexpected status (-want +got): %v", tc.name, diff) + } + }) + } +} + +func TestGetServiceHostname(t *testing.T) { + + testCases := []struct { + name string + expectedHostName string + predictorHostName string + }{ + { + name: "using knative domainTemplate: {{.Name}}.{{.Namespace}}.{{.Domain}}", + expectedHostName: "kftest.user1.example.com", + predictorHostName: "kftest-predictor-default.user1.example.com", + }, + { + name: "using knative domainTemplate: {{.Name}}-{{.Namespace}}.{{.Domain}}", + expectedHostName: "kftest-user1.example.com", + predictorHostName: "kftest-predictor-default-user1.example.com", + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + testIsvc := createInferenceServiceWithHostname(tt.predictorHostName) + result := getServiceHost(testIsvc) + if diff := cmp.Diff(tt.expectedHostName, result); diff != "" { + t.Errorf("Test %q unexpected result (-want +got): %v", t.Name(), diff) + } + }) + } +} + +func createInferenceServiceWithHostname(hostName string) *v1beta1.InferenceService { + return &v1beta1.InferenceService{ + Status: v1beta1.InferenceServiceStatus{ + Components: map[v1beta1.ComponentType]v1beta1.ComponentStatusSpec{ + v1beta1.PredictorComponent: { + URL: &apis.URL{ + Scheme: "http", + Host: hostName, + }, + }, + }, + }, + } +} diff --git a/pkg/controller/v1beta1/inferenceservice/reconcilers/knative/ksvc_reconciler.go b/pkg/controller/v1beta1/inferenceservice/reconcilers/knative/ksvc_reconciler.go index bd796236c7c..35dc6233058 100644 --- a/pkg/controller/v1beta1/inferenceservice/reconcilers/knative/ksvc_reconciler.go +++ b/pkg/controller/v1beta1/inferenceservice/reconcilers/knative/ksvc_reconciler.go @@ -19,6 +19,7 @@ import ( "github.com/golang/protobuf/proto" "github.com/kubeflow/kfserving/pkg/apis/serving/v1beta1" "github.com/kubeflow/kfserving/pkg/constants" + "github.com/kubeflow/kfserving/pkg/utils" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/equality" @@ -107,7 +108,9 @@ func createKnativeService(componentMeta metav1.ObjectMeta, Percent: proto.Int64(100), }) } - + labels := utils.Filter(componentMeta.Labels, func(key string) bool { + return !utils.Includes(constants.RevisionTemplateLabelDisallowedList, key) + }) service := &knservingv1.Service{ ObjectMeta: metav1.ObjectMeta{ Name: componentMeta.Name, @@ -118,7 +121,7 @@ func createKnativeService(componentMeta metav1.ObjectMeta, ConfigurationSpec: knservingv1.ConfigurationSpec{ Template: knservingv1.RevisionTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ - Labels: componentMeta.Labels, + Labels: labels, Annotations: annotations, }, Spec: knservingv1.RevisionSpec{ diff --git a/test/scripts/run-e2e-tests.sh b/test/scripts/run-e2e-tests.sh index bf289c17aee..3710edc8dc2 100755 --- a/test/scripts/run-e2e-tests.sh +++ b/test/scripts/run-e2e-tests.sh @@ -24,7 +24,7 @@ CLUSTER_NAME="${CLUSTER_NAME}" AWS_REGION="${AWS_REGION}" ISTIO_VERSION="1.3.1" -KNATIVE_VERSION="v0.15.0" +KNATIVE_VERSION="v0.17.0" KUBECTL_VERSION="v1.14.0" CERT_MANAGER_VERSION="v0.12.0" # Check and wait for istio/knative/kfserving pod started normally.