diff --git a/config/default/configmap/kfservice.yaml b/config/default/configmap/kfservice.yaml index c2e258934b3..f6aa6e06fc6 100644 --- a/config/default/configmap/kfservice.yaml +++ b/config/default/configmap/kfservice.yaml @@ -4,7 +4,7 @@ metadata: name: kfservice-config namespace: kfserving-system data: - frameworks: |- + predictors: |- { "tensorflow": { "image": "tensorflow/serving" diff --git a/docs/apis/README.md b/docs/apis/README.md index d55882f364e..3c0ea6ec741 100644 --- a/docs/apis/README.md +++ b/docs/apis/README.md @@ -479,11 +479,11 @@ ExplainerConfig -

FrameworkConfig +

PredictorConfig

(Appears on: -FrameworksConfig) +PredictorsConfig)

@@ -507,11 +507,11 @@ string -

FrameworkHandler +

Predictor

-

FrameworksConfig +

PredictorsConfig

@@ -527,8 +527,8 @@ string tensorflow
- -FrameworkConfig + +PredictorConfig @@ -539,8 +539,8 @@ FrameworkConfig tensorrt
- -FrameworkConfig + +PredictorConfig @@ -551,8 +551,8 @@ FrameworkConfig xgboost
- -FrameworkConfig + +PredictorConfig @@ -563,8 +563,8 @@ FrameworkConfig sklearn
- -FrameworkConfig + +PredictorConfig @@ -575,8 +575,8 @@ FrameworkConfig pytorch
- -FrameworkConfig + +PredictorConfig @@ -587,8 +587,8 @@ FrameworkConfig onnx
- -FrameworkConfig + +PredictorConfig diff --git a/pkg/apis/serving/v1alpha2/explainer.go b/pkg/apis/serving/v1alpha2/explainer.go index 99391fdff1b..2b0d4416615 100644 --- a/pkg/apis/serving/v1alpha2/explainer.go +++ b/pkg/apis/serving/v1alpha2/explainer.go @@ -15,22 +15,21 @@ package v1alpha2 import ( "fmt" + v1 "k8s.io/api/core/v1" "k8s.io/klog" ) -type ExplainerHandler interface { +type Explainer interface { GetStorageUri() string - CreateExplainerServingContainer(modelName string, predictorHost string, config *ExplainersConfig) *v1.Container + CreateExplainerContainer(modelName string, predictorHost string, config *ExplainersConfig) *v1.Container ApplyDefaults() Validate() error } const ( - // ExactlyOneModelSpecViolatedError is a known error message - ExactlyOneExplainerSpecViolatedError = "Exactly one of [Custom, Alibi] must be specified in ExplainerSpec" - // AtLeastOneModelSpecViolatedError is a known error message - AtLeastOneExplainerSpecViolatedError = "At least one of [Custom, Alibi] must be specified in ExplainerSpec" + // ExactlyOneExplainerViolatedError is a known error message + ExactlyOneExplainerViolatedError = "Exactly one of [Custom, Alibi] must be specified in ExplainerSpec" ) // Returns a URI to the explainer. This URI is passed to the model-initializer via the ModelInitializerSourceUriInternalAnnotationKey @@ -38,8 +37,8 @@ func (m *ExplainerSpec) GetStorageUri() string { return getExplainerHandler(m).GetStorageUri() } -func (m *ExplainerSpec) CreateExplainerServingContainer(modelName string, predictorHost string, config *ExplainersConfig) *v1.Container { - return getExplainerHandler(m).CreateExplainerServingContainer(modelName, predictorHost, config) +func (m *ExplainerSpec) CreateExplainerContainer(modelName string, predictorHost string, config *ExplainersConfig) *v1.Container { + return getExplainerHandler(m).CreateExplainerContainer(modelName, predictorHost, config) } func (m *ExplainerSpec) ApplyDefaults() { @@ -47,11 +46,11 @@ func (m *ExplainerSpec) ApplyDefaults() { } func (m *ExplainerSpec) Validate() error { - handler, err := makeExplainerHandler(m) + explainer, err := makeExplainer(m) if err != nil { return err } - return handler.Validate() + return explainer.Validate() } type ExplainerConfig struct { @@ -63,29 +62,25 @@ type ExplainersConfig struct { AlibiExplainer ExplainerConfig `json:"alibi,omitempty"` } -func getExplainerHandler(modelSpec *ExplainerSpec) ExplainerHandler { - handler, err := makeExplainerHandler(modelSpec) +func getExplainerHandler(modelSpec *ExplainerSpec) Explainer { + explainer, err := makeExplainer(modelSpec) if err != nil { klog.Fatal(err) } - return handler + return explainer } -func makeExplainerHandler(explainerSpec *ExplainerSpec) (ExplainerHandler, error) { - handlers := []ExplainerHandler{} +func makeExplainer(explainerSpec *ExplainerSpec) (Explainer, error) { + handlers := []Explainer{} if explainerSpec.Custom != nil { handlers = append(handlers, explainerSpec.Custom) } if explainerSpec.Alibi != nil { handlers = append(handlers, explainerSpec.Alibi) } - - if len(handlers) == 0 { - return nil, fmt.Errorf(AtLeastOneExplainerSpecViolatedError) - } if len(handlers) != 1 { - return nil, fmt.Errorf(ExactlyOneExplainerSpecViolatedError) + return nil, fmt.Errorf(ExactlyOneExplainerViolatedError) } return handlers[0], nil } diff --git a/pkg/apis/serving/v1alpha2/explainer_alibi.go b/pkg/apis/serving/v1alpha2/explainer_alibi.go index 0aa1e9790cf..08993742a8b 100644 --- a/pkg/apis/serving/v1alpha2/explainer_alibi.go +++ b/pkg/apis/serving/v1alpha2/explainer_alibi.go @@ -2,10 +2,11 @@ package v1alpha2 import ( "fmt" + "strings" + "github.com/kubeflow/kfserving/pkg/constants" "github.com/kubeflow/kfserving/pkg/utils" v1 "k8s.io/api/core/v1" - "strings" ) var ( @@ -21,15 +22,15 @@ func (s *AlibiExplainerSpec) GetStorageUri() string { return s.StorageURI } -func (s *AlibiExplainerSpec) CreateExplainerServingContainer(modelName string, predictorHost string, config *ExplainersConfig) *v1.Container { +func (s *AlibiExplainerSpec) CreateExplainerContainer(modelName string, predictorHost string, config *ExplainersConfig) *v1.Container { imageName := AlibiImageName if config.AlibiExplainer.ContainerImage != "" { imageName = config.AlibiExplainer.ContainerImage } var args = []string{ - constants.ModelServerArgsModelName, modelName, - constants.ModelServerArgsPredictorHost, predictorHost, + constants.ArgumentModelName, modelName, + constants.ArgumentPredictorHost, predictorHost, } if s.StorageURI != "" { diff --git a/pkg/apis/serving/v1alpha2/framework.go b/pkg/apis/serving/v1alpha2/framework.go deleted file mode 100644 index d97f1aeae40..00000000000 --- a/pkg/apis/serving/v1alpha2/framework.go +++ /dev/null @@ -1,147 +0,0 @@ -/* -Copyright 2019 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 v1alpha2 - -import ( - "fmt" - - "github.com/kubeflow/kfserving/pkg/constants" - v1 "k8s.io/api/core/v1" - resource "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/klog" -) - -type FrameworkHandler interface { - GetStorageUri() string - CreateModelServingContainer(modelName string, config *FrameworksConfig) *v1.Container - ApplyDefaults() - Validate() error -} - -const ( - // ExactlyOneModelSpecViolatedError is a known error message - ExactlyOneModelSpecViolatedError = "Exactly one of [Custom, ONNX, Tensorflow, TensorRT, SKLearn, XGBoost] must be specified in ModelSpec" - // AtLeastOneModelSpecViolatedError is a known error message - AtLeastOneModelSpecViolatedError = "At least one of [Custom, ONNX, Tensorflow, TensorRT, SKLearn, XGBoost] must be specified in ModelSpec" -) - -var ( - DefaultMemory = resource.MustParse("2Gi") - DefaultCPU = resource.MustParse("1") -) - -// Returns a URI to the model. This URI is passed to the storage-initializer via the StorageInitializerSourceUriInternalAnnotationKey -func (m *PredictorSpec) GetStorageUri() string { - return getHandler(m).GetStorageUri() -} - -func (m *PredictorSpec) CreateModelServingContainer(modelName string, config *FrameworksConfig) *v1.Container { - return getHandler(m).CreateModelServingContainer(modelName, config) -} - -func (m *PredictorSpec) ApplyDefaults() { - getHandler(m).ApplyDefaults() -} - -func (m *PredictorSpec) Validate() error { - handler, err := makeHandler(m) - if err != nil { - return err - } - return handler.Validate() -} - -type FrameworkConfig struct { - ContainerImage string `json:"image"` - - //TODO add readiness/liveness probe config -} -type FrameworksConfig struct { - Tensorflow FrameworkConfig `json:"tensorflow,omitempty"` - TensorRT FrameworkConfig `json:"tensorrt,omitempty"` - Xgboost FrameworkConfig `json:"xgboost,omitempty"` - SKlearn FrameworkConfig `json:"sklearn,omitempty"` - PyTorch FrameworkConfig `json:"pytorch,omitempty"` - ONNX FrameworkConfig `json:"onnx,omitempty"` -} - -func setResourceRequirementDefaults(requirements *v1.ResourceRequirements) { - if requirements.Requests == nil { - requirements.Requests = v1.ResourceList{} - } - - if _, ok := requirements.Requests[v1.ResourceCPU]; !ok { - requirements.Requests[v1.ResourceCPU] = DefaultCPU - } - if _, ok := requirements.Requests[v1.ResourceMemory]; !ok { - requirements.Requests[v1.ResourceMemory] = DefaultMemory - } - - if requirements.Limits == nil { - requirements.Limits = v1.ResourceList{} - } - - if _, ok := requirements.Limits[v1.ResourceCPU]; !ok { - requirements.Limits[v1.ResourceCPU] = DefaultCPU - } - if _, ok := requirements.Limits[v1.ResourceMemory]; !ok { - requirements.Limits[v1.ResourceMemory] = DefaultMemory - } -} - -func isGPUEnabled(requirements v1.ResourceRequirements) bool { - _, ok := requirements.Limits[constants.NvidiaGPUResourceType] - return ok -} - -func getHandler(modelSpec *PredictorSpec) FrameworkHandler { - handler, err := makeHandler(modelSpec) - if err != nil { - klog.Fatal(err) - } - - return handler -} - -func makeHandler(predictorSpec *PredictorSpec) (FrameworkHandler, error) { - handlers := []FrameworkHandler{} - if predictorSpec.Custom != nil { - handlers = append(handlers, predictorSpec.Custom) - } - if predictorSpec.XGBoost != nil { - handlers = append(handlers, predictorSpec.XGBoost) - } - if predictorSpec.SKLearn != nil { - handlers = append(handlers, predictorSpec.SKLearn) - } - if predictorSpec.Tensorflow != nil { - handlers = append(handlers, predictorSpec.Tensorflow) - } - if predictorSpec.ONNX != nil { - handlers = append(handlers, predictorSpec.ONNX) - } - if predictorSpec.PyTorch != nil { - handlers = append(handlers, predictorSpec.PyTorch) - } - if predictorSpec.TensorRT != nil { - handlers = append(handlers, predictorSpec.TensorRT) - } - if len(handlers) == 0 { - return nil, fmt.Errorf(AtLeastOneModelSpecViolatedError) - } - if len(handlers) != 1 { - return nil, fmt.Errorf(ExactlyOneModelSpecViolatedError) - } - return handlers[0], nil -} diff --git a/pkg/apis/serving/v1alpha2/framework_custom.go b/pkg/apis/serving/v1alpha2/framework_custom.go index a1b4eedc423..8f2ddcf5ac9 100644 --- a/pkg/apis/serving/v1alpha2/framework_custom.go +++ b/pkg/apis/serving/v1alpha2/framework_custom.go @@ -32,10 +32,10 @@ func (c *CustomSpec) GetStorageUri() string { return "" } -func (c *CustomSpec) CreateModelServingContainer(modelName string, config *FrameworksConfig) *v1.Container { +func (c *CustomSpec) GetContainer(modelName string, config *PredictorsConfig) *v1.Container { return &c.Container } -func (c *CustomSpec) CreateExplainerServingContainer(modelName string, predictUrl string, config *ExplainersConfig) *v1.Container { +func (c *CustomSpec) CreateExplainerContainer(modelName string, predictUrl string, config *ExplainersConfig) *v1.Container { return &c.Container } diff --git a/pkg/apis/serving/v1alpha2/framework_onnx.go b/pkg/apis/serving/v1alpha2/framework_onnx.go index 5660b4451db..e12dab43191 100644 --- a/pkg/apis/serving/v1alpha2/framework_onnx.go +++ b/pkg/apis/serving/v1alpha2/framework_onnx.go @@ -39,7 +39,7 @@ func (s *ONNXSpec) GetStorageUri() string { return s.StorageURI } -func (s *ONNXSpec) CreateModelServingContainer(modelName string, config *FrameworksConfig) *v1.Container { +func (s *ONNXSpec) GetContainer(modelName string, config *PredictorsConfig) *v1.Container { imageName := ONNXServingImageName if config.ONNX.ContainerImage != "" { imageName = config.ONNX.ContainerImage diff --git a/pkg/apis/serving/v1alpha2/framework_onnx_test.go b/pkg/apis/serving/v1alpha2/framework_onnx_test.go index 1a6b13d3bb5..c111add7d94 100644 --- a/pkg/apis/serving/v1alpha2/framework_onnx_test.go +++ b/pkg/apis/serving/v1alpha2/framework_onnx_test.go @@ -44,8 +44,8 @@ var onnxSpec = ONNXSpec{ RuntimeVersion: "someAmazingVersion", } -var onnxConfig = FrameworksConfig{ - ONNX: FrameworkConfig{ +var onnxConfig = PredictorsConfig{ + ONNX: PredictorConfig{ ContainerImage: "someOtherImage", }, } @@ -65,12 +65,12 @@ func TestCreateOnnxModelServingContainer(t *testing.T) { } // Test Create with config - container := onnxSpec.CreateModelServingContainer("someName", &onnxConfig) + container := onnxSpec.GetContainer("someName", &onnxConfig) g.Expect(container).To(gomega.Equal(expectedContainer)) // Test Create without config expectedContainer.Image = "mcr.microsoft.com/onnxruntime/server:someAmazingVersion" - emptyConfig := FrameworksConfig{ONNX: FrameworkConfig{}} - container = onnxSpec.CreateModelServingContainer("someName", &emptyConfig) + emptyConfig := PredictorsConfig{ONNX: PredictorConfig{}} + container = onnxSpec.GetContainer("someName", &emptyConfig) g.Expect(container).To(gomega.Equal(expectedContainer)) } diff --git a/pkg/apis/serving/v1alpha2/framework_scikit.go b/pkg/apis/serving/v1alpha2/framework_scikit.go index 1e91878de53..b8d0eee1653 100644 --- a/pkg/apis/serving/v1alpha2/framework_scikit.go +++ b/pkg/apis/serving/v1alpha2/framework_scikit.go @@ -33,13 +33,13 @@ var ( DefaultSKLearnRuntimeVersion = "latest" ) -var _ FrameworkHandler = (*SKLearnSpec)(nil) +var _ Predictor = (*SKLearnSpec)(nil) func (s *SKLearnSpec) GetStorageUri() string { return s.StorageURI } -func (s *SKLearnSpec) CreateModelServingContainer(modelName string, config *FrameworksConfig) *v1.Container { +func (s *SKLearnSpec) GetContainer(modelName string, config *PredictorsConfig) *v1.Container { imageName := SKLearnServerImageName if config.SKlearn.ContainerImage != "" { imageName = config.SKlearn.ContainerImage diff --git a/pkg/apis/serving/v1alpha2/framework_tensorflow.go b/pkg/apis/serving/v1alpha2/framework_tensorflow.go index 3860b561c12..cf639408903 100644 --- a/pkg/apis/serving/v1alpha2/framework_tensorflow.go +++ b/pkg/apis/serving/v1alpha2/framework_tensorflow.go @@ -49,7 +49,7 @@ func (t *TensorflowSpec) GetStorageUri() string { return t.StorageURI } -func (t *TensorflowSpec) CreateModelServingContainer(modelName string, config *FrameworksConfig) *v1.Container { +func (t *TensorflowSpec) GetContainer(modelName string, config *PredictorsConfig) *v1.Container { imageName := TensorflowServingImageName if config.Tensorflow.ContainerImage != "" { imageName = config.Tensorflow.ContainerImage diff --git a/pkg/apis/serving/v1alpha2/framework_xgboost.go b/pkg/apis/serving/v1alpha2/framework_xgboost.go index 42c2aa7761a..49cb6a4a1f4 100644 --- a/pkg/apis/serving/v1alpha2/framework_xgboost.go +++ b/pkg/apis/serving/v1alpha2/framework_xgboost.go @@ -37,7 +37,7 @@ func (x *XGBoostSpec) GetStorageUri() string { return x.StorageURI } -func (x *XGBoostSpec) CreateModelServingContainer(modelName string, config *FrameworksConfig) *v1.Container { +func (x *XGBoostSpec) GetContainer(modelName string, config *PredictorsConfig) *v1.Container { imageName := XGBoostServerImageName if config.Xgboost.ContainerImage != "" { imageName = config.Xgboost.ContainerImage diff --git a/pkg/apis/serving/v1alpha2/kfservice_framework_pytorch.go b/pkg/apis/serving/v1alpha2/kfservice_framework_pytorch.go index e7eb0173142..2dbc4f00122 100644 --- a/pkg/apis/serving/v1alpha2/kfservice_framework_pytorch.go +++ b/pkg/apis/serving/v1alpha2/kfservice_framework_pytorch.go @@ -34,13 +34,13 @@ var ( DefaultPyTorchModelClassName = "PyTorchModel" ) -var _ FrameworkHandler = (*PyTorchSpec)(nil) +var _ Predictor = (*PyTorchSpec)(nil) func (s *PyTorchSpec) GetStorageUri() string { return s.StorageURI } -func (s *PyTorchSpec) CreateModelServingContainer(modelName string, config *FrameworksConfig) *v1.Container { +func (s *PyTorchSpec) GetContainer(modelName string, config *PredictorsConfig) *v1.Container { imageName := PyTorchServerImageName if config.PyTorch.ContainerImage != "" { imageName = config.PyTorch.ContainerImage diff --git a/pkg/apis/serving/v1alpha2/kfservice_framework_tensorrt.go b/pkg/apis/serving/v1alpha2/kfservice_framework_tensorrt.go index b6a5443751d..ff114d799fb 100644 --- a/pkg/apis/serving/v1alpha2/kfservice_framework_tensorrt.go +++ b/pkg/apis/serving/v1alpha2/kfservice_framework_tensorrt.go @@ -38,7 +38,7 @@ func (t *TensorRTSpec) GetStorageUri() string { return t.StorageURI } -func (t *TensorRTSpec) CreateModelServingContainer(modelName string, config *FrameworksConfig) *v1.Container { +func (t *TensorRTSpec) GetContainer(modelName string, config *PredictorsConfig) *v1.Container { imageName := DefaultTensorRTISImageName if config.TensorRT.ContainerImage != "" { imageName = config.TensorRT.ContainerImage diff --git a/pkg/apis/serving/v1alpha2/kfservice_framework_tensorrt_test.go b/pkg/apis/serving/v1alpha2/kfservice_framework_tensorrt_test.go index c27c3bf3252..f11cd504f69 100644 --- a/pkg/apis/serving/v1alpha2/kfservice_framework_tensorrt_test.go +++ b/pkg/apis/serving/v1alpha2/kfservice_framework_tensorrt_test.go @@ -39,8 +39,8 @@ func TestCreateModelServingContainer(t *testing.T) { }, }, } - var config = FrameworksConfig{ - TensorRT: FrameworkConfig{ + var config = PredictorsConfig{ + TensorRT: PredictorConfig{ ContainerImage: "someOtherImage", }, } @@ -71,12 +71,12 @@ func TestCreateModelServingContainer(t *testing.T) { } // Test Create without config - container := spec.CreateModelServingContainer("someName", &config) + container := spec.GetContainer("someName", &config) g.Expect(container).To(gomega.Equal(expectedContainer)) // Test Create with config expectedContainer.Image = "nvcr.io/nvidia/tensorrtserver:19.05-py3" - emptyConfig := FrameworksConfig{TensorRT: FrameworkConfig{}} - container = spec.CreateModelServingContainer("someName", &emptyConfig) + emptyConfig := PredictorsConfig{TensorRT: PredictorConfig{}} + container = spec.GetContainer("someName", &emptyConfig) g.Expect(container).To(gomega.Equal(expectedContainer)) } diff --git a/pkg/apis/serving/v1alpha2/kfservice_validation.go b/pkg/apis/serving/v1alpha2/kfservice_validation.go index 2c009c7ac0e..89b7c3ef4b4 100644 --- a/pkg/apis/serving/v1alpha2/kfservice_validation.go +++ b/pkg/apis/serving/v1alpha2/kfservice_validation.go @@ -18,8 +18,6 @@ package v1alpha2 import ( "fmt" - "regexp" - "strings" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -64,12 +62,13 @@ func validateKFService(kfsvc *KFService) error { if kfsvc == nil { return fmt.Errorf("Unable to validate, KFService is nil") } - if err := validateModelSpec(&kfsvc.Spec.Default.Predictor); err != nil { - return err + endpoints := []*EndpointSpec{ + &kfsvc.Spec.Default, + kfsvc.Spec.Canary, } - if kfsvc.Spec.Canary != nil { - if err := validateModelSpec(&kfsvc.Spec.Canary.Predictor); err != nil { + for _, endpoint := range endpoints { + if err := validateEndpoint(endpoint); err != nil { return err } } @@ -80,56 +79,22 @@ func validateKFService(kfsvc *KFService) error { return nil } -func validateModelSpec(spec *PredictorSpec) error { - if spec == nil { +func validateEndpoint(endpoint *EndpointSpec) error { + if endpoint == nil { return nil } - if err := spec.Validate(); err != nil { - return err - } - if err := validateStorageURI(spec.GetStorageUri()); err != nil { + if err := endpoint.Predictor.Validate(); err != nil { return err } - if err := validateReplicas(spec.MinReplicas, spec.MaxReplicas); err != nil { - return err - } - return nil -} - -func validateStorageURI(storageURI string) error { - if storageURI == "" { - return nil - } - - // local path (not some protocol?) - if !regexp.MustCompile("\\w+?://").MatchString(storageURI) { - return nil - } - - // one of the prefixes we know? - for _, prefix := range SupportedStorageURIPrefixList { - if strings.HasPrefix(storageURI, prefix) { - return nil + if endpoint.Transformer != nil { + if err := endpoint.Transformer.Validate(); err != nil { + return err } } - - azureURIMatcher := regexp.MustCompile(AzureBlobURIRegEx) - if parts := azureURIMatcher.FindStringSubmatch(storageURI); parts != nil { - return nil - } - - return fmt.Errorf(UnsupportedStorageURIFormatError, strings.Join(SupportedStorageURIPrefixList, ", "), storageURI) -} - -func validateReplicas(minReplicas int, maxReplicas int) error { - if minReplicas < 0 { - return fmt.Errorf(MinReplicasLowerBoundExceededError) - } - if maxReplicas < 0 { - return fmt.Errorf(MaxReplicasLowerBoundExceededError) - } - if minReplicas > maxReplicas && maxReplicas != 0 { - return fmt.Errorf(MinReplicasShouldBeLessThanMaxError) + if endpoint.Explainer != nil { + if err := endpoint.Explainer.Validate(); err != nil { + return err + } } return nil } diff --git a/pkg/apis/serving/v1alpha2/kfservice_validation_test.go b/pkg/apis/serving/v1alpha2/kfservice_validation_test.go index eeadb114577..f7314dc30c2 100644 --- a/pkg/apis/serving/v1alpha2/kfservice_validation_test.go +++ b/pkg/apis/serving/v1alpha2/kfservice_validation_test.go @@ -110,14 +110,14 @@ func TestRejectMultipleModelSpecs(t *testing.T) { g := gomega.NewGomegaWithT(t) kfsvc := makeTestKFService() kfsvc.Spec.Default.Predictor.Custom = &CustomSpec{Container: v1.Container{}} - g.Expect(kfsvc.ValidateCreate()).Should(gomega.MatchError(ExactlyOneModelSpecViolatedError)) + g.Expect(kfsvc.ValidateCreate()).Should(gomega.MatchError(ExactlyOnePredictorViolatedError)) } func TestRejectModelSpecMissing(t *testing.T) { g := gomega.NewGomegaWithT(t) kfsvc := makeTestKFService() kfsvc.Spec.Default.Predictor.Tensorflow = nil - g.Expect(kfsvc.ValidateCreate()).Should(gomega.MatchError(AtLeastOneModelSpecViolatedError)) + g.Expect(kfsvc.ValidateCreate()).Should(gomega.MatchError(ExactlyOnePredictorViolatedError)) } func TestRejectMultipleCanaryModelSpecs(t *testing.T) { g := gomega.NewGomegaWithT(t) @@ -128,7 +128,7 @@ func TestRejectMultipleCanaryModelSpecs(t *testing.T) { Tensorflow: kfsvc.Spec.Default.Predictor.Tensorflow, }, } - g.Expect(kfsvc.ValidateCreate()).Should(gomega.MatchError(ExactlyOneModelSpecViolatedError)) + g.Expect(kfsvc.ValidateCreate()).Should(gomega.MatchError(ExactlyOnePredictorViolatedError)) } func TestRejectCanaryModelSpecMissing(t *testing.T) { @@ -137,7 +137,7 @@ func TestRejectCanaryModelSpecMissing(t *testing.T) { kfsvc.Spec.Canary = &EndpointSpec{ Predictor: PredictorSpec{}, } - g.Expect(kfsvc.ValidateCreate()).Should(gomega.MatchError(AtLeastOneModelSpecViolatedError)) + g.Expect(kfsvc.ValidateCreate()).Should(gomega.MatchError(ExactlyOnePredictorViolatedError)) } func TestRejectBadCanaryTrafficValues(t *testing.T) { g := gomega.NewGomegaWithT(t) @@ -197,3 +197,17 @@ func TestCustomOK(t *testing.T) { fmt.Println(err) g.Expect(kfsvc.ValidateCreate()).Should(gomega.Succeed()) } + +func TestRejectBadTransformer(t *testing.T) { + g := gomega.NewGomegaWithT(t) + kfsvc := makeTestKFService() + kfsvc.Spec.Default.Transformer = &TransformerSpec{} + g.Expect(kfsvc.ValidateCreate()).Should(gomega.MatchError(ExactlyOneTransformerViolatedError)) +} + +func TestRejectBadExplainer(t *testing.T) { + g := gomega.NewGomegaWithT(t) + kfsvc := makeTestKFService() + kfsvc.Spec.Default.Explainer = &ExplainerSpec{} + g.Expect(kfsvc.ValidateCreate()).Should(gomega.MatchError(ExactlyOneExplainerViolatedError)) +} diff --git a/pkg/apis/serving/v1alpha2/openapi_generated.go b/pkg/apis/serving/v1alpha2/openapi_generated.go index 6ac1f5ecc9c..1316e28dd67 100644 --- a/pkg/apis/serving/v1alpha2/openapi_generated.go +++ b/pkg/apis/serving/v1alpha2/openapi_generated.go @@ -233,62 +233,6 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA Dependencies: []string{ "github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.ExplainerConfig"}, }, - "github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.FrameworkConfig": { - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Properties: map[string]spec.Schema{ - "image": { - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - }, - Required: []string{"image"}, - }, - }, - Dependencies: []string{}, - }, - "github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.FrameworksConfig": { - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Properties: map[string]spec.Schema{ - "tensorflow": { - SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.FrameworkConfig"), - }, - }, - "tensorrt": { - SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.FrameworkConfig"), - }, - }, - "xgboost": { - SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.FrameworkConfig"), - }, - }, - "sklearn": { - SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.FrameworkConfig"), - }, - }, - "pytorch": { - SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.FrameworkConfig"), - }, - }, - "onnx": { - SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.FrameworkConfig"), - }, - }, - }, - }, - }, - Dependencies: []string{ - "github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.FrameworkConfig"}, - }, "github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.KFService": { Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -502,6 +446,22 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA Dependencies: []string{ "k8s.io/api/core/v1.ResourceRequirements"}, }, + "github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.PredictorConfig": { + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Properties: map[string]spec.Schema{ + "image": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"image"}, + }, + }, + Dependencies: []string{}, + }, "github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.PredictorSpec": { Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -570,6 +530,46 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA Dependencies: []string{ "github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.CustomSpec", "github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.ONNXSpec", "github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.PyTorchSpec", "github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.SKLearnSpec", "github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.TensorRTSpec", "github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.TensorflowSpec", "github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.XGBoostSpec"}, }, + "github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.PredictorsConfig": { + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Properties: map[string]spec.Schema{ + "tensorflow": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.PredictorConfig"), + }, + }, + "tensorrt": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.PredictorConfig"), + }, + }, + "xgboost": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.PredictorConfig"), + }, + }, + "sklearn": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.PredictorConfig"), + }, + }, + "pytorch": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.PredictorConfig"), + }, + }, + "onnx": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.PredictorConfig"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.PredictorConfig"}, + }, "github.com/kubeflow/kfserving/pkg/apis/serving/v1alpha2.PyTorchSpec": { Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ diff --git a/pkg/apis/serving/v1alpha2/predictor.go b/pkg/apis/serving/v1alpha2/predictor.go new file mode 100644 index 00000000000..5c7b845d846 --- /dev/null +++ b/pkg/apis/serving/v1alpha2/predictor.go @@ -0,0 +1,192 @@ +/* +Copyright 2019 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 v1alpha2 + +import ( + "fmt" + "regexp" + "strings" + + "github.com/kubeflow/kfserving/pkg/constants" + v1 "k8s.io/api/core/v1" + resource "k8s.io/apimachinery/pkg/api/resource" +) + +type Predictor interface { + GetStorageUri() string + GetContainer(modelName string, config *PredictorsConfig) *v1.Container + ApplyDefaults() + Validate() error +} + +const ( + // ExactlyOnePredictorViolatedError is a known error message + ExactlyOnePredictorViolatedError = "Exactly one of [Custom, ONNX, Tensorflow, TensorRT, SKLearn, XGBoost] must be specified in PredictorSpec" +) + +var ( + DefaultMemory = resource.MustParse("2Gi") + DefaultCPU = resource.MustParse("1") +) + +// Returns a URI to the model. This URI is passed to the storage-initializer via the StorageInitializerSourceUriInternalAnnotationKey +func (p *PredictorSpec) GetStorageUri() string { + predictor, err := getPredictor(p) + if err != nil { + return "" + } + return predictor.GetStorageUri() +} + +func (p *PredictorSpec) GetContainer(modelName string, config *PredictorsConfig) *v1.Container { + predictor, err := getPredictor(p) + if err != nil { + return nil + } + return predictor.GetContainer(modelName, config) +} + +func (p *PredictorSpec) ApplyDefaults() { + predictor, err := getPredictor(p) + if err == nil { + predictor.ApplyDefaults() + } +} + +func (p *PredictorSpec) Validate() error { + predictor, err := getPredictor(p) + if err != nil { + return err + } + if err := predictor.Validate(); err != nil { + return err + } + if err := validateStorageURI(p.GetStorageUri()); err != nil { + return err + } + if err := validateReplicas(p.MinReplicas, p.MaxReplicas); err != nil { + return err + } + return nil +} + +type PredictorConfig struct { + ContainerImage string `json:"image"` + + //TODO add readiness/liveness probe config +} +type PredictorsConfig struct { + Tensorflow PredictorConfig `json:"tensorflow,omitempty"` + TensorRT PredictorConfig `json:"tensorrt,omitempty"` + Xgboost PredictorConfig `json:"xgboost,omitempty"` + SKlearn PredictorConfig `json:"sklearn,omitempty"` + PyTorch PredictorConfig `json:"pytorch,omitempty"` + ONNX PredictorConfig `json:"onnx,omitempty"` +} + +func validateStorageURI(storageURI string) error { + if storageURI == "" { + return nil + } + + // local path (not some protocol?) + if !regexp.MustCompile("\\w+?://").MatchString(storageURI) { + return nil + } + + // one of the prefixes we know? + for _, prefix := range SupportedStorageURIPrefixList { + if strings.HasPrefix(storageURI, prefix) { + return nil + } + } + + azureURIMatcher := regexp.MustCompile(AzureBlobURIRegEx) + if parts := azureURIMatcher.FindStringSubmatch(storageURI); parts != nil { + return nil + } + + return fmt.Errorf(UnsupportedStorageURIFormatError, strings.Join(SupportedStorageURIPrefixList, ", "), storageURI) +} + +func validateReplicas(minReplicas int, maxReplicas int) error { + if minReplicas < 0 { + return fmt.Errorf(MinReplicasLowerBoundExceededError) + } + if maxReplicas < 0 { + return fmt.Errorf(MaxReplicasLowerBoundExceededError) + } + if minReplicas > maxReplicas && maxReplicas != 0 { + return fmt.Errorf(MinReplicasShouldBeLessThanMaxError) + } + return nil +} + +func setResourceRequirementDefaults(requirements *v1.ResourceRequirements) { + if requirements.Requests == nil { + requirements.Requests = v1.ResourceList{} + } + + if _, ok := requirements.Requests[v1.ResourceCPU]; !ok { + requirements.Requests[v1.ResourceCPU] = DefaultCPU + } + if _, ok := requirements.Requests[v1.ResourceMemory]; !ok { + requirements.Requests[v1.ResourceMemory] = DefaultMemory + } + + if requirements.Limits == nil { + requirements.Limits = v1.ResourceList{} + } + + if _, ok := requirements.Limits[v1.ResourceCPU]; !ok { + requirements.Limits[v1.ResourceCPU] = DefaultCPU + } + if _, ok := requirements.Limits[v1.ResourceMemory]; !ok { + requirements.Limits[v1.ResourceMemory] = DefaultMemory + } +} + +func isGPUEnabled(requirements v1.ResourceRequirements) bool { + _, ok := requirements.Limits[constants.NvidiaGPUResourceType] + return ok +} + +func getPredictor(predictorSpec *PredictorSpec) (Predictor, error) { + predictors := []Predictor{} + if predictorSpec.Custom != nil { + predictors = append(predictors, predictorSpec.Custom) + } + if predictorSpec.XGBoost != nil { + predictors = append(predictors, predictorSpec.XGBoost) + } + if predictorSpec.SKLearn != nil { + predictors = append(predictors, predictorSpec.SKLearn) + } + if predictorSpec.Tensorflow != nil { + predictors = append(predictors, predictorSpec.Tensorflow) + } + if predictorSpec.ONNX != nil { + predictors = append(predictors, predictorSpec.ONNX) + } + if predictorSpec.PyTorch != nil { + predictors = append(predictors, predictorSpec.PyTorch) + } + if predictorSpec.TensorRT != nil { + predictors = append(predictors, predictorSpec.TensorRT) + } + if len(predictors) != 1 { + return nil, fmt.Errorf(ExactlyOnePredictorViolatedError) + } + return predictors[0], nil +} diff --git a/pkg/apis/serving/v1alpha2/swagger.json b/pkg/apis/serving/v1alpha2/swagger.json index 151679d1a20..8cba73b236d 100644 --- a/pkg/apis/serving/v1alpha2/swagger.json +++ b/pkg/apis/serving/v1alpha2/swagger.json @@ -174,38 +174,6 @@ } } }, - "v1alpha2.FrameworkConfig": { - "required": [ - "image" - ], - "properties": { - "image": { - "type": "string" - } - } - }, - "v1alpha2.FrameworksConfig": { - "properties": { - "onnx": { - "$ref": "#/definitions/v1alpha2.FrameworkConfig" - }, - "pytorch": { - "$ref": "#/definitions/v1alpha2.FrameworkConfig" - }, - "sklearn": { - "$ref": "#/definitions/v1alpha2.FrameworkConfig" - }, - "tensorflow": { - "$ref": "#/definitions/v1alpha2.FrameworkConfig" - }, - "tensorrt": { - "$ref": "#/definitions/v1alpha2.FrameworkConfig" - }, - "xgboost": { - "$ref": "#/definitions/v1alpha2.FrameworkConfig" - } - } - }, "v1alpha2.KFService": { "description": "KFService is the Schema for the services API", "properties": { @@ -328,6 +296,16 @@ } } }, + "v1alpha2.PredictorConfig": { + "required": [ + "image" + ], + "properties": { + "image": { + "type": "string" + } + } + }, "v1alpha2.PredictorSpec": { "description": "PredictorSpec defines the configuration to route traffic to a predictor.", "properties": { @@ -369,6 +347,28 @@ } } }, + "v1alpha2.PredictorsConfig": { + "properties": { + "onnx": { + "$ref": "#/definitions/v1alpha2.PredictorConfig" + }, + "pytorch": { + "$ref": "#/definitions/v1alpha2.PredictorConfig" + }, + "sklearn": { + "$ref": "#/definitions/v1alpha2.PredictorConfig" + }, + "tensorflow": { + "$ref": "#/definitions/v1alpha2.PredictorConfig" + }, + "tensorrt": { + "$ref": "#/definitions/v1alpha2.PredictorConfig" + }, + "xgboost": { + "$ref": "#/definitions/v1alpha2.PredictorConfig" + } + } + }, "v1alpha2.PyTorchSpec": { "description": "PyTorchSpec defines arguments for configuring PyTorch model serving.", "required": [ diff --git a/pkg/apis/serving/v1alpha2/transformer.go b/pkg/apis/serving/v1alpha2/transformer.go new file mode 100644 index 00000000000..5a018d57cb7 --- /dev/null +++ b/pkg/apis/serving/v1alpha2/transformer.go @@ -0,0 +1,69 @@ +package v1alpha2 + +import ( + "fmt" + + "github.com/kubeflow/kfserving/pkg/constants" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/klog" +) + +// Constants +const ( + ExactlyOneTransformerViolatedError = "Exactly one of [Custom, Feast] must be specified in TransformerSpec" +) + +// Transformer interface is implemented by all Transformers +type Transformer interface { + GetContainerSpec() *v1.Container + ApplyDefaults() + Validate() error +} + +// GetContainerSpec for the transformer +func (t *TransformerSpec) GetContainerSpec(metadata metav1.ObjectMeta, isCanary bool) *v1.Container { + transformer, err := getTransformer(t) + if err != nil { + return &v1.Container{} + } + container := transformer.GetContainerSpec().DeepCopy() + container.Args = append(container.Args, []string{ + constants.ArgumentModelName, + metadata.Name, + constants.ArgumentPredictorHost, + constants.PredictorURL(metadata, isCanary), + }...) + return container +} + +// ApplyDefaults to the TransformerSpec +func (t *TransformerSpec) ApplyDefaults() { + transformer, err := getTransformer(t) + if err == nil { + transformer.ApplyDefaults() + } +} + +// Validate the TransformerSpec +func (t *TransformerSpec) Validate() error { + transformer, err := getTransformer(t) + if err != nil { + return err + } + return transformer.Validate() +} + +func getTransformer(t *TransformerSpec) (Transformer, error) { + transformers := []Transformer{} + if t.Custom != nil { + transformers = append(transformers, t.Custom) + } + // Fail if not exactly one + if len(transformers) != 1 { + err := fmt.Errorf(ExactlyOneTransformerViolatedError) + klog.Error(err) + return nil, err + } + return transformers[0], nil +} diff --git a/pkg/apis/serving/v1alpha2/transformer_custom.go b/pkg/apis/serving/v1alpha2/transformer_custom.go new file mode 100644 index 00000000000..0afb06994b5 --- /dev/null +++ b/pkg/apis/serving/v1alpha2/transformer_custom.go @@ -0,0 +1,12 @@ +package v1alpha2 + +import ( + v1 "k8s.io/api/core/v1" +) + +var _ Transformer = (*CustomSpec)(nil) + +// GetContainerSpec for the CustomSpec +func (c *CustomSpec) GetContainerSpec() *v1.Container { + return &c.Container +} diff --git a/pkg/apis/serving/v1alpha2/zz_generated.deepcopy.go b/pkg/apis/serving/v1alpha2/zz_generated.deepcopy.go index 7732f8def5f..adcde0c6b79 100644 --- a/pkg/apis/serving/v1alpha2/zz_generated.deepcopy.go +++ b/pkg/apis/serving/v1alpha2/zz_generated.deepcopy.go @@ -199,44 +199,6 @@ func (in *ExplainersConfig) DeepCopy() *ExplainersConfig { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FrameworkConfig) DeepCopyInto(out *FrameworkConfig) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FrameworkConfig. -func (in *FrameworkConfig) DeepCopy() *FrameworkConfig { - if in == nil { - return nil - } - out := new(FrameworkConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FrameworksConfig) DeepCopyInto(out *FrameworksConfig) { - *out = *in - out.Tensorflow = in.Tensorflow - out.TensorRT = in.TensorRT - out.Xgboost = in.Xgboost - out.SKlearn = in.SKlearn - out.PyTorch = in.PyTorch - out.ONNX = in.ONNX - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FrameworksConfig. -func (in *FrameworksConfig) DeepCopy() *FrameworksConfig { - if in == nil { - return nil - } - out := new(FrameworksConfig) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KFService) DeepCopyInto(out *KFService) { *out = *in @@ -392,6 +354,22 @@ func (in *ONNXSpec) DeepCopy() *ONNXSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PredictorConfig) DeepCopyInto(out *PredictorConfig) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PredictorConfig. +func (in *PredictorConfig) DeepCopy() *PredictorConfig { + if in == nil { + return nil + } + out := new(PredictorConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PredictorSpec) DeepCopyInto(out *PredictorSpec) { *out = *in @@ -444,6 +422,28 @@ func (in *PredictorSpec) DeepCopy() *PredictorSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PredictorsConfig) DeepCopyInto(out *PredictorsConfig) { + *out = *in + out.Tensorflow = in.Tensorflow + out.TensorRT = in.TensorRT + out.Xgboost = in.Xgboost + out.SKlearn = in.SKlearn + out.PyTorch = in.PyTorch + out.ONNX = in.ONNX + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PredictorsConfig. +func (in *PredictorsConfig) DeepCopy() *PredictorsConfig { + if in == nil { + return nil + } + out := new(PredictorsConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PyTorchSpec) DeepCopyInto(out *PyTorchSpec) { *out = *in diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go index 624b5a63e1c..7647cced8cc 100644 --- a/pkg/constants/constants.go +++ b/pkg/constants/constants.go @@ -17,10 +17,12 @@ limitations under the License. package constants import ( + "fmt" "os" "strings" "k8s.io/api/admissionregistration/v1beta1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // KFServing Constants @@ -107,8 +109,8 @@ const ( // KFService model server args const ( - ModelServerArgsModelName = "--model_name" - ModelServerArgsPredictorHost = "--predictor_host" + ArgumentModelName = "--model_name" + ArgumentPredictorHost = "--predictor_host" ) func (e KFServiceEndpoint) String() string { @@ -169,3 +171,10 @@ func CanaryServiceName(name string, endpoint KFServiceEndpoint) string { func RouteName(name string, verb KFServiceVerb) string { return name + "-" + verb.String() } + +func PredictorURL(metadata v1.ObjectMeta, isCanary bool) string { + if isCanary { + return fmt.Sprintf("http://%s.%s.svc.cluster.local", CanaryPredictorServiceName(metadata.Name), metadata.Namespace) + } + return fmt.Sprintf("http://%s.%s.svc.cluster.local", DefaultPredictorServiceName(metadata.Name), metadata.Namespace) +} diff --git a/pkg/controller/kfservice/kfservice_controller_test.go b/pkg/controller/kfservice/kfservice_controller_test.go index 17d94f4fe42..0046b1e5e50 100644 --- a/pkg/controller/kfservice/kfservice_controller_test.go +++ b/pkg/controller/kfservice/kfservice_controller_test.go @@ -47,7 +47,7 @@ var c client.Client const timeout = time.Second * 10 var configs = map[string]string{ - "frameworks": `{ + "predictors": `{ "tensorflow" : { "image" : "tensorflow/serving" }, diff --git a/pkg/controller/kfservice/resources/knative/service.go b/pkg/controller/kfservice/resources/knative/service.go index 174412f6675..5618b91a094 100644 --- a/pkg/controller/kfservice/resources/knative/service.go +++ b/pkg/controller/kfservice/resources/knative/service.go @@ -34,7 +34,7 @@ import ( ) const ( - FrameworkConfigKeyName = "frameworks" + PredictorConfigKeyName = "predictors" ExplainerConfigKeyName = "explainers" ) @@ -46,15 +46,15 @@ var serviceAnnotationDisallowedList = []string{ } type ServiceBuilder struct { - frameworksConfig *v1alpha2.FrameworksConfig + frameworksConfig *v1alpha2.PredictorsConfig credentialBuilder *credentials.CredentialBuilder explainersConfig *v1alpha2.ExplainersConfig } func NewServiceBuilder(client client.Client, config *v1.ConfigMap) *ServiceBuilder { - frameworkConfig := &v1alpha2.FrameworksConfig{} + frameworkConfig := &v1alpha2.PredictorsConfig{} explainerConfig := &v1alpha2.ExplainersConfig{} - if fmks, ok := config.Data[FrameworkConfigKeyName]; ok { + if fmks, ok := config.Data[PredictorConfigKeyName]; ok { err := json.Unmarshal([]byte(fmks), &frameworkConfig) if err != nil { panic(fmt.Errorf("Unable to unmarshall framework json string due to %v ", err)) @@ -166,7 +166,7 @@ func (c *ServiceBuilder) CreatePredictorService(name string, metadata metav1.Obj PodSpec: v1.PodSpec{ ServiceAccountName: predictorSpec.ServiceAccountName, Containers: []v1.Container{ - *predictorSpec.CreateModelServingContainer(metadata.Name, c.frameworksConfig), + *predictorSpec.GetContainer(metadata.Name, c.frameworksConfig), }, }, }, @@ -216,9 +216,9 @@ func (c *ServiceBuilder) CreateTransformerService(name string, metadata metav1.O } container := transformerSpec.Custom.Container predefinedArgs := []string{ - constants.ModelServerArgsModelName, + constants.ArgumentModelName, metadata.Name, - constants.ModelServerArgsPredictorHost, + constants.ArgumentPredictorHost, predictorHostName, } container.Args = append(container.Args, predefinedArgs...) @@ -317,7 +317,7 @@ func (c *ServiceBuilder) CreateExplainerService(name string, metadata metav1.Obj PodSpec: v1.PodSpec{ ServiceAccountName: explainerSpec.ServiceAccountName, Containers: []v1.Container{ - *explainerSpec.CreateExplainerServingContainer(metadata.Name, predictorService, c.explainersConfig), + *explainerSpec.CreateExplainerContainer(metadata.Name, predictorService, c.explainersConfig), }, }, }, diff --git a/pkg/controller/kfservice/resources/knative/service_test.go b/pkg/controller/kfservice/resources/knative/service_test.go index d6640fbb459..79bf27afd28 100644 --- a/pkg/controller/kfservice/resources/knative/service_test.go +++ b/pkg/controller/kfservice/resources/knative/service_test.go @@ -55,7 +55,7 @@ var kfsvc = v1alpha2.KFService{ } var configMapData = map[string]string{ - "frameworks": `{ + "predictors": `{ "tensorflow" : { "image" : "tensorflow/tfserving" }, @@ -571,9 +571,9 @@ func TestTransformerToKnativeService(t *testing.T) { { Image: "transformer:latest", Args: []string{ - constants.ModelServerArgsModelName, + constants.ArgumentModelName, kfsvc.Name, - constants.ModelServerArgsPredictorHost, + constants.ArgumentPredictorHost, constants.DefaultPredictorServiceName(kfsvc.Name) + "." + kfsvc.Namespace, }, }, @@ -612,9 +612,9 @@ func TestTransformerToKnativeService(t *testing.T) { { Image: "transformer:v2", Args: []string{ - constants.ModelServerArgsModelName, + constants.ArgumentModelName, kfsvc.Name, - constants.ModelServerArgsPredictorHost, + constants.ArgumentPredictorHost, constants.CanaryPredictorServiceName(kfsvc.Name) + "." + kfsvc.Namespace, }, }, @@ -752,9 +752,9 @@ func TestExplainerToKnativeService(t *testing.T) { { Image: "alibi:latest", Args: []string{ - constants.ModelServerArgsModelName, + constants.ArgumentModelName, kfsvc.Name, - constants.ModelServerArgsPredictorHost, + constants.ArgumentPredictorHost, constants.DefaultPredictorServiceName(kfsvc.Name) + "." + kfsvc.Namespace, string(v1alpha2.AlibiAnchorsTabularExplainer), }, @@ -791,9 +791,9 @@ func TestExplainerToKnativeService(t *testing.T) { { Image: "alibi:latest", Args: []string{ - constants.ModelServerArgsModelName, + constants.ArgumentModelName, kfsvc.Name, - constants.ModelServerArgsPredictorHost, + constants.ArgumentPredictorHost, constants.CanaryPredictorServiceName(kfsvc.Name) + "." + kfsvc.Namespace, string(v1alpha2.AlibiAnchorsTabularExplainer), },