From 17c655e5e9871c8b2f12d8e605a2e04304d77a43 Mon Sep 17 00:00:00 2001 From: Youhwan Seol Date: Wed, 8 Feb 2023 19:19:39 +0900 Subject: [PATCH] Implementation of IPS deploy logic Change-Id: Ifc6ffa865b91913750331a0f2ce2bab10272a147 Signed-off-by: Youhwan Seol --- go.mod | 6 +- pkg/api/v1/deployment/deployment.go | 2 +- pkg/client/kserve/client.go | 20 ++++++ pkg/client/kserve/utils.go | 120 ++++++++++++++++++++++++++++++++ pkg/commons/types/values.go | 36 ++++++++++ pkg/controller/v1/adapter/controller.go | 32 +++++++-- pkg/controller/v1/adapter/utils.go | 77 ++++++++++++++++++++ 7 files changed, 284 insertions(+), 9 deletions(-) create mode 100644 pkg/client/kserve/utils.go create mode 100644 pkg/commons/types/values.go create mode 100644 pkg/controller/v1/adapter/utils.go diff --git a/go.mod b/go.mod index 584d2b3..c01b951 100755 --- a/go.mod +++ b/go.mod @@ -8,13 +8,15 @@ require ( github.com/kserve/kserve v0.7.0 github.com/pkg/errors v0.9.1 // indirect github.com/stretchr/testify v1.8.1 + gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 + k8s.io/api v0.20.2 + k8s.io/apimachinery v0.20.2 k8s.io/client-go v11.0.1-0.20190805182717-6502b5e7b1b5+incompatible ) require ( github.com/spf13/viper v1.7.0 github.com/xeipuuv/gojsonschema v1.2.0 - gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 ) require ( @@ -88,9 +90,7 @@ require ( gopkg.in/ini.v1 v1.56.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.20.2 // indirect k8s.io/apiextensions-apiserver v0.20.2 // indirect - k8s.io/apimachinery v0.20.2 // indirect k8s.io/klog v1.0.0 // indirect k8s.io/klog/v2 v2.4.0 // indirect k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6 // indirect diff --git a/pkg/api/v1/deployment/deployment.go b/pkg/api/v1/deployment/deployment.go index df1d85d..68ff9a2 100644 --- a/pkg/api/v1/deployment/deployment.go +++ b/pkg/api/v1/deployment/deployment.go @@ -60,7 +60,7 @@ func (Executor) Deploy(c *gin.Context) { return } - err := ipsAdapter.Deploy(name, version) + _, err := ipsAdapter.Deploy(name, version) if err != nil { utils.WriteError(c.Writer, err) return diff --git a/pkg/client/kserve/client.go b/pkg/client/kserve/client.go index 0993e5c..4d52d9f 100644 --- a/pkg/client/kserve/client.go +++ b/pkg/client/kserve/client.go @@ -27,6 +27,7 @@ import ( "gerrit.o-ran-sc.org/r/aiml-fw/aihp/ips/kserve-adapter/pkg/commons/errors" "gerrit.o-ran-sc.org/r/aiml-fw/aihp/ips/kserve-adapter/pkg/commons/logger" + "gerrit.o-ran-sc.org/r/aiml-fw/aihp/ips/kserve-adapter/pkg/commons/types" ) const ( @@ -37,6 +38,7 @@ var ifsvGetter func(string) (client_v1beta1.InferenceServiceInterface, error) type Command interface { Init(kubeconfigPath string) error + Create(values types.Values) (string, error) } type Client struct { @@ -82,3 +84,21 @@ func (c *Client) Init(kubeconfigPath string) (err error) { } return } + +func (c *Client) Create(values types.Values) (revision string, err error) { + logger.Logging(logger.DEBUG, "IN") + defer logger.Logging(logger.DEBUG, "OUT") + + info := convertValuesToInferenceService(values) + if err != nil { + return + } + + _, err = c.api.Create(&info) + if err != nil { + logger.Logging(logger.ERROR, err.Error()) + err = errors.InternalServerError{Message: err.Error()} + return + } + return +} diff --git a/pkg/client/kserve/utils.go b/pkg/client/kserve/utils.go new file mode 100644 index 0000000..81fcd93 --- /dev/null +++ b/pkg/client/kserve/utils.go @@ -0,0 +1,120 @@ +/* +================================================================================== + +Copyright (c) 2023 Samsung Electronics Co., Ltd. All Rights Reserved. + +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 kserve + +import ( + "strconv" + + api_v1beta1 "github.com/kserve/kserve/pkg/apis/serving/v1beta1" + core_v1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "gerrit.o-ran-sc.org/r/aiml-fw/aihp/ips/kserve-adapter/pkg/commons/logger" + "gerrit.o-ran-sc.org/r/aiml-fw/aihp/ips/kserve-adapter/pkg/commons/types" +) + +func convertValuesToInferenceService(values types.Values) (ifsv api_v1beta1.InferenceService) { + logger.Logging(logger.DEBUG, "IN") + defer logger.Logging(logger.DEBUG, "OUT") + + maxReplicas, err := strconv.Atoi(values.MAXReplicas) + if err != nil { + logger.Logging(logger.ERROR, err.Error()) + maxReplicas = 1 + } + + minReplicas, err := strconv.Atoi(values.MINReplicas) + if err != nil { + logger.Logging(logger.ERROR, err.Error()) + minReplicas = 1 + } + + ifsv = api_v1beta1.InferenceService{ + TypeMeta: v1.TypeMeta{ + Kind: "InferenceService", + APIVersion: "serving.kserve.io/v1beta1", + }, + ObjectMeta: v1.ObjectMeta{ + Name: values.FullName, + Namespace: ips_namespace, + Labels: map[string]string{ + "controller-tools.k8s.io": "1.0", + "app": values.FullName, + }, + }, + Spec: api_v1beta1.InferenceServiceSpec{ + Predictor: api_v1beta1.PredictorSpec{ + ComponentExtensionSpec: api_v1beta1.ComponentExtensionSpec{ + MaxReplicas: maxReplicas, + MinReplicas: &minReplicas, + }, + PodSpec: api_v1beta1.PodSpec{ + ServiceAccountName: values.RICServiceAccountName, + }, + }, + }, + Status: api_v1beta1.InferenceServiceStatus{}, + } + + switch values.Engine { + case "tensorflow": + ifsv.Spec.Predictor.Tensorflow = &api_v1beta1.TFServingSpec{ + PredictorExtensionSpec: api_v1beta1.PredictorExtensionSpec{ + StorageURI: &values.StorageURI, + Container: core_v1.Container{ + Image: values.Image, + Ports: []core_v1.ContainerPort{ + { + Name: "h2c", + ContainerPort: 9000, + Protocol: "TCP", + }, + }, + }, + }, + } + case "sklearn": + ifsv.Spec.Predictor.SKLearn = &api_v1beta1.SKLearnSpec{ + PredictorExtensionSpec: api_v1beta1.PredictorExtensionSpec{ + StorageURI: &values.StorageURI, + Container: core_v1.Container{ + Image: values.Image, + Ports: []core_v1.ContainerPort{ + { + Name: "h2c", + ContainerPort: 9000, + Protocol: "TCP", + }, + }, + }, + }, + } + } + + if values.CanaryTrafficPercent >= 0 { + ifsv.Spec.Predictor.CanaryTrafficPercent = &values.CanaryTrafficPercent + } + + if values.ResourceVersion != "" { + ifsv.ResourceVersion = values.ResourceVersion + } + + return +} diff --git a/pkg/commons/types/values.go b/pkg/commons/types/values.go new file mode 100644 index 0000000..f8a9614 --- /dev/null +++ b/pkg/commons/types/values.go @@ -0,0 +1,36 @@ +/* +================================================================================== + +Copyright (c) 2023 Samsung Electronics Co., Ltd. All Rights Reserved. + +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 types + +type Values struct { + APIVersion string `yaml:"api_version"` + Engine string `yaml:"engine"` + Name string `yaml:"name"` + FullName string `yaml:"fullname"` + ImagePullPolicy string `yaml:"image_pull_policy"` + MAXReplicas string `yaml:"max_replicas"` + MINReplicas string `yaml:"min_replicas"` + Resources string `yaml:"resources"` + RICServiceAccountName string `yaml:"ric_serviceaccount_name"` + StorageURI string `yaml:"storageUri"` + Image string `yaml:"image"` + ResourceVersion string + CanaryTrafficPercent int64 +} diff --git a/pkg/controller/v1/adapter/controller.go b/pkg/controller/v1/adapter/controller.go index 6506f68..1918763 100644 --- a/pkg/controller/v1/adapter/controller.go +++ b/pkg/controller/v1/adapter/controller.go @@ -28,7 +28,7 @@ import ( ) type Command interface { - Deploy(name string, version string) error + Deploy(name string, version string) (string, error) } type Executor struct { @@ -38,22 +38,44 @@ type Executor struct { var kserveClient kserve.Command var onboardClient onboard.Command +var removeFunc func(string) error + func init() { kserveClient = &kserve.Client{} - onboardClient = onboard.Executor{} kubeconfigPath := os.Getenv("KUBECONFIG") err := kserveClient.Init(kubeconfigPath) if err != nil { os.Exit(8) } + + onboardClient = onboard.Executor{} + + removeFunc = func(path string) (err error) { + err = os.RemoveAll(path) + return + } } -func (Executor) Deploy(name string, version string) error { +func (Executor) Deploy(name string, version string) (revision string, err error) { logger.Logging(logger.DEBUG, "IN") defer logger.Logging(logger.DEBUG, "OUT") - // TODO: Get object from onboard & Deploy using kserveClient + path, err := onboardClient.Download(name, version) + if err != nil { + return + } + defer removeFunc(path) + + values, err := valueParse(path) + if err != nil { + logger.Logging(logger.ERROR, err.Error()) + return + } - return nil + revision, err = kserveClient.Create(values) + if err != nil { + return + } + return } diff --git a/pkg/controller/v1/adapter/utils.go b/pkg/controller/v1/adapter/utils.go new file mode 100644 index 0000000..52605c6 --- /dev/null +++ b/pkg/controller/v1/adapter/utils.go @@ -0,0 +1,77 @@ +/* +================================================================================== + +Copyright (c) 2023 Samsung Electronics Co., Ltd. All Rights Reserved. + +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 adapter + +import ( + "os" + "path/filepath" + + "gopkg.in/yaml.v1" + + "gerrit.o-ran-sc.org/r/aiml-fw/aihp/ips/kserve-adapter/pkg/commons/errors" + "gerrit.o-ran-sc.org/r/aiml-fw/aihp/ips/kserve-adapter/pkg/commons/logger" + "gerrit.o-ran-sc.org/r/aiml-fw/aihp/ips/kserve-adapter/pkg/commons/types" +) + +func valueParse(path string) (values types.Values, err error) { + logger.Logging(logger.DEBUG, "IN") + defer logger.Logging(logger.DEBUG, "OUT") + + filePath, err := getValuesFilePath(path) + if err != nil { + return + } + + b, err := os.ReadFile(filePath) + if err != nil { + logger.Logging(logger.ERROR, err.Error()) + err = errors.IOError{Message: err.Error()} + return + } + + err = yaml.Unmarshal(b, &values) + if err != nil { + logger.Logging(logger.ERROR, err.Error()) + err = errors.IOError{Message: err.Error()} + return + } + values.CanaryTrafficPercent = -1 + + return +} + +func getValuesFilePath(root string) (path string, err error) { + logger.Logging(logger.DEBUG, "IN") + defer logger.Logging(logger.DEBUG, "OUT") + + files, err := os.ReadDir(root) + if err != nil { + logger.Logging(logger.ERROR, err.Error()) + return "", errors.IOError{Message: err.Error()} + } + + for _, file := range files { + if file.IsDir() { + path = filepath.Join(root, file.Name()) + } + } + path = filepath.Join(path, "values.yaml") + return +} -- 2.16.6