From 51c8bb57b7ed47d159dbd8e9335e915c743d394b Mon Sep 17 00:00:00 2001 From: Yuan Tang Date: Thu, 9 May 2024 10:38:08 -0400 Subject: [PATCH] feat: Support customizable deployment strategy for RawDeployment mode. Fixes #3452 (#3603) * feat: Support customizable deployment strategy for RawDeployment mode Signed-off-by: Yuan Tang * regen Signed-off-by: Yuan Tang * lint Signed-off-by: Yuan Tang * Correctly apply rollingupdate Signed-off-by: Yuan Tang * address comments Signed-off-by: Yuan Tang * Add validation Signed-off-by: Yuan Tang --------- Signed-off-by: Yuan Tang --- .../serving.kserve.io_inferenceservices.yaml | 54 + .../client/kfserving_sdk_v1beta1_sample.ipynb | 62 +- .../alibi-detect/cifar10/cifar10_drift.ipynb | 112 +- .../explanation/aix/mnist/query_explain.ipynb | 30 +- .../alibi/cifar10/cifar10_explanations.ipynb | 11 +- .../alibi/income/income_explanations.ipynb | 60 +- .../movie_review_explanations.ipynb | 56 +- docs/samples/logger/basic/logger_demo.ipynb | 24 +- .../logger/knative-eventing/logger_demo.ipynb | 24 +- .../cifar10/cifar10_outlier.ipynb | 120 +- docs/samples/pipelines/kfs-pipeline.ipynb | 75 +- .../torchserve/logger/logger_demo.ipynb | 10 +- docs/samples/v1beta1/onnx/mosaic-onnx.ipynb | 34 +- .../sklearn-mixedtype-model.ipynb | 87 +- pkg/apis/serving/v1beta1/component.go | 5 + .../v1beta1/inference_service_validation.go | 3 + .../inference_service_validation_test.go | 13 + pkg/apis/serving/v1beta1/openapi_generated.go | 32 +- pkg/apis/serving/v1beta1/swagger.json | 16 + .../serving/v1beta1/zz_generated.deepcopy.go | 12 +- .../rawkube_controller_test.go | 414 ++++ .../deployment/deployment_reconciler.go | 5 +- .../docs/V1beta1ComponentExtensionSpec.md | 1 + python/kserve/docs/V1beta1ExplainerSpec.md | 1 + python/kserve/docs/V1beta1PredictorSpec.md | 1 + python/kserve/docs/V1beta1TransformerSpec.md | 1 + .../v1beta1_component_extension_spec.py | 28 +- .../kserve/models/v1beta1_explainer_spec.py | 28 +- .../kserve/models/v1beta1_predictor_spec.py | 28 +- .../kserve/models/v1beta1_transformer_spec.py | 28 +- .../serving.kserve.io_inferenceservices.yaml | 1949 +---------------- 31 files changed, 1126 insertions(+), 2198 deletions(-) diff --git a/config/crd/serving.kserve.io_inferenceservices.yaml b/config/crd/serving.kserve.io_inferenceservices.yaml index 5c5ebb0ead..3680558fa4 100644 --- a/config/crd/serving.kserve.io_inferenceservices.yaml +++ b/config/crd/serving.kserve.io_inferenceservices.yaml @@ -2319,6 +2319,24 @@ spec: - name type: object type: array + deploymentStrategy: + properties: + rollingUpdate: + properties: + maxSurge: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: + type: string + type: object dnsConfig: properties: nameservers: @@ -4964,6 +4982,24 @@ spec: - name type: object type: array + deploymentStrategy: + properties: + rollingUpdate: + properties: + maxSurge: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: + type: string + type: object dnsConfig: properties: nameservers: @@ -14517,6 +14553,24 @@ spec: - name type: object type: array + deploymentStrategy: + properties: + rollingUpdate: + properties: + maxSurge: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: + type: string + type: object dnsConfig: properties: nameservers: diff --git a/docs/samples/client/kfserving_sdk_v1beta1_sample.ipynb b/docs/samples/client/kfserving_sdk_v1beta1_sample.ipynb index 830dccb533..9f375d0d89 100644 --- a/docs/samples/client/kfserving_sdk_v1beta1_sample.ipynb +++ b/docs/samples/client/kfserving_sdk_v1beta1_sample.ipynb @@ -22,7 +22,7 @@ "metadata": {}, "outputs": [], "source": [ - "from kubernetes import client \n", + "from kubernetes import client\n", "from kfserving import KFServingClient\n", "from kfserving import constants\n", "from kfserving import utils\n", @@ -45,8 +45,8 @@ "metadata": {}, "outputs": [], "source": [ - "#namespace = utils.get_default_target_namespace()\n", - "namespace = 'kfserving-test'" + "# namespace = utils.get_default_target_namespace()\n", + "namespace = \"kfserving-test\"" ] }, { @@ -69,16 +69,21 @@ "metadata": {}, "outputs": [], "source": [ - "api_version = constants.KFSERVING_GROUP + '/' + kfserving_version\n", + "api_version = constants.KFSERVING_GROUP + \"/\" + kfserving_version\n", "\n", - "isvc = V1beta1InferenceService(api_version=api_version,\n", - " kind=constants.KFSERVING_KIND,\n", - " metadata=client.V1ObjectMeta(\n", - " name='flower-sample', namespace=namespace),\n", - " spec=V1beta1InferenceServiceSpec(\n", - " predictor=V1beta1PredictorSpec(\n", - " tensorflow=(V1beta1TFServingSpec(\n", - " storage_uri='gs://kfserving-examples/models/tensorflow/flowers'))))\n", + "isvc = V1beta1InferenceService(\n", + " api_version=api_version,\n", + " kind=constants.KFSERVING_KIND,\n", + " metadata=client.V1ObjectMeta(name=\"flower-sample\", namespace=namespace),\n", + " spec=V1beta1InferenceServiceSpec(\n", + " predictor=V1beta1PredictorSpec(\n", + " tensorflow=(\n", + " V1beta1TFServingSpec(\n", + " storage_uri=\"gs://kfserving-examples/models/tensorflow/flowers\"\n", + " )\n", + " )\n", + " )\n", + " ),\n", ")" ] }, @@ -152,7 +157,7 @@ } ], "source": [ - "KFServing.get('flower-sample', namespace=namespace, watch=True, timeout_seconds=120)" + "KFServing.get(\"flower-sample\", namespace=namespace, watch=True, timeout_seconds=120)" ] }, { @@ -223,18 +228,23 @@ } ], "source": [ - "isvc = V1beta1InferenceService(api_version=api_version,\n", - " kind=constants.KFSERVING_KIND,\n", - " metadata=client.V1ObjectMeta(\n", - " name='flower-sample', namespace=namespace),\n", - " spec=V1beta1InferenceServiceSpec(\n", - " predictor=V1beta1PredictorSpec(\n", - " canary_traffic_percent=20,\n", - " tensorflow=(V1beta1TFServingSpec(\n", - " storage_uri='gs://kfserving-examples/models/tensorflow/flowers-2'))))\n", + "isvc = V1beta1InferenceService(\n", + " api_version=api_version,\n", + " kind=constants.KFSERVING_KIND,\n", + " metadata=client.V1ObjectMeta(name=\"flower-sample\", namespace=namespace),\n", + " spec=V1beta1InferenceServiceSpec(\n", + " predictor=V1beta1PredictorSpec(\n", + " canary_traffic_percent=20,\n", + " tensorflow=(\n", + " V1beta1TFServingSpec(\n", + " storage_uri=\"gs://kfserving-examples/models/tensorflow/flowers-2\"\n", + " )\n", + " ),\n", + " )\n", + " ),\n", ")\n", "\n", - "KFServing.patch('flower-sample', isvc, namespace=namespace)" + "KFServing.patch(\"flower-sample\", isvc, namespace=namespace)" ] }, { @@ -250,7 +260,7 @@ "metadata": {}, "outputs": [], "source": [ - "KFServing.wait_isvc_ready('flower-sample', namespace=namespace)" + "KFServing.wait_isvc_ready(\"flower-sample\", namespace=namespace)" ] }, { @@ -268,7 +278,7 @@ } ], "source": [ - "KFServing.get('flower-sample', namespace=namespace, watch=True)" + "KFServing.get(\"flower-sample\", namespace=namespace, watch=True)" ] }, { @@ -313,7 +323,7 @@ } ], "source": [ - "KFServing.delete('flower-sample', namespace=namespace)" + "KFServing.delete(\"flower-sample\", namespace=namespace)" ] }, { diff --git a/docs/samples/drift-detection/alibi-detect/cifar10/cifar10_drift.ipynb b/docs/samples/drift-detection/alibi-detect/cifar10/cifar10_drift.ipynb index 7fdbf742e5..392b0f978d 100644 --- a/docs/samples/drift-detection/alibi-detect/cifar10/cifar10_drift.ipynb +++ b/docs/samples/drift-detection/alibi-detect/cifar10/cifar10_drift.ipynb @@ -355,8 +355,8 @@ "metadata": {}, "outputs": [], "source": [ - "CLUSTER_IPS=!(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')\n", - "CLUSTER_IP=CLUSTER_IPS[0]\n", + "CLUSTER_IPS = !(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')\n", + "CLUSTER_IP = CLUSTER_IPS[0]\n", "print(CLUSTER_IP)" ] }, @@ -378,7 +378,7 @@ "metadata": {}, "outputs": [], "source": [ - "#CLUSTER_IP=\"localhost:8080\"" + "# CLUSTER_IP=\"localhost:8080\"" ] }, { @@ -395,8 +395,8 @@ } ], "source": [ - "SERVICE_HOSTNAMES=!(kubectl get inferenceservice -n cifar10 tfserving-cifar10 -o jsonpath='{.status.url}' | cut -d \"/\" -f 3)\n", - "SERVICE_HOSTNAME_CIFAR10=SERVICE_HOSTNAMES[0]\n", + "SERVICE_HOSTNAMES = !(kubectl get inferenceservice -n cifar10 tfserving-cifar10 -o jsonpath='{.status.url}' | cut -d \"/\" -f 3)\n", + "SERVICE_HOSTNAME_CIFAR10 = SERVICE_HOSTNAMES[0]\n", "print(SERVICE_HOSTNAME_CIFAR10)" ] }, @@ -414,8 +414,8 @@ } ], "source": [ - "SERVICE_HOSTNAMES=!(kubectl get ksvc -n cifar10 drift-detector -o jsonpath='{.status.url}' | cut -d \"/\" -f 3)\n", - "SERVICE_HOSTNAME_VAEOD=SERVICE_HOSTNAMES[0]\n", + "SERVICE_HOSTNAMES = !(kubectl get ksvc -n cifar10 drift-detector -o jsonpath='{.status.url}' | cut -d \"/\" -f 3)\n", + "SERVICE_HOSTNAME_VAEOD = SERVICE_HOSTNAMES[0]\n", "print(SERVICE_HOSTNAME_VAEOD)" ] }, @@ -438,51 +438,71 @@ "import requests\n", "import json\n", "import tensorflow as tf\n", + "\n", "tf.keras.backend.clear_session()\n", "\n", "train, test = tf.keras.datasets.cifar10.load_data()\n", "X_train, y_train = train\n", "X_test, y_test = test\n", "\n", - "X_train = X_train.astype('float32') / 255\n", - "X_test = X_test.astype('float32') / 255\n", + "X_train = X_train.astype(\"float32\") / 255\n", + "X_test = X_test.astype(\"float32\") / 255\n", "print(X_train.shape, y_train.shape, X_test.shape, y_test.shape)\n", - "classes = ('plane', 'car', 'bird', 'cat',\n", - " 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')\n", + "classes = (\n", + " \"plane\",\n", + " \"car\",\n", + " \"bird\",\n", + " \"cat\",\n", + " \"deer\",\n", + " \"dog\",\n", + " \"frog\",\n", + " \"horse\",\n", + " \"ship\",\n", + " \"truck\",\n", + ")\n", + "\n", "\n", "def show(X):\n", " plt.imshow(X.reshape(32, 32, 3))\n", - " plt.axis('off')\n", + " plt.axis(\"off\")\n", " plt.show()\n", "\n", + "\n", "def predict(X):\n", - " formData = {\n", - " 'instances': X.tolist()\n", - " }\n", + " formData = {\"instances\": X.tolist()}\n", " headers = {}\n", " headers[\"Host\"] = SERVICE_HOSTNAME_CIFAR10\n", - " res = requests.post('http://'+CLUSTER_IP+'/v1/models/tfserving-cifar10:predict', json=formData, headers=headers)\n", + " res = requests.post(\n", + " \"http://\" + CLUSTER_IP + \"/v1/models/tfserving-cifar10:predict\",\n", + " json=formData,\n", + " headers=headers,\n", + " )\n", " if res.status_code == 200:\n", " j = res.json()\n", " if len(j[\"predictions\"]) == 1:\n", " return classes[np.array(j[\"predictions\"])[0].argmax()]\n", " else:\n", - " print(\"Failed with \",res.status_code)\n", + " print(\"Failed with \", res.status_code)\n", " return []\n", - " \n", + "\n", + "\n", "def drift(X):\n", - " formData = {\n", - " 'instances': X.tolist()\n", + " formData = {\"instances\": X.tolist()}\n", + " headers = {\n", + " \"ce-namespace\": \"default\",\n", + " \"ce-modelid\": \"cifar10drift\",\n", + " \"ce-type\": \"io.seldon.serving.inference.request\",\n", + " \"ce-id\": \"1234\",\n", + " \"ce-source\": \"localhost\",\n", + " \"ce-specversion\": \"1.0\",\n", " }\n", - " headers = { \"ce-namespace\": \"default\",\"ce-modelid\":\"cifar10drift\",\"ce-type\":\"io.seldon.serving.inference.request\", \\\n", - " \"ce-id\":\"1234\",\"ce-source\":\"localhost\",\"ce-specversion\":\"1.0\"}\n", - " headers[\"Host\"] = SERVICE_HOSTNAME_VAEOD \n", - " res = requests.post('http://'+CLUSTER_IP+'/', json=formData, headers=headers)\n", + " headers[\"Host\"] = SERVICE_HOSTNAME_VAEOD\n", + " res = requests.post(\"http://\" + CLUSTER_IP + \"/\", json=formData, headers=headers)\n", " if res.status_code == 200:\n", " od = res.json()\n", " return od\n", " else:\n", - " print(\"Failed with \",res.status_code)\n", + " print(\"Failed with \", res.status_code)\n", " return []" ] }, @@ -525,7 +545,7 @@ ], "source": [ "idx = 1\n", - "X = X_train[idx:idx+1]\n", + "X = X_train[idx : idx + 1]\n", "show(X)\n", "predict(X)" ] @@ -550,7 +570,7 @@ "metadata": {}, "outputs": [], "source": [ - "!kubectl logs -n cifar10 $(kubectl get pod -n cifar10 -l app=hello-display -o jsonpath='{.items[0].metadata.name}') " + "!kubectl logs -n cifar10 $(kubectl get pod -n cifar10 -l app=hello-display -o jsonpath='{.items[0].metadata.name}')" ] }, { @@ -589,8 +609,9 @@ ], "source": [ "from tqdm.notebook import tqdm\n", - "for i in tqdm(range(0,5000,100)):\n", - " X = X_train[i:i+100]\n", + "\n", + "for i in tqdm(range(0, 5000, 100)):\n", + " X = X_train[i : i + 100]\n", " predict(X)" ] }, @@ -615,13 +636,13 @@ } ], "source": [ - "res=!kubectl logs -n cifar10 $(kubectl get pod -n cifar10 -l app=hello-display -o jsonpath='{.items[0].metadata.name}') \n", - "data= []\n", - "for i in range(0,len(res)):\n", - " if res[i] == 'Data,':\n", - " data.append(res[i+1])\n", + "res = !kubectl logs -n cifar10 $(kubectl get pod -n cifar10 -l app=hello-display -o jsonpath='{.items[0].metadata.name}')\n", + "data = []\n", + "for i in range(0, len(res)):\n", + " if res[i] == \"Data,\":\n", + " data.append(res[i + 1])\n", "j = json.loads(json.loads(data[0]))\n", - "print(\"Drift\",j[\"data\"][\"is_drift\"]==1)" + "print(\"Drift\", j[\"data\"][\"is_drift\"] == 1)" ] }, { @@ -638,9 +659,10 @@ "outputs": [], "source": [ "from alibi_detect.datasets import fetch_cifar10c, corruption_types_cifar10c\n", - "corruption = ['motion_blur']\n", + "\n", + "corruption = [\"motion_blur\"]\n", "X_corr, y_corr = fetch_cifar10c(corruption=corruption, severity=5, return_X_y=True)\n", - "X_corr = X_corr.astype('float32') / 255" + "X_corr = X_corr.astype(\"float32\") / 255" ] }, { @@ -726,8 +748,8 @@ } ], "source": [ - "for i in tqdm(range(0,5000,100)):\n", - " X = X_corr[i:i+100]\n", + "for i in tqdm(range(0, 5000, 100)):\n", + " X = X_corr[i : i + 100]\n", " predict(X)" ] }, @@ -752,13 +774,13 @@ } ], "source": [ - "res=!kubectl logs -n cifar10 $(kubectl get pod -n cifar10 -l app=hello-display -o jsonpath='{.items[0].metadata.name}') \n", - "data= []\n", - "for i in range(0,len(res)):\n", - " if res[i] == 'Data,':\n", - " data.append(res[i+1])\n", + "res = !kubectl logs -n cifar10 $(kubectl get pod -n cifar10 -l app=hello-display -o jsonpath='{.items[0].metadata.name}')\n", + "data = []\n", + "for i in range(0, len(res)):\n", + " if res[i] == \"Data,\":\n", + " data.append(res[i + 1])\n", "j = json.loads(json.loads(data[-1]))\n", - "print(\"Drift\",j[\"data\"][\"is_drift\"]==1)" + "print(\"Drift\", j[\"data\"][\"is_drift\"] == 1)" ] }, { diff --git a/docs/samples/explanation/aix/mnist/query_explain.ipynb b/docs/samples/explanation/aix/mnist/query_explain.ipynb index 478945703c..f10e25bd74 100644 --- a/docs/samples/explanation/aix/mnist/query_explain.ipynb +++ b/docs/samples/explanation/aix/mnist/query_explain.ipynb @@ -58,7 +58,11 @@ "from keras.preprocessing import image\n", "from keras.applications.imagenet_utils import decode_predictions\n", "import time\n", - "from skimage.color import gray2rgb, rgb2gray, label2rgb # since the code wants color images" + "from skimage.color import (\n", + " gray2rgb,\n", + " rgb2gray,\n", + " label2rgb,\n", + ") # since the code wants color images" ] }, { @@ -85,18 +89,16 @@ } ], "source": [ - "print('************************************************************')\n", - "print('************************************************************')\n", - "print('************************************************************')\n", + "print(\"************************************************************\")\n", + "print(\"************************************************************\")\n", + "print(\"************************************************************\")\n", "print(\"starting query\")\n", "\n", "if len(sys.argv) < 3:\n", " raise Exception(\"No endpoint specified. \")\n", "\n", "endpoint = sys.argv[1]\n", - "headers = {\n", - " 'Host': sys.argv[2]\n", - "}\n", + "headers = {\"Host\": sys.argv[2]}\n", "test_num = 1002\n", "is_file = False\n", "if len(sys.argv) > 3:\n", @@ -118,7 +120,7 @@ " if labels[x] != 0:\n", " actual = x\n", " inputs = gray2rgb(inputs.reshape((-1, 28, 28)))\n", - " inputs = np.reshape(inputs, (28,28,3))\n", + " inputs = np.reshape(inputs, (28, 28, 3))\n", "input_image = {\"instances\": [inputs.tolist()]}" ] }, @@ -148,8 +150,8 @@ } ], "source": [ - "fig0 = (inputs[:,:,0] + 0.5)*255\n", - "f, axarr = plt.subplots(1, 1, figsize=(10,10))\n", + "fig0 = (inputs[:, :, 0] + 0.5) * 255\n", + "f, axarr = plt.subplots(1, 1, figsize=(10, 10))\n", "axarr.set_title(\"Original Image\")\n", "axarr.imshow(fig0, cmap=\"gray\")\n", "plt.show()" @@ -245,12 +247,12 @@ "masks = np.array(res_json[\"explanations\"][\"masks\"])\n", "top_labels = np.array(res_json[\"explanations\"][\"top_labels\"])\n", "\n", - "fig, m_axs = plt.subplots(2,5, figsize = (12,6))\n", + "fig, m_axs = plt.subplots(2, 5, figsize=(12, 6))\n", "for i, c_ax in enumerate(m_axs.flatten()):\n", " mask = masks[i]\n", - " c_ax.imshow(label2rgb(mask, temp, bg_label = 0), interpolation = 'nearest')\n", - " c_ax.set_title('Positive for {}\\nActual {}'.format(top_labels[i], actual))\n", - " c_ax.axis('off')\n", + " c_ax.imshow(label2rgb(mask, temp, bg_label=0), interpolation=\"nearest\")\n", + " c_ax.set_title(\"Positive for {}\\nActual {}\".format(top_labels[i], actual))\n", + " c_ax.axis(\"off\")\n", "plt.show()" ] } diff --git a/docs/samples/explanation/alibi/cifar10/cifar10_explanations.ipynb b/docs/samples/explanation/alibi/cifar10/cifar10_explanations.ipynb index 6a37482478..1644ff8d37 100644 --- a/docs/samples/explanation/alibi/cifar10/cifar10_explanations.ipynb +++ b/docs/samples/explanation/alibi/cifar10/cifar10_explanations.ipynb @@ -56,8 +56,8 @@ "metadata": {}, "outputs": [], "source": [ - "CLUSTER_IPS=!(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')\n", - "CLUSTER_IP=CLUSTER_IPS[0]\n", + "CLUSTER_IPS = !(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')\n", + "CLUSTER_IP = CLUSTER_IPS[0]\n", "print(CLUSTER_IP)" ] }, @@ -67,8 +67,8 @@ "metadata": {}, "outputs": [], "source": [ - "SERVICE_HOSTNAMES=!(kubectl get inferenceservice cifar10 -n default -o jsonpath='{.status.url}' | cut -d \"/\" -f 3)\n", - "SERVICE_HOSTNAME=SERVICE_HOSTNAMES[0]\n", + "SERVICE_HOSTNAMES = !(kubectl get inferenceservice cifar10 -n default -o jsonpath='{.status.url}' | cut -d \"/\" -f 3)\n", + "SERVICE_HOSTNAME = SERVICE_HOSTNAMES[0]\n", "print(SERVICE_HOSTNAME)" ] }, @@ -79,7 +79,8 @@ "outputs": [], "source": [ "import sys\n", - "sys.path.append('../')\n", + "\n", + "sys.path.append(\"../\")\n", "from alibi_helper import *" ] }, diff --git a/docs/samples/explanation/alibi/income/income_explanations.ipynb b/docs/samples/explanation/alibi/income/income_explanations.ipynb index 12a49b0386..317d7b78ae 100644 --- a/docs/samples/explanation/alibi/income/income_explanations.ipynb +++ b/docs/samples/explanation/alibi/income/income_explanations.ipynb @@ -50,8 +50,8 @@ "metadata": {}, "outputs": [], "source": [ - "CLUSTER_IPS=!(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')\n", - "CLUSTER_IP=CLUSTER_IPS[0]\n", + "CLUSTER_IPS = !(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')\n", + "CLUSTER_IP = CLUSTER_IPS[0]\n", "print(CLUSTER_IP)" ] }, @@ -61,8 +61,10 @@ "metadata": {}, "outputs": [], "source": [ - "SERVICE_HOSTNAMES=!(kubectl get inferenceservice income -o jsonpath='{.status.url}' | cut -d \"/\" -f 3)\n", - "SERVICE_HOSTNAME=SERVICE_HOSTNAMES[0]\n", + "SERVICE_HOSTNAMES = (\n", + " !(kubectl get inferenceservice income -o jsonpath='{.status.url}' | cut -d \"/\" -f 3)\n", + ")\n", + "SERVICE_HOSTNAME = SERVICE_HOSTNAMES[0]\n", "print(SERVICE_HOSTNAME)" ] }, @@ -73,9 +75,11 @@ "outputs": [], "source": [ "import sys\n", - "sys.path.append('../')\n", + "\n", + "sys.path.append(\"../\")\n", "from alibi_helper import *\n", "from alibi.datasets import fetch_adult\n", + "\n", "adult = fetch_adult()\n", "cmap = dict.fromkeys(adult.category_map.keys())\n", "for key, val in adult.category_map.items():\n", @@ -90,9 +94,17 @@ "source": [ "idxLow = 0\n", "idxHigh = 32554\n", - "for idx in [idxLow,idxHigh]:\n", - " show_row([getFeatures([adult.data[idx]], cmap)],adult)\n", - " show_prediction(predict(adult.data[idx:idx+1].tolist(),\"income\",adult,SERVICE_HOSTNAME,CLUSTER_IP))" + "for idx in [idxLow, idxHigh]:\n", + " show_row([getFeatures([adult.data[idx]], cmap)], adult)\n", + " show_prediction(\n", + " predict(\n", + " adult.data[idx : idx + 1].tolist(),\n", + " \"income\",\n", + " adult,\n", + " SERVICE_HOSTNAME,\n", + " CLUSTER_IP,\n", + " )\n", + " )" ] }, { @@ -108,7 +120,9 @@ "metadata": {}, "outputs": [], "source": [ - "exp = explain(adult.data[idxLow:idxLow+1].tolist(),\"income\",SERVICE_HOSTNAME,CLUSTER_IP)" + "exp = explain(\n", + " adult.data[idxLow : idxLow + 1].tolist(), \"income\", SERVICE_HOSTNAME, CLUSTER_IP\n", + ")" ] }, { @@ -117,7 +131,7 @@ "metadata": {}, "outputs": [], "source": [ - "show_anchors(exp['data']['anchor'])" + "show_anchors(exp[\"data\"][\"anchor\"])" ] }, { @@ -133,8 +147,8 @@ "metadata": {}, "outputs": [], "source": [ - "show_bar([exp['data']['precision']],[''],\"Precision\")\n", - "show_bar([exp['data']['coverage']],[''],\"Coverage\")" + "show_bar([exp[\"data\"][\"precision\"]], [\"\"], \"Precision\")\n", + "show_bar([exp[\"data\"][\"coverage\"]], [\"\"], \"Coverage\")" ] }, { @@ -143,7 +157,7 @@ "metadata": {}, "outputs": [], "source": [ - "show_feature_coverage(exp['data'])" + "show_feature_coverage(exp[\"data\"])" ] }, { @@ -152,7 +166,7 @@ "metadata": {}, "outputs": [], "source": [ - "show_examples(exp['data'],0,adult)" + "show_examples(exp[\"data\"], 0, adult)" ] }, { @@ -163,7 +177,7 @@ }, "outputs": [], "source": [ - "show_examples(exp['data'],0,adult,False)" + "show_examples(exp[\"data\"], 0, adult, False)" ] }, { @@ -179,7 +193,9 @@ "metadata": {}, "outputs": [], "source": [ - "exp = explain(adult.data[idxHigh:idxHigh+1].tolist(),\"income\", SERVICE_HOSTNAME,CLUSTER_IP)" + "exp = explain(\n", + " adult.data[idxHigh : idxHigh + 1].tolist(), \"income\", SERVICE_HOSTNAME, CLUSTER_IP\n", + ")" ] }, { @@ -188,7 +204,7 @@ "metadata": {}, "outputs": [], "source": [ - "show_anchors(exp['data']['anchor'])" + "show_anchors(exp[\"data\"][\"anchor\"])" ] }, { @@ -204,8 +220,8 @@ "metadata": {}, "outputs": [], "source": [ - "show_bar([exp['data']['precision']],[''],\"Precision\")\n", - "show_bar([exp['data']['coverage']],[''],\"Coverage\")" + "show_bar([exp[\"data\"][\"precision\"]], [\"\"], \"Precision\")\n", + "show_bar([exp[\"data\"][\"coverage\"]], [\"\"], \"Coverage\")" ] }, { @@ -214,7 +230,7 @@ "metadata": {}, "outputs": [], "source": [ - "show_feature_coverage(exp['data'])" + "show_feature_coverage(exp[\"data\"])" ] }, { @@ -223,7 +239,7 @@ "metadata": {}, "outputs": [], "source": [ - "show_examples(exp['data'],0,adult)" + "show_examples(exp[\"data\"], 0, adult)" ] }, { @@ -234,7 +250,7 @@ }, "outputs": [], "source": [ - "show_examples(exp['data'],0,adult,False)" + "show_examples(exp[\"data\"], 0, adult, False)" ] }, { diff --git a/docs/samples/explanation/alibi/moviesentiment/movie_review_explanations.ipynb b/docs/samples/explanation/alibi/moviesentiment/movie_review_explanations.ipynb index d1c6fc4e07..1d266fdd39 100644 --- a/docs/samples/explanation/alibi/moviesentiment/movie_review_explanations.ipynb +++ b/docs/samples/explanation/alibi/moviesentiment/movie_review_explanations.ipynb @@ -38,8 +38,8 @@ "metadata": {}, "outputs": [], "source": [ - "CLUSTER_IPS=!(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')\n", - "CLUSTER_IP=CLUSTER_IPS[0]\n", + "CLUSTER_IPS = !(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')\n", + "CLUSTER_IP = CLUSTER_IPS[0]\n", "print(CLUSTER_IP)" ] }, @@ -49,8 +49,8 @@ "metadata": {}, "outputs": [], "source": [ - "SERVICE_HOSTNAMES=!(kubectl get inferenceservice moviesentiment -o jsonpath='{.status.url}' | cut -d \"/\" -f 3)\n", - "SERVICE_HOSTNAME=SERVICE_HOSTNAMES[0]\n", + "SERVICE_HOSTNAMES = !(kubectl get inferenceservice moviesentiment -o jsonpath='{.status.url}' | cut -d \"/\" -f 3)\n", + "SERVICE_HOSTNAME = SERVICE_HOSTNAMES[0]\n", "print(SERVICE_HOSTNAME)" ] }, @@ -61,7 +61,8 @@ "outputs": [], "source": [ "import sys\n", - "sys.path.append('../')\n", + "\n", + "sys.path.append(\"../\")\n", "from alibi_helper import *" ] }, @@ -72,6 +73,7 @@ "outputs": [], "source": [ "from alibi.datasets import fetch_movie_sentiment\n", + "\n", "movies = fetch_movie_sentiment()" ] }, @@ -83,9 +85,17 @@ "source": [ "idxNeg = 37\n", "idxPos = 5227\n", - "for idx in [idxNeg,idxPos]:\n", + "for idx in [idxNeg, idxPos]:\n", " print(movies.data[idx])\n", - " show_prediction(predict(movies.data[idx:idx+1],'moviesentiment',movies,SERVICE_HOSTNAME,CLUSTER_IP))" + " show_prediction(\n", + " predict(\n", + " movies.data[idx : idx + 1],\n", + " \"moviesentiment\",\n", + " movies,\n", + " SERVICE_HOSTNAME,\n", + " CLUSTER_IP,\n", + " )\n", + " )" ] }, { @@ -101,7 +111,9 @@ "metadata": {}, "outputs": [], "source": [ - "exp = explain(movies.data[idxNeg:idxNeg+1],\"moviesentiment\",SERVICE_HOSTNAME,CLUSTER_IP)" + "exp = explain(\n", + " movies.data[idxNeg : idxNeg + 1], \"moviesentiment\", SERVICE_HOSTNAME, CLUSTER_IP\n", + ")" ] }, { @@ -110,7 +122,7 @@ "metadata": {}, "outputs": [], "source": [ - "show_anchors(exp['data']['anchor'])" + "show_anchors(exp[\"data\"][\"anchor\"])" ] }, { @@ -126,8 +138,8 @@ "metadata": {}, "outputs": [], "source": [ - "show_bar([exp['data']['precision']],[''],\"Precision\")\n", - "show_bar([exp['data']['coverage']],[''],\"Coverage\")" + "show_bar([exp[\"data\"][\"precision\"]], [\"\"], \"Precision\")\n", + "show_bar([exp[\"data\"][\"coverage\"]], [\"\"], \"Coverage\")" ] }, { @@ -136,7 +148,7 @@ "metadata": {}, "outputs": [], "source": [ - "show_feature_coverage(exp['data'])" + "show_feature_coverage(exp[\"data\"])" ] }, { @@ -145,7 +157,7 @@ "metadata": {}, "outputs": [], "source": [ - "show_examples(exp['data'],0,movies)" + "show_examples(exp[\"data\"], 0, movies)" ] }, { @@ -156,7 +168,7 @@ }, "outputs": [], "source": [ - "show_examples(exp['data'],0,movies,False)" + "show_examples(exp[\"data\"], 0, movies, False)" ] }, { @@ -172,7 +184,9 @@ "metadata": {}, "outputs": [], "source": [ - "exp = explain(movies.data[idxPos:idxPos+1],\"moviesentiment\",SERVICE_HOSTNAME,CLUSTER_IP)" + "exp = explain(\n", + " movies.data[idxPos : idxPos + 1], \"moviesentiment\", SERVICE_HOSTNAME, CLUSTER_IP\n", + ")" ] }, { @@ -181,7 +195,7 @@ "metadata": {}, "outputs": [], "source": [ - "show_anchors(exp['data']['anchor'])" + "show_anchors(exp[\"data\"][\"anchor\"])" ] }, { @@ -197,8 +211,8 @@ "metadata": {}, "outputs": [], "source": [ - "show_bar([exp['data']['precision']],[''],\"Precision\")\n", - "show_bar([exp['data']['coverage']],[''],\"Coverage\")" + "show_bar([exp[\"data\"][\"precision\"]], [\"\"], \"Precision\")\n", + "show_bar([exp[\"data\"][\"coverage\"]], [\"\"], \"Coverage\")" ] }, { @@ -207,7 +221,7 @@ "metadata": {}, "outputs": [], "source": [ - "show_feature_coverage(exp['data'])" + "show_feature_coverage(exp[\"data\"])" ] }, { @@ -216,7 +230,7 @@ "metadata": {}, "outputs": [], "source": [ - "show_examples(exp['data'],0,movies)" + "show_examples(exp[\"data\"], 0, movies)" ] }, { @@ -227,7 +241,7 @@ }, "outputs": [], "source": [ - "show_examples(exp['data'],0,movies,False)" + "show_examples(exp[\"data\"], 0, movies, False)" ] }, { diff --git a/docs/samples/logger/basic/logger_demo.ipynb b/docs/samples/logger/basic/logger_demo.ipynb index b8739e3fb8..91c892f4ca 100644 --- a/docs/samples/logger/basic/logger_demo.ipynb +++ b/docs/samples/logger/basic/logger_demo.ipynb @@ -63,8 +63,8 @@ "metadata": {}, "outputs": [], "source": [ - "CLUSTER_IPS=!(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')\n", - "CLUSTER_IP=CLUSTER_IPS[0]\n", + "CLUSTER_IPS = !(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')\n", + "CLUSTER_IP = CLUSTER_IPS[0]\n", "print(CLUSTER_IP)" ] }, @@ -74,8 +74,8 @@ "metadata": {}, "outputs": [], "source": [ - "SERVICE_HOSTNAMES=!(kubectl get inferenceservice sklearn-iris -o jsonpath='{.status.url}' | cut -d \"/\" -f 3)\n", - "SERVICE_HOSTNAME=SERVICE_HOSTNAMES[0]\n", + "SERVICE_HOSTNAMES = !(kubectl get inferenceservice sklearn-iris -o jsonpath='{.status.url}' | cut -d \"/\" -f 3)\n", + "SERVICE_HOSTNAME = SERVICE_HOSTNAMES[0]\n", "print(SERVICE_HOSTNAME)" ] }, @@ -86,17 +86,21 @@ "outputs": [], "source": [ "import requests\n", + "\n", + "\n", "def predict(X, name, svc_hostname, cluster_ip):\n", - " formData = {\n", - " 'instances': X\n", - " }\n", + " formData = {\"instances\": X}\n", " headers = {}\n", " headers[\"Host\"] = svc_hostname\n", - " res = requests.post('http://'+cluster_ip+'/v1/models/'+name+':predict', json=formData, headers=headers)\n", + " res = requests.post(\n", + " \"http://\" + cluster_ip + \"/v1/models/\" + name + \":predict\",\n", + " json=formData,\n", + " headers=headers,\n", + " )\n", " if res.status_code == 200:\n", " return res.json()\n", " else:\n", - " print(\"Failed with \",res.status_code)\n", + " print(\"Failed with \", res.status_code)\n", " return []" ] }, @@ -106,7 +110,7 @@ "metadata": {}, "outputs": [], "source": [ - "predict([[6.8, 2.8, 4.8, 1.4]],\"sklearn-iris\",SERVICE_HOSTNAME,CLUSTER_IP)" + "predict([[6.8, 2.8, 4.8, 1.4]], \"sklearn-iris\", SERVICE_HOSTNAME, CLUSTER_IP)" ] }, { diff --git a/docs/samples/logger/knative-eventing/logger_demo.ipynb b/docs/samples/logger/knative-eventing/logger_demo.ipynb index b8c7e9c131..760f4305e7 100644 --- a/docs/samples/logger/knative-eventing/logger_demo.ipynb +++ b/docs/samples/logger/knative-eventing/logger_demo.ipynb @@ -113,8 +113,8 @@ "metadata": {}, "outputs": [], "source": [ - "CLUSTER_IPS=!(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')\n", - "CLUSTER_IP=CLUSTER_IPS[0]\n", + "CLUSTER_IPS = !(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')\n", + "CLUSTER_IP = CLUSTER_IPS[0]\n", "print(CLUSTER_IP)" ] }, @@ -124,8 +124,8 @@ "metadata": {}, "outputs": [], "source": [ - "SERVICE_HOSTNAMES=!(kubectl get inferenceservice sklearn-iris -o jsonpath='{.status.url}' | cut -d \"/\" -f 3)\n", - "SERVICE_HOSTNAME=SERVICE_HOSTNAMES[0]\n", + "SERVICE_HOSTNAMES = !(kubectl get inferenceservice sklearn-iris -o jsonpath='{.status.url}' | cut -d \"/\" -f 3)\n", + "SERVICE_HOSTNAME = SERVICE_HOSTNAMES[0]\n", "print(SERVICE_HOSTNAME)" ] }, @@ -136,17 +136,21 @@ "outputs": [], "source": [ "import requests\n", + "\n", + "\n", "def predict(X, name, svc_hostname, cluster_ip):\n", - " formData = {\n", - " 'instances': X\n", - " }\n", + " formData = {\"instances\": X}\n", " headers = {}\n", " headers[\"Host\"] = svc_hostname\n", - " res = requests.post('http://'+cluster_ip+'/v1/models/'+name+':predict', json=formData, headers=headers)\n", + " res = requests.post(\n", + " \"http://\" + cluster_ip + \"/v1/models/\" + name + \":predict\",\n", + " json=formData,\n", + " headers=headers,\n", + " )\n", " if res.status_code == 200:\n", " return res.json()\n", " else:\n", - " print(\"Failed with \",res.status_code)\n", + " print(\"Failed with \", res.status_code)\n", " return []" ] }, @@ -156,7 +160,7 @@ "metadata": {}, "outputs": [], "source": [ - "predict([[6.8, 2.8, 4.8, 1.4]],\"sklearn-iris\",SERVICE_HOSTNAME,CLUSTER_IP)" + "predict([[6.8, 2.8, 4.8, 1.4]], \"sklearn-iris\", SERVICE_HOSTNAME, CLUSTER_IP)" ] }, { diff --git a/docs/samples/outlier-detection/alibi-detect/cifar10/cifar10_outlier.ipynb b/docs/samples/outlier-detection/alibi-detect/cifar10/cifar10_outlier.ipynb index 7041cf7a91..e3d1c5df4b 100644 --- a/docs/samples/outlier-detection/alibi-detect/cifar10/cifar10_outlier.ipynb +++ b/docs/samples/outlier-detection/alibi-detect/cifar10/cifar10_outlier.ipynb @@ -376,8 +376,8 @@ "metadata": {}, "outputs": [], "source": [ - "CLUSTER_IPS=!(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')\n", - "CLUSTER_IP=CLUSTER_IPS[0]\n", + "CLUSTER_IPS = !(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')\n", + "CLUSTER_IP = CLUSTER_IPS[0]\n", "print(CLUSTER_IP)" ] }, @@ -389,7 +389,7 @@ }, "outputs": [], "source": [ - "CLUSTER_IP=\"localhost:8080\"" + "CLUSTER_IP = \"localhost:8080\"" ] }, { @@ -421,8 +421,8 @@ } ], "source": [ - "SERVICE_HOSTNAMES=!(kubectl get inferenceservice -n cifar10 tfserving-cifar10 -o jsonpath='{.status.url}' | cut -d \"/\" -f 3)\n", - "SERVICE_HOSTNAME_CIFAR10=SERVICE_HOSTNAMES[0]\n", + "SERVICE_HOSTNAMES = !(kubectl get inferenceservice -n cifar10 tfserving-cifar10 -o jsonpath='{.status.url}' | cut -d \"/\" -f 3)\n", + "SERVICE_HOSTNAME_CIFAR10 = SERVICE_HOSTNAMES[0]\n", "print(SERVICE_HOSTNAME_CIFAR10)" ] }, @@ -442,8 +442,8 @@ } ], "source": [ - "SERVICE_HOSTNAMES=!(kubectl get ksvc -n cifar10 vae-outlier -o jsonpath='{.status.url}' | cut -d \"/\" -f 3)\n", - "SERVICE_HOSTNAME_VAEOD=SERVICE_HOSTNAMES[0]\n", + "SERVICE_HOSTNAMES = !(kubectl get ksvc -n cifar10 vae-outlier -o jsonpath='{.status.url}' | cut -d \"/\" -f 3)\n", + "SERVICE_HOSTNAME_VAEOD = SERVICE_HOSTNAMES[0]\n", "print(SERVICE_HOSTNAME_VAEOD)" ] }, @@ -467,6 +467,7 @@ "import numpy as np\n", "import json\n", "import tensorflow as tf\n", + "\n", "tf.keras.backend.clear_session()\n", "\n", "from alibi_detect.od.vae import OutlierVAE\n", @@ -478,45 +479,66 @@ "X_train, y_train = train\n", "X_test, y_test = test\n", "\n", - "X_train = X_train.astype('float32') / 255\n", - "X_test = X_test.astype('float32') / 255\n", + "X_train = X_train.astype(\"float32\") / 255\n", + "X_test = X_test.astype(\"float32\") / 255\n", "print(X_train.shape, y_train.shape, X_test.shape, y_test.shape)\n", - "classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')\n", + "classes = (\n", + " \"plane\",\n", + " \"car\",\n", + " \"bird\",\n", + " \"cat\",\n", + " \"deer\",\n", + " \"dog\",\n", + " \"frog\",\n", + " \"horse\",\n", + " \"ship\",\n", + " \"truck\",\n", + ")\n", + "\n", "\n", "def show(X):\n", " plt.imshow(X.reshape(32, 32, 3))\n", - " plt.axis('off')\n", + " plt.axis(\"off\")\n", " plt.show()\n", "\n", + "\n", "def predict(X):\n", - " formData = {\n", - " 'instances': X.tolist()\n", - " }\n", + " formData = {\"instances\": X.tolist()}\n", " headers = {}\n", " headers[\"Host\"] = SERVICE_HOSTNAME_CIFAR10\n", - " res = requests.post('http://'+CLUSTER_IP+'/v1/models/tfserving-cifar10:predict', json=formData, headers=headers)\n", + " res = requests.post(\n", + " \"http://\" + CLUSTER_IP + \"/v1/models/tfserving-cifar10:predict\",\n", + " json=formData,\n", + " headers=headers,\n", + " )\n", " if res.status_code == 200:\n", " return classes[np.array(res.json()[\"predictions\"])[0].argmax()]\n", " else:\n", - " print(\"Failed with \",res.status_code)\n", + " print(\"Failed with \", res.status_code)\n", " return []\n", - " \n", + "\n", + "\n", "def outlier(X):\n", - " formData = {\n", - " 'instances': X.tolist()\n", + " formData = {\"instances\": X.tolist()}\n", + " headers = {\n", + " \"Alibi-Detect-Return-Feature-Score\": \"true\",\n", + " \"Alibi-Detect-Return-Instance-Score\": \"true\",\n", + " \"ce-namespace\": \"default\",\n", + " \"ce-modelid\": \"cifar10\",\n", + " \"ce-type\": \"io.seldon.serving.inference.request\",\n", + " \"ce-id\": \"1234\",\n", + " \"ce-source\": \"localhost\",\n", + " \"ce-specversion\": \"1.0\",\n", " }\n", - " headers = {\"Alibi-Detect-Return-Feature-Score\":\"true\",\"Alibi-Detect-Return-Instance-Score\":\"true\", \\\n", - " \"ce-namespace\": \"default\",\"ce-modelid\":\"cifar10\",\"ce-type\":\"io.seldon.serving.inference.request\", \\\n", - " \"ce-id\":\"1234\",\"ce-source\":\"localhost\",\"ce-specversion\":\"1.0\"}\n", " headers[\"Host\"] = SERVICE_HOSTNAME_VAEOD\n", - " res = requests.post('http://'+CLUSTER_IP+'/', json=formData, headers=headers)\n", + " res = requests.post(\"http://\" + CLUSTER_IP + \"/\", json=formData, headers=headers)\n", " if res.status_code == 200:\n", " od = res.json()\n", " od[\"data\"][\"feature_score\"] = np.array(od[\"data\"][\"feature_score\"])\n", " od[\"data\"][\"instance_score\"] = np.array(od[\"data\"][\"instance_score\"])\n", " return od\n", " else:\n", - " print(\"Failed with \",res.status_code)\n", + " print(\"Failed with \", res.status_code)\n", " return []" ] }, @@ -557,7 +579,7 @@ ], "source": [ "idx = 1\n", - "X = X_train[idx:idx+1]\n", + "X = X_train[idx : idx + 1]\n", "show(X)\n", "predict(X)" ] @@ -603,13 +625,13 @@ } ], "source": [ - "res=!kubectl logs -n cifar10 $(kubectl get pod -n cifar10 -l app=hello-display -o jsonpath='{.items[0].metadata.name}') \n", - "data= []\n", - "for i in range(0,len(res)):\n", - " if res[i] == 'Data,':\n", - " data.append(res[i+1])\n", + "res = !kubectl logs -n cifar10 $(kubectl get pod -n cifar10 -l app=hello-display -o jsonpath='{.items[0].metadata.name}')\n", + "data = []\n", + "for i in range(0, len(res)):\n", + " if res[i] == \"Data,\":\n", + " data.append(res[i + 1])\n", "j = json.loads(json.loads(data[-1]))\n", - "print(\"Outlier\",j[\"data\"][\"is_outlier\"]==[1])" + "print(\"Outlier\", j[\"data\"][\"is_outlier\"] == [1])" ] }, { @@ -627,14 +649,16 @@ }, "outputs": [], "source": [ - "np.random.seed(0) \n", - "X_mask, mask = apply_mask(X.reshape(1, 32, 32, 3),\n", - " mask_size=(10,10),\n", - " n_masks=1,\n", - " channels=[0,1,2],\n", - " mask_type='normal',\n", - " noise_distr=(0,1),\n", - " clip_rng=(0,1))" + "np.random.seed(0)\n", + "X_mask, mask = apply_mask(\n", + " X.reshape(1, 32, 32, 3),\n", + " mask_size=(10, 10),\n", + " n_masks=1,\n", + " channels=[0, 1, 2],\n", + " mask_type=\"normal\",\n", + " noise_distr=(0, 1),\n", + " clip_rng=(0, 1),\n", + ")" ] }, { @@ -667,7 +691,7 @@ ], "source": [ "show(X_mask)\n", - "predict(X_mask)\n" + "predict(X_mask)" ] }, { @@ -711,13 +735,13 @@ } ], "source": [ - "res=!kubectl logs -n cifar10 $(kubectl get pod -n cifar10 -l app=hello-display -o jsonpath='{.items[0].metadata.name}') \n", - "data= []\n", - "for i in range(0,len(res)):\n", - " if res[i] == 'Data,':\n", - " data.append(res[i+1])\n", + "res = !kubectl logs -n cifar10 $(kubectl get pod -n cifar10 -l app=hello-display -o jsonpath='{.items[0].metadata.name}')\n", + "data = []\n", + "for i in range(0, len(res)):\n", + " if res[i] == \"Data,\":\n", + " data.append(res[i + 1])\n", "j = json.loads(json.loads(data[-1]))\n", - "print(\"Outlier\",j[\"data\"][\"is_outlier\"]==[1])" + "print(\"Outlier\", j[\"data\"][\"is_outlier\"] == [1])" ] }, { @@ -746,9 +770,7 @@ } ], "source": [ - "plot_feature_outlier_image(od_preds, \n", - " X_mask, \n", - " X_recon=None)" + "plot_feature_outlier_image(od_preds, X_mask, X_recon=None)" ] }, { diff --git a/docs/samples/pipelines/kfs-pipeline.ipynb b/docs/samples/pipelines/kfs-pipeline.ipynb index 32b699d1de..605d2ce653 100644 --- a/docs/samples/pipelines/kfs-pipeline.ipynb +++ b/docs/samples/pipelines/kfs-pipeline.ipynb @@ -40,8 +40,8 @@ "# Note: Add the KubeFlow Pipeline endpoint below if the client is not running on the same cluster.\n", "# Example: kfp.Client('http://192.168.1.27:31380/pipeline')\n", "client = kfp.Client()\n", - "EXPERIMENT_NAME = 'KServe Experiments'\n", - "experiment = client.create_experiment(name=EXPERIMENT_NAME, namespace='anonymous')" + "EXPERIMENT_NAME = \"KServe Experiments\"\n", + "experiment = client.create_experiment(name=EXPERIMENT_NAME, namespace=\"anonymous\")" ] }, { @@ -58,30 +58,34 @@ "outputs": [], "source": [ "# kfserving_op = components.load_component_from_url('https://raw.githubusercontent.com/kubeflow/pipelines/master/components/kubeflow/kfserving/component.yaml')\n", - "kserve_op = components.load_component_from_url('https://raw.githubusercontent.com/kubeflow/pipelines/master/components/kserve/component.yaml')\n", - "\n", - "@dsl.pipeline(\n", - " name='KServe pipeline',\n", - " description='A pipeline for KServe.'\n", + "kserve_op = components.load_component_from_url(\n", + " \"https://raw.githubusercontent.com/kubeflow/pipelines/master/components/kserve/component.yaml\"\n", ")\n", + "\n", + "\n", + "@dsl.pipeline(name=\"KServe pipeline\", description=\"A pipeline for KServe.\")\n", "def kservePipeline(\n", - " action='apply',\n", - " model_name='tensorflow-sample',\n", - " model_uri='gs://kfserving-examples/models/tensorflow/flowers',\n", - " namespace='anonymous',\n", - " framework='tensorflow'):\n", + " action=\"apply\",\n", + " model_name=\"tensorflow-sample\",\n", + " model_uri=\"gs://kfserving-examples/models/tensorflow/flowers\",\n", + " namespace=\"anonymous\",\n", + " framework=\"tensorflow\",\n", + "):\n", + "\n", + " kserve = kserve_op(\n", + " action=action,\n", + " model_name=model_name,\n", + " model_uri=model_uri,\n", + " namespace=namespace,\n", + " framework=framework,\n", + " ).set_image_pull_policy(\"Always\")\n", "\n", - " kserve = kserve_op(action = action,\n", - " model_name=model_name,\n", - " model_uri=model_uri,\n", - " namespace=namespace,\n", - " framework=framework).set_image_pull_policy('Always')\n", "\n", "# Compile pipeline\n", - "compiler.Compiler().compile(kservePipeline, 'tf-flower.tar.gz')\n", + "compiler.Compiler().compile(kservePipeline, \"tf-flower.tar.gz\")\n", "\n", "# Execute pipeline\n", - "run = client.run_pipeline(experiment.id, 'tf-flower', 'tf-flower.tar.gz')" + "run = client.run_pipeline(experiment.id, \"tf-flower\", \"tf-flower.tar.gz\")" ] }, { @@ -98,29 +102,32 @@ "outputs": [], "source": [ "# kfserving_op = components.load_component_from_url('https://raw.githubusercontent.com/kubeflow/pipelines/master/components/kubeflow/kfserving/component.yaml')\n", - "kserve_op = components.load_component_from_url('https://raw.githubusercontent.com/kubeflow/pipelines/master/components/kserve/component.yaml')\n", - "\n", - "@dsl.pipeline(\n", - " name='KServe pipeline',\n", - " description='A pipeline for KServe.'\n", + "kserve_op = components.load_component_from_url(\n", + " \"https://raw.githubusercontent.com/kubeflow/pipelines/master/components/kserve/component.yaml\"\n", ")\n", + "\n", + "\n", + "@dsl.pipeline(name=\"KServe pipeline\", description=\"A pipeline for KServe.\")\n", "def kservePipeline(\n", - " action='apply',\n", - " model_name='max-image-segmenter',\n", - " namespace='anonymous',\n", - " custom_model_spec='{\"name\": \"image-segmenter\", \"image\": \"codait/max-image-segmenter:latest\", \"port\": \"5000\"}'\n", + " action=\"apply\",\n", + " model_name=\"max-image-segmenter\",\n", + " namespace=\"anonymous\",\n", + " custom_model_spec='{\"name\": \"image-segmenter\", \"image\": \"codait/max-image-segmenter:latest\", \"port\": \"5000\"}',\n", "):\n", "\n", - " kserve = kserve_op(action=action,\n", - " model_name=model_name,\n", - " namespace=namespace,\n", - " custom_model_spec=custom_model_spec).set_image_pull_policy('Always')\n", + " kserve = kserve_op(\n", + " action=action,\n", + " model_name=model_name,\n", + " namespace=namespace,\n", + " custom_model_spec=custom_model_spec,\n", + " ).set_image_pull_policy(\"Always\")\n", + "\n", "\n", "# Compile pipeline\n", - "compiler.Compiler().compile(kservePipeline, 'custom.tar.gz')\n", + "compiler.Compiler().compile(kservePipeline, \"custom.tar.gz\")\n", "\n", "# Execute pipeline\n", - "run = client.run_pipeline(experiment.id, 'custom-model', 'custom.tar.gz')" + "run = client.run_pipeline(experiment.id, \"custom-model\", \"custom.tar.gz\")" ] } ], diff --git a/docs/samples/v1beta1/custom/torchserve/logger/logger_demo.ipynb b/docs/samples/v1beta1/custom/torchserve/logger/logger_demo.ipynb index e23b1e4aca..9581c8efae 100644 --- a/docs/samples/v1beta1/custom/torchserve/logger/logger_demo.ipynb +++ b/docs/samples/v1beta1/custom/torchserve/logger/logger_demo.ipynb @@ -117,8 +117,8 @@ } ], "source": [ - "CLUSTER_IPS=!(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')\n", - "CLUSTER_IP=CLUSTER_IPS[0]\n", + "CLUSTER_IPS = !(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')\n", + "CLUSTER_IP = CLUSTER_IPS[0]\n", "print(CLUSTER_IP)" ] }, @@ -136,8 +136,8 @@ } ], "source": [ - "SERVICE_HOSTNAMES=!(kubectl get inferenceservice torchserve-custom -n kfserving-test -o jsonpath='{.status.url}' | cut -d \"/\" -f 3)\n", - "SERVICE_HOSTNAME=SERVICE_HOSTNAMES[0]\n", + "SERVICE_HOSTNAMES = !(kubectl get inferenceservice torchserve-custom -n kfserving-test -o jsonpath='{.status.url}' | cut -d \"/\" -f 3)\n", + "SERVICE_HOSTNAME = SERVICE_HOSTNAMES[0]\n", "print(SERVICE_HOSTNAME)" ] }, @@ -241,7 +241,7 @@ } ], "source": [ - "!kubectl logs $(kubectl get pod -l serving.knative.dev/configuration=message-dumper -n default -o jsonpath='{.items[0].metadata.name}') -c user-container " + "!kubectl logs $(kubectl get pod -l serving.knative.dev/configuration=message-dumper -n default -o jsonpath='{.items[0].metadata.name}') -c user-container" ] }, { diff --git a/docs/samples/v1beta1/onnx/mosaic-onnx.ipynb b/docs/samples/v1beta1/onnx/mosaic-onnx.ipynb index 4b1658dc1c..c0f4fe5751 100644 --- a/docs/samples/v1beta1/onnx/mosaic-onnx.ipynb +++ b/docs/samples/v1beta1/onnx/mosaic-onnx.ipynb @@ -84,7 +84,7 @@ ], "source": [ "# preprocess image data\n", - "norm_img_data = np.array(image).astype('float32')\n", + "norm_img_data = np.array(image).astype(\"float32\")\n", "norm_img_data = np.transpose(norm_img_data, [2, 0, 1])\n", "norm_img_data = np.expand_dims(norm_img_data, axis=0)\n", "np.shape(norm_img_data)" @@ -100,10 +100,10 @@ "message_data = {}\n", "inputs = {}\n", "message_data[\"inputs\"] = []\n", - "inputs[\"name\"]=\"input1\"\n", - "inputs[\"shape\"]=norm_img_data.shape\n", - "inputs[\"datatype\"]=\"FP32\" # as the given onnx model expects float32\n", - "inputs[\"data\"]=norm_img_data.tolist()\n", + "inputs[\"name\"] = \"input1\"\n", + "inputs[\"shape\"] = norm_img_data.shape\n", + "inputs[\"datatype\"] = \"FP32\" # as the given onnx model expects float32\n", + "inputs[\"data\"] = norm_img_data.tolist()\n", "message_data[\"inputs\"].append(inputs)" ] }, @@ -123,13 +123,19 @@ "source": [ "# Call predictor\n", "\n", - "service_hostname=os.environ[\"SERVICE_HOSTNAME\"]\n", - "model_name=os.environ[\"MODEL_NAME\"]\n", - "ingress_ip=\"localhost\"\n", - "ingress_port=os.environ[\"INGRESS_PORT\"]\n", + "service_hostname = os.environ[\"SERVICE_HOSTNAME\"]\n", + "model_name = os.environ[\"MODEL_NAME\"]\n", + "ingress_ip = \"localhost\"\n", + "ingress_port = os.environ[\"INGRESS_PORT\"]\n", "predictor_url = f\"http://{ingress_ip}:{ingress_port}/v2/models/{model_name}/infer\"\n", - "request_headers = {'Content-Type': 'application/json', 'Accept': 'application/json', 'Host': service_hostname}\n", - "response = requests.post(predictor_url, headers=request_headers, data=json.dumps(message_data))\n", + "request_headers = {\n", + " \"Content-Type\": \"application/json\",\n", + " \"Accept\": \"application/json\",\n", + " \"Host\": service_hostname,\n", + "}\n", + "response = requests.post(\n", + " predictor_url, headers=request_headers, data=json.dumps(message_data)\n", + ")\n", "print(response.status_code)" ] }, @@ -140,8 +146,8 @@ "outputs": [], "source": [ "response_message = json.loads(response.text)\n", - "output1 = np.array(response_message[\"outputs\"][0]['data'], dtype=np.float32)\n", - "output1 = output1.reshape(3,224,224)" + "output1 = np.array(response_message[\"outputs\"][0][\"data\"], dtype=np.float32)\n", + "output1 = output1.reshape(3, 224, 224)" ] }, { @@ -165,7 +171,7 @@ "source": [ "# postprocess\n", "result = np.clip(output1, 0, 255)\n", - "result = result.transpose(1,2,0).astype(\"uint8\")\n", + "result = result.transpose(1, 2, 0).astype(\"uint8\")\n", "img = Image.fromarray(result)\n", "img" ] diff --git a/docs/samples/v1beta1/sklearn/v1/sklearn-mixedtype-model/sklearn-mixedtype-model.ipynb b/docs/samples/v1beta1/sklearn/v1/sklearn-mixedtype-model/sklearn-mixedtype-model.ipynb index da8f94f8a5..4cb79d0277 100644 --- a/docs/samples/v1beta1/sklearn/v1/sklearn-mixedtype-model/sklearn-mixedtype-model.ipynb +++ b/docs/samples/v1beta1/sklearn/v1/sklearn-mixedtype-model/sklearn-mixedtype-model.ipynb @@ -36,16 +36,18 @@ "metadata": {}, "outputs": [], "source": [ - "features = [\"MSZoning\",\n", - " \"LotArea\",\n", - " \"LotShape\",\n", - " \"Utilities\",\n", - " \"YrSold\",\n", - " \"Neighborhood\",\n", - " \"OverallQual\",\n", - " \"YearBuilt\",\n", - " \"SaleType\",\n", - " \"GarageArea\"]" + "features = [\n", + " \"MSZoning\",\n", + " \"LotArea\",\n", + " \"LotShape\",\n", + " \"Utilities\",\n", + " \"YrSold\",\n", + " \"Neighborhood\",\n", + " \"OverallQual\",\n", + " \"YearBuilt\",\n", + " \"SaleType\",\n", + " \"GarageArea\",\n", + "]" ] }, { @@ -69,33 +71,33 @@ "outputs": [], "source": [ "p = Pipeline(\n", - " [\n", - " (\"dicttodf\", DictToDFTransformer()),\n", - " (\n", - " \"preprocess\",\n", - " ColumnTransformer(\n", - " [\n", - " (\n", - " \"numerical\",\n", - " make_pipeline(\n", - " SimpleImputer(strategy=\"mean\"),\n", - " StandardScaler(),\n", - " ),\n", - " sorted(numerical_features.columns),\n", + " [\n", + " (\"dicttodf\", DictToDFTransformer()),\n", + " (\n", + " \"preprocess\",\n", + " ColumnTransformer(\n", + " [\n", + " (\n", + " \"numerical\",\n", + " make_pipeline(\n", + " SimpleImputer(strategy=\"mean\"),\n", + " StandardScaler(),\n", " ),\n", - " (\n", - " \"categorical\",\n", - " make_pipeline(\n", - " SimpleImputer(strategy=\"most_frequent\"),\n", - " OneHotEncoder(handle_unknown=\"ignore\", sparse=False),\n", - " ),\n", - " sorted(categorical_features.columns),\n", + " sorted(numerical_features.columns),\n", + " ),\n", + " (\n", + " \"categorical\",\n", + " make_pipeline(\n", + " SimpleImputer(strategy=\"most_frequent\"),\n", + " OneHotEncoder(handle_unknown=\"ignore\", sparse=False),\n", " ),\n", - " ]\n", - " ),\n", + " sorted(categorical_features.columns),\n", + " ),\n", + " ]\n", " ),\n", - " (\"regressor\", SGDRegressor(random_state=666, learning_rate=\"adaptive\")),\n", - " ]\n", + " ),\n", + " (\"regressor\", SGDRegressor(random_state=666, learning_rate=\"adaptive\")),\n", + " ]\n", ")" ] }, @@ -230,7 +232,7 @@ } ], "source": [ - "sample_request = df[cols].head(1).to_dict('records')\n", + "sample_request = df[cols].head(1).to_dict(\"records\")\n", "sample_request" ] }, @@ -241,7 +243,20 @@ "metadata": {}, "outputs": [], "source": [ - "request = [{'MSZoning': 'RL', 'LotArea': 8450, 'LotShape': 'Reg', 'Utilities': 'AllPub', 'YrSold': 2008, 'Neighborhood': 'CollgCr', 'OverallQual': 7, 'YearBuilt': 2003, 'SaleType': 'WD', 'GarageArea': 548}]\n", + "request = [\n", + " {\n", + " \"MSZoning\": \"RL\",\n", + " \"LotArea\": 8450,\n", + " \"LotShape\": \"Reg\",\n", + " \"Utilities\": \"AllPub\",\n", + " \"YrSold\": 2008,\n", + " \"Neighborhood\": \"CollgCr\",\n", + " \"OverallQual\": 7,\n", + " \"YearBuilt\": 2003,\n", + " \"SaleType\": \"WD\",\n", + " \"GarageArea\": 548,\n", + " }\n", + "]\n", "response = model.predict(sample_request)" ] }, diff --git a/pkg/apis/serving/v1beta1/component.go b/pkg/apis/serving/v1beta1/component.go index 5424534adc..bf0d937e00 100644 --- a/pkg/apis/serving/v1beta1/component.go +++ b/pkg/apis/serving/v1beta1/component.go @@ -23,6 +23,7 @@ import ( "github.com/kserve/kserve/pkg/constants" "github.com/kserve/kserve/pkg/utils" + appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -106,6 +107,10 @@ type ComponentExtensionSpec struct { // More info: http://kubernetes.io/docs/user-guide/annotations // +optional Annotations map[string]string `json:"annotations,omitempty"` + + // The deployment strategy to use to replace existing pods with new ones. Only applicable for raw deployment mode. + // +optional + DeploymentStrategy *appsv1.DeploymentStrategy `json:"deploymentStrategy,omitempty"` } // ScaleMetric enum diff --git a/pkg/apis/serving/v1beta1/inference_service_validation.go b/pkg/apis/serving/v1beta1/inference_service_validation.go index c9c033f094..46ad38c940 100644 --- a/pkg/apis/serving/v1beta1/inference_service_validation.go +++ b/pkg/apis/serving/v1beta1/inference_service_validation.go @@ -224,6 +224,9 @@ func validateKPAMetrics(metric ScaleMetric) error { } func validateScalingKPACompExtension(compExtSpec *ComponentExtensionSpec) error { + if compExtSpec.DeploymentStrategy != nil { + return fmt.Errorf("customizing deploymentStrategy is only supported for raw deployment mode") + } metric := MetricConcurrency if compExtSpec.ScaleMetric != nil { metric = *compExtSpec.ScaleMetric diff --git a/pkg/apis/serving/v1beta1/inference_service_validation_test.go b/pkg/apis/serving/v1beta1/inference_service_validation_test.go index a73221c2c1..048cdd1e39 100644 --- a/pkg/apis/serving/v1beta1/inference_service_validation_test.go +++ b/pkg/apis/serving/v1beta1/inference_service_validation_test.go @@ -23,6 +23,7 @@ import ( "google.golang.org/protobuf/proto" "github.com/onsi/gomega" + appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -141,6 +142,18 @@ func TestRejectMultipleModelSpecs(t *testing.T) { g.Expect(warnings).Should(gomega.BeEmpty()) } +func TestCustomizeDeploymentStrategyUnsupportedForServerless(t *testing.T) { + g := gomega.NewGomegaWithT(t) + isvc := makeTestInferenceService() + isvc.Spec.Predictor.PodSpec = PodSpec{ServiceAccountName: "test"} + isvc.Spec.Predictor.DeploymentStrategy = &appsv1.DeploymentStrategy{ + Type: appsv1.RecreateDeploymentStrategyType, + } + warnings, err := isvc.ValidateCreate() + g.Expect(err).Should(gomega.MatchError("customizing deploymentStrategy is only supported for raw deployment mode")) + g.Expect(warnings).Should(gomega.BeEmpty()) +} + func TestModelSpecAndCustomOverridesIsValid(t *testing.T) { g := gomega.NewGomegaWithT(t) isvc := makeTestInferenceService() diff --git a/pkg/apis/serving/v1beta1/openapi_generated.go b/pkg/apis/serving/v1beta1/openapi_generated.go index 2148f20b68..7d82faec4c 100644 --- a/pkg/apis/serving/v1beta1/openapi_generated.go +++ b/pkg/apis/serving/v1beta1/openapi_generated.go @@ -2263,11 +2263,17 @@ func schema_pkg_apis_serving_v1beta1_ComponentExtensionSpec(ref common.Reference }, }, }, + "deploymentStrategy": { + SchemaProps: spec.SchemaProps{ + Description: "The deployment strategy to use to replace existing pods with new ones. Only applicable for raw deployment mode.", + Ref: ref("k8s.io/api/apps/v1.DeploymentStrategy"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/kserve/kserve/pkg/apis/serving/v1beta1.Batcher", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.LoggerSpec"}, + "github.com/kserve/kserve/pkg/apis/serving/v1beta1.Batcher", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.LoggerSpec", "k8s.io/api/apps/v1.DeploymentStrategy"}, } } @@ -4603,11 +4609,17 @@ func schema_pkg_apis_serving_v1beta1_ExplainerSpec(ref common.ReferenceCallback) }, }, }, + "deploymentStrategy": { + SchemaProps: spec.SchemaProps{ + Description: "The deployment strategy to use to replace existing pods with new ones. Only applicable for raw deployment mode.", + Ref: ref("k8s.io/api/apps/v1.DeploymentStrategy"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/kserve/kserve/pkg/apis/serving/v1beta1.ARTExplainerSpec", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.AlibiExplainerSpec", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.Batcher", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.LoggerSpec", "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.Container", "k8s.io/api/core/v1.EphemeralContainer", "k8s.io/api/core/v1.HostAlias", "k8s.io/api/core/v1.LocalObjectReference", "k8s.io/api/core/v1.PodDNSConfig", "k8s.io/api/core/v1.PodOS", "k8s.io/api/core/v1.PodReadinessGate", "k8s.io/api/core/v1.PodResourceClaim", "k8s.io/api/core/v1.PodSchedulingGate", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration", "k8s.io/api/core/v1.TopologySpreadConstraint", "k8s.io/api/core/v1.Volume", "k8s.io/apimachinery/pkg/api/resource.Quantity"}, + "github.com/kserve/kserve/pkg/apis/serving/v1beta1.ARTExplainerSpec", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.AlibiExplainerSpec", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.Batcher", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.LoggerSpec", "k8s.io/api/apps/v1.DeploymentStrategy", "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.Container", "k8s.io/api/core/v1.EphemeralContainer", "k8s.io/api/core/v1.HostAlias", "k8s.io/api/core/v1.LocalObjectReference", "k8s.io/api/core/v1.PodDNSConfig", "k8s.io/api/core/v1.PodOS", "k8s.io/api/core/v1.PodReadinessGate", "k8s.io/api/core/v1.PodResourceClaim", "k8s.io/api/core/v1.PodSchedulingGate", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration", "k8s.io/api/core/v1.TopologySpreadConstraint", "k8s.io/api/core/v1.Volume", "k8s.io/apimachinery/pkg/api/resource.Quantity"}, } } @@ -8325,11 +8337,17 @@ func schema_pkg_apis_serving_v1beta1_PredictorSpec(ref common.ReferenceCallback) }, }, }, + "deploymentStrategy": { + SchemaProps: spec.SchemaProps{ + Description: "The deployment strategy to use to replace existing pods with new ones. Only applicable for raw deployment mode.", + Ref: ref("k8s.io/api/apps/v1.DeploymentStrategy"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/kserve/kserve/pkg/apis/serving/v1beta1.Batcher", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.HuggingFaceRuntimeSpec", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.LightGBMSpec", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.LoggerSpec", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.ModelSpec", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.ONNXRuntimeSpec", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.PMMLSpec", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.PaddleServerSpec", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.SKLearnSpec", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.TFServingSpec", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.TorchServeSpec", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.TritonSpec", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.XGBoostSpec", "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.Container", "k8s.io/api/core/v1.EphemeralContainer", "k8s.io/api/core/v1.HostAlias", "k8s.io/api/core/v1.LocalObjectReference", "k8s.io/api/core/v1.PodDNSConfig", "k8s.io/api/core/v1.PodOS", "k8s.io/api/core/v1.PodReadinessGate", "k8s.io/api/core/v1.PodResourceClaim", "k8s.io/api/core/v1.PodSchedulingGate", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration", "k8s.io/api/core/v1.TopologySpreadConstraint", "k8s.io/api/core/v1.Volume", "k8s.io/apimachinery/pkg/api/resource.Quantity"}, + "github.com/kserve/kserve/pkg/apis/serving/v1beta1.Batcher", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.HuggingFaceRuntimeSpec", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.LightGBMSpec", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.LoggerSpec", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.ModelSpec", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.ONNXRuntimeSpec", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.PMMLSpec", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.PaddleServerSpec", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.SKLearnSpec", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.TFServingSpec", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.TorchServeSpec", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.TritonSpec", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.XGBoostSpec", "k8s.io/api/apps/v1.DeploymentStrategy", "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.Container", "k8s.io/api/core/v1.EphemeralContainer", "k8s.io/api/core/v1.HostAlias", "k8s.io/api/core/v1.LocalObjectReference", "k8s.io/api/core/v1.PodDNSConfig", "k8s.io/api/core/v1.PodOS", "k8s.io/api/core/v1.PodReadinessGate", "k8s.io/api/core/v1.PodResourceClaim", "k8s.io/api/core/v1.PodSchedulingGate", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration", "k8s.io/api/core/v1.TopologySpreadConstraint", "k8s.io/api/core/v1.Volume", "k8s.io/apimachinery/pkg/api/resource.Quantity"}, } } @@ -9813,11 +9831,17 @@ func schema_pkg_apis_serving_v1beta1_TransformerSpec(ref common.ReferenceCallbac }, }, }, + "deploymentStrategy": { + SchemaProps: spec.SchemaProps{ + Description: "The deployment strategy to use to replace existing pods with new ones. Only applicable for raw deployment mode.", + Ref: ref("k8s.io/api/apps/v1.DeploymentStrategy"), + }, + }, }, }, }, Dependencies: []string{ - "github.com/kserve/kserve/pkg/apis/serving/v1beta1.Batcher", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.LoggerSpec", "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.Container", "k8s.io/api/core/v1.EphemeralContainer", "k8s.io/api/core/v1.HostAlias", "k8s.io/api/core/v1.LocalObjectReference", "k8s.io/api/core/v1.PodDNSConfig", "k8s.io/api/core/v1.PodOS", "k8s.io/api/core/v1.PodReadinessGate", "k8s.io/api/core/v1.PodResourceClaim", "k8s.io/api/core/v1.PodSchedulingGate", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration", "k8s.io/api/core/v1.TopologySpreadConstraint", "k8s.io/api/core/v1.Volume", "k8s.io/apimachinery/pkg/api/resource.Quantity"}, + "github.com/kserve/kserve/pkg/apis/serving/v1beta1.Batcher", "github.com/kserve/kserve/pkg/apis/serving/v1beta1.LoggerSpec", "k8s.io/api/apps/v1.DeploymentStrategy", "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.Container", "k8s.io/api/core/v1.EphemeralContainer", "k8s.io/api/core/v1.HostAlias", "k8s.io/api/core/v1.LocalObjectReference", "k8s.io/api/core/v1.PodDNSConfig", "k8s.io/api/core/v1.PodOS", "k8s.io/api/core/v1.PodReadinessGate", "k8s.io/api/core/v1.PodResourceClaim", "k8s.io/api/core/v1.PodSchedulingGate", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration", "k8s.io/api/core/v1.TopologySpreadConstraint", "k8s.io/api/core/v1.Volume", "k8s.io/apimachinery/pkg/api/resource.Quantity"}, } } diff --git a/pkg/apis/serving/v1beta1/swagger.json b/pkg/apis/serving/v1beta1/swagger.json index 4d1209cd7e..cff2c8ce73 100644 --- a/pkg/apis/serving/v1beta1/swagger.json +++ b/pkg/apis/serving/v1beta1/swagger.json @@ -1186,6 +1186,10 @@ "type": "integer", "format": "int64" }, + "deploymentStrategy": { + "description": "The deployment strategy to use to replace existing pods with new ones. Only applicable for raw deployment mode.", + "$ref": "#/definitions/k8s.io.api.apps.v1.DeploymentStrategy" + }, "labels": { "description": "Labels that will be add to the component pod. More info: http://kubernetes.io/docs/user-guide/labels", "type": "object", @@ -2277,6 +2281,10 @@ "x-kubernetes-patch-merge-key": "name", "x-kubernetes-patch-strategy": "merge" }, + "deploymentStrategy": { + "description": "The deployment strategy to use to replace existing pods with new ones. Only applicable for raw deployment mode.", + "$ref": "#/definitions/k8s.io.api.apps.v1.DeploymentStrategy" + }, "dnsConfig": { "description": "Specifies the DNS parameters of a pod. Parameters specified here will be merged to the generated DNS configuration based on DNSPolicy.", "$ref": "#/definitions/v1.PodDNSConfig" @@ -4312,6 +4320,10 @@ "x-kubernetes-patch-merge-key": "name", "x-kubernetes-patch-strategy": "merge" }, + "deploymentStrategy": { + "description": "The deployment strategy to use to replace existing pods with new ones. Only applicable for raw deployment mode.", + "$ref": "#/definitions/k8s.io.api.apps.v1.DeploymentStrategy" + }, "dnsConfig": { "description": "Specifies the DNS parameters of a pod. Parameters specified here will be merged to the generated DNS configuration based on DNSPolicy.", "$ref": "#/definitions/v1.PodDNSConfig" @@ -5185,6 +5197,10 @@ "x-kubernetes-patch-merge-key": "name", "x-kubernetes-patch-strategy": "merge" }, + "deploymentStrategy": { + "description": "The deployment strategy to use to replace existing pods with new ones. Only applicable for raw deployment mode.", + "$ref": "#/definitions/k8s.io.api.apps.v1.DeploymentStrategy" + }, "dnsConfig": { "description": "Specifies the DNS parameters of a pod. Parameters specified here will be merged to the generated DNS configuration based on DNSPolicy.", "$ref": "#/definitions/v1.PodDNSConfig" diff --git a/pkg/apis/serving/v1beta1/zz_generated.deepcopy.go b/pkg/apis/serving/v1beta1/zz_generated.deepcopy.go index 01d4bceaed..45e0b731a0 100644 --- a/pkg/apis/serving/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/serving/v1beta1/zz_generated.deepcopy.go @@ -23,10 +23,11 @@ package v1beta1 import ( "github.com/kserve/kserve/pkg/constants" + "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" "knative.dev/pkg/apis" - "knative.dev/pkg/apis/duck/v1" + duckv1 "knative.dev/pkg/apis/duck/v1" servingv1 "knative.dev/serving/pkg/apis/serving/v1" ) @@ -149,6 +150,11 @@ func (in *ComponentExtensionSpec) DeepCopyInto(out *ComponentExtensionSpec) { (*out)[key] = val } } + if in.DeploymentStrategy != nil { + in, out := &in.DeploymentStrategy, &out.DeploymentStrategy + *out = new(v1.DeploymentStrategy) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentExtensionSpec. @@ -188,7 +194,7 @@ func (in *ComponentStatusSpec) DeepCopyInto(out *ComponentStatusSpec) { } if in.Address != nil { in, out := &in.Address, &out.Address - *out = new(v1.Addressable) + *out = new(duckv1.Addressable) (*in).DeepCopyInto(*out) } } @@ -437,7 +443,7 @@ func (in *InferenceServiceStatus) DeepCopyInto(out *InferenceServiceStatus) { in.Status.DeepCopyInto(&out.Status) if in.Address != nil { in, out := &in.Address, &out.Address - *out = new(v1.Addressable) + *out = new(duckv1.Addressable) (*in).DeepCopyInto(*out) } if in.URL != nil { diff --git a/pkg/controller/v1beta1/inferenceservice/rawkube_controller_test.go b/pkg/controller/v1beta1/inferenceservice/rawkube_controller_test.go index a578aa8738..789983c704 100644 --- a/pkg/controller/v1beta1/inferenceservice/rawkube_controller_test.go +++ b/pkg/controller/v1beta1/inferenceservice/rawkube_controller_test.go @@ -503,6 +503,420 @@ var _ = Describe("v1beta1 inference service controller", func() { } Expect(actualHPA.Spec).To(gomega.Equal(expectedHPA.Spec)) }) + It("Should have ingress/service/deployment/hpa created with DeploymentStrategy", func() { + By("By creating a new InferenceService with DeploymentStrategy in PredictorSpec") + // Create configmap + var configMap = &v1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: constants.InferenceServiceConfigMapName, + Namespace: constants.KServeNamespace, + }, + Data: configs, + } + Expect(k8sClient.Create(context.TODO(), configMap)).NotTo(HaveOccurred()) + defer k8sClient.Delete(context.TODO(), configMap) + // Create ServingRuntime + servingRuntime := &v1alpha1.ServingRuntime{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tf-serving-raw", + Namespace: "default", + }, + Spec: v1alpha1.ServingRuntimeSpec{ + SupportedModelFormats: []v1alpha1.SupportedModelFormat{ + { + Name: "tensorflow", + Version: proto.String("1"), + AutoSelect: proto.Bool(true), + }, + }, + ServingRuntimePodSpec: v1alpha1.ServingRuntimePodSpec{ + Containers: []v1.Container{ + { + Name: "kserve-container", + Image: "tensorflow/serving:1.14.0", + Command: []string{"/usr/bin/tensorflow_model_server"}, + Args: []string{ + "--port=9000", + "--rest_api_port=8080", + "--model_base_path=/mnt/models", + "--rest_api_timeout_in_ms=60000", + }, + Resources: defaultResource, + }, + }, + }, + Disabled: proto.Bool(false), + }, + } + k8sClient.Create(context.TODO(), servingRuntime) + defer k8sClient.Delete(context.TODO(), servingRuntime) + serviceName := "raw-foo-customized" + var expectedRequest = reconcile.Request{NamespacedName: types.NamespacedName{Name: serviceName, Namespace: "default"}} + var serviceKey = expectedRequest.NamespacedName + var storageUri = "s3://test/mnist/export" + predictorDeploymentKey := types.NamespacedName{Name: constants.PredictorServiceName(serviceKey.Name), + Namespace: serviceKey.Namespace} + var replicas int32 = 1 + var revisionHistory int32 = 10 + var progressDeadlineSeconds int32 = 600 + var gracePeriod int64 = 30 + ctx := context.Background() + isvc := &v1beta1.InferenceService{ + ObjectMeta: metav1.ObjectMeta{ + Name: serviceKey.Name, + Namespace: serviceKey.Namespace, + Annotations: map[string]string{ + "serving.kserve.io/deploymentMode": "RawDeployment", + "serving.kserve.io/autoscalerClass": "hpa", + "serving.kserve.io/metrics": "cpu", + "serving.kserve.io/targetUtilizationPercentage": "75", + }, + }, + Spec: v1beta1.InferenceServiceSpec{ + Predictor: v1beta1.PredictorSpec{ + ComponentExtensionSpec: v1beta1.ComponentExtensionSpec{ + MinReplicas: v1beta1.GetIntReference(1), + MaxReplicas: 3, + DeploymentStrategy: &appsv1.DeploymentStrategy{ + Type: appsv1.RecreateDeploymentStrategyType, + }}, + Tensorflow: &v1beta1.TFServingSpec{ + PredictorExtensionSpec: v1beta1.PredictorExtensionSpec{ + StorageURI: &storageUri, + RuntimeVersion: proto.String("1.14.0"), + Container: v1.Container{ + Name: constants.InferenceServiceContainerName, + Resources: defaultResource, + }, + }, + }, + }, + }, + } + isvc.DefaultInferenceService(nil, nil) + Expect(k8sClient.Create(ctx, isvc)).Should(Succeed()) + + inferenceService := &v1beta1.InferenceService{} + + Eventually(func() bool { + err := k8sClient.Get(ctx, serviceKey, inferenceService) + if err != nil { + return false + } + return true + }, timeout, interval).Should(BeTrue()) + + actualDeployment := &appsv1.Deployment{} + + Eventually(func() error { return k8sClient.Get(context.TODO(), predictorDeploymentKey, actualDeployment) }, timeout). + Should(Succeed()) + + expectedDeployment := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: predictorDeploymentKey.Name, + Namespace: predictorDeploymentKey.Namespace, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: &replicas, + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "isvc." + predictorDeploymentKey.Name, + }, + }, + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Name: predictorDeploymentKey.Name, + Namespace: "default", + Labels: map[string]string{ + "app": "isvc." + predictorDeploymentKey.Name, + constants.KServiceComponentLabel: constants.Predictor.String(), + constants.InferenceServicePodLabelKey: serviceName, + }, + Annotations: map[string]string{ + constants.StorageInitializerSourceUriInternalAnnotationKey: *isvc.Spec.Predictor.Model.StorageURI, + "serving.kserve.io/deploymentMode": "RawDeployment", + "serving.kserve.io/autoscalerClass": "hpa", + "serving.kserve.io/metrics": "cpu", + "serving.kserve.io/targetUtilizationPercentage": "75", + }, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + { + Image: "tensorflow/serving:" + + *isvc.Spec.Predictor.Model.RuntimeVersion, + Name: constants.InferenceServiceContainerName, + Command: []string{v1beta1.TensorflowEntrypointCommand}, + Args: []string{ + "--port=" + v1beta1.TensorflowServingGRPCPort, + "--rest_api_port=" + v1beta1.TensorflowServingRestPort, + "--model_base_path=" + constants.DefaultModelLocalMountPath, + "--rest_api_timeout_in_ms=60000", + }, + Resources: defaultResource, + ReadinessProbe: &v1.Probe{ + ProbeHandler: v1.ProbeHandler{ + TCPSocket: &v1.TCPSocketAction{ + Port: intstr.IntOrString{ + IntVal: 8080, + }, + }, + }, + InitialDelaySeconds: 0, + TimeoutSeconds: 1, + PeriodSeconds: 10, + SuccessThreshold: 1, + FailureThreshold: 3, + }, + TerminationMessagePath: "/dev/termination-log", + TerminationMessagePolicy: "File", + ImagePullPolicy: "IfNotPresent", + }, + }, + SchedulerName: "default-scheduler", + RestartPolicy: "Always", + TerminationGracePeriodSeconds: &gracePeriod, + DNSPolicy: "ClusterFirst", + SecurityContext: &v1.PodSecurityContext{ + SELinuxOptions: nil, + WindowsOptions: nil, + RunAsUser: nil, + RunAsGroup: nil, + RunAsNonRoot: nil, + SupplementalGroups: nil, + FSGroup: nil, + Sysctls: nil, + FSGroupChangePolicy: nil, + SeccompProfile: nil, + }, + }, + }, + // This is now customized and different from defaults set via `setDefaultDeploymentSpec`. + Strategy: appsv1.DeploymentStrategy{ + Type: appsv1.RecreateDeploymentStrategyType, + }, + RevisionHistoryLimit: &revisionHistory, + ProgressDeadlineSeconds: &progressDeadlineSeconds, + }, + } + Expect(actualDeployment.Spec).To(gomega.Equal(expectedDeployment.Spec)) + + //check service + actualService := &v1.Service{} + predictorServiceKey := types.NamespacedName{Name: constants.PredictorServiceName(serviceKey.Name), + Namespace: serviceKey.Namespace} + Eventually(func() error { return k8sClient.Get(context.TODO(), predictorServiceKey, actualService) }, timeout). + Should(Succeed()) + + expectedService := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: predictorServiceKey.Name, + Namespace: predictorServiceKey.Namespace, + }, + Spec: v1.ServiceSpec{ + Ports: []v1.ServicePort{ + { + Name: constants.PredictorServiceName(serviceName), + Protocol: "TCP", + Port: 80, + TargetPort: intstr.IntOrString{Type: 0, IntVal: 8080, StrVal: ""}, + }, + }, + Type: "ClusterIP", + SessionAffinity: "None", + Selector: map[string]string{ + "app": fmt.Sprintf("isvc.%s", constants.PredictorServiceName(serviceName)), + }, + }, + } + actualService.Spec.ClusterIP = "" + actualService.Spec.ClusterIPs = nil + actualService.Spec.IPFamilies = nil + actualService.Spec.IPFamilyPolicy = nil + actualService.Spec.InternalTrafficPolicy = nil + Expect(actualService.Spec).To(gomega.Equal(expectedService.Spec)) + + //check isvc status + updatedDeployment := actualDeployment.DeepCopy() + updatedDeployment.Status.Conditions = []appsv1.DeploymentCondition{ + { + Type: appsv1.DeploymentAvailable, + Status: v1.ConditionTrue, + }, + } + Expect(k8sClient.Status().Update(context.TODO(), updatedDeployment)).NotTo(gomega.HaveOccurred()) + + //check ingress + pathType := netv1.PathTypePrefix + actualIngress := &netv1.Ingress{} + predictorIngressKey := types.NamespacedName{Name: serviceKey.Name, + Namespace: serviceKey.Namespace} + Eventually(func() error { return k8sClient.Get(context.TODO(), predictorIngressKey, actualIngress) }, timeout). + Should(Succeed()) + expectedIngress := netv1.Ingress{ + Spec: netv1.IngressSpec{ + Rules: []netv1.IngressRule{ + { + Host: "raw-foo-customized-default.example.com", + IngressRuleValue: netv1.IngressRuleValue{ + HTTP: &netv1.HTTPIngressRuleValue{ + Paths: []netv1.HTTPIngressPath{ + { + Path: "/", + PathType: &pathType, + Backend: netv1.IngressBackend{ + Service: &netv1.IngressServiceBackend{ + Name: "raw-foo-customized-predictor", + Port: netv1.ServiceBackendPort{ + Number: 80, + }, + }, + }, + }, + }, + }, + }, + }, + { + Host: "raw-foo-customized-predictor-default.example.com", + IngressRuleValue: netv1.IngressRuleValue{ + HTTP: &netv1.HTTPIngressRuleValue{ + Paths: []netv1.HTTPIngressPath{ + { + Path: "/", + PathType: &pathType, + Backend: netv1.IngressBackend{ + Service: &netv1.IngressServiceBackend{ + Name: "raw-foo-customized-predictor", + Port: netv1.ServiceBackendPort{ + Number: 80, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + Expect(actualIngress.Spec).To(gomega.Equal(expectedIngress.Spec)) + // verify if InferenceService status is updated + expectedIsvcStatus := v1beta1.InferenceServiceStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{ + { + Type: v1beta1.IngressReady, + Status: "True", + }, + { + Type: v1beta1.PredictorReady, + Status: "True", + }, + { + Type: apis.ConditionReady, + Status: "True", + }, + }, + }, + URL: &apis.URL{ + Scheme: "http", + Host: "raw-foo-customized-default.example.com", + }, + Address: &duckv1.Addressable{ + URL: &apis.URL{ + Scheme: "http", + Host: fmt.Sprintf("%s-predictor.%s.svc.cluster.local", serviceKey.Name, serviceKey.Namespace), + }, + }, + Components: map[v1beta1.ComponentType]v1beta1.ComponentStatusSpec{ + v1beta1.PredictorComponent: { + LatestCreatedRevision: "", + URL: &apis.URL{ + Scheme: "http", + Host: "raw-foo-customized-predictor-default.example.com", + }, + }, + }, + ModelStatus: v1beta1.ModelStatus{ + TransitionStatus: "InProgress", + ModelRevisionStates: &v1beta1.ModelRevisionStates{TargetModelState: "Pending"}, + }, + } + Eventually(func() string { + isvc := &v1beta1.InferenceService{} + if err := k8sClient.Get(context.TODO(), serviceKey, isvc); err != nil { + return err.Error() + } + return cmp.Diff(&expectedIsvcStatus, &isvc.Status, cmpopts.IgnoreTypes(apis.VolatileTime{})) + }, timeout).Should(gomega.BeEmpty()) + + //check HPA + var minReplicas int32 = 1 + var maxReplicas int32 = 3 + var cpuUtilization int32 = 75 + var stabilizationWindowSeconds int32 = 0 + selectPolicy := autoscalingv2.MaxChangePolicySelect + actualHPA := &autoscalingv2.HorizontalPodAutoscaler{} + predictorHPAKey := types.NamespacedName{Name: constants.PredictorServiceName(serviceKey.Name), + Namespace: serviceKey.Namespace} + Eventually(func() error { return k8sClient.Get(context.TODO(), predictorHPAKey, actualHPA) }, timeout). + Should(Succeed()) + expectedHPA := &autoscalingv2.HorizontalPodAutoscaler{ + Spec: autoscalingv2.HorizontalPodAutoscalerSpec{ + ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{ + APIVersion: "apps/v1", + Kind: "Deployment", + Name: constants.PredictorServiceName(serviceKey.Name), + }, + MinReplicas: &minReplicas, + MaxReplicas: maxReplicas, + Metrics: []autoscalingv2.MetricSpec{ + { + Type: autoscalingv2.ResourceMetricSourceType, + Resource: &autoscalingv2.ResourceMetricSource{ + Name: v1.ResourceCPU, + Target: autoscalingv2.MetricTarget{ + Type: "Utilization", + AverageUtilization: &cpuUtilization, + }, + }, + }, + }, + Behavior: &autoscalingv2.HorizontalPodAutoscalerBehavior{ + ScaleUp: &autoscalingv2.HPAScalingRules{ + StabilizationWindowSeconds: &stabilizationWindowSeconds, + SelectPolicy: &selectPolicy, + Policies: []autoscalingv2.HPAScalingPolicy{ + { + Type: "Pods", + Value: 4, + PeriodSeconds: 15, + }, + { + Type: "Percent", + Value: 100, + PeriodSeconds: 15, + }, + }, + }, + ScaleDown: &autoscalingv2.HPAScalingRules{ + StabilizationWindowSeconds: nil, + SelectPolicy: &selectPolicy, + Policies: []autoscalingv2.HPAScalingPolicy{ + { + Type: "Percent", + Value: 100, + PeriodSeconds: 15, + }, + }, + }, + }, + }, + } + Expect(actualHPA.Spec).To(gomega.Equal(expectedHPA.Spec)) + }) It("Should have ingress/service/deployment created", func() { By("By creating a new InferenceService with AutoscalerClassExternal") // Create configmap diff --git a/pkg/controller/v1beta1/inferenceservice/reconcilers/deployment/deployment_reconciler.go b/pkg/controller/v1beta1/inferenceservice/reconcilers/deployment/deployment_reconciler.go index 92a8f0f972..58efa664c3 100644 --- a/pkg/controller/v1beta1/inferenceservice/reconcilers/deployment/deployment_reconciler.go +++ b/pkg/controller/v1beta1/inferenceservice/reconcilers/deployment/deployment_reconciler.go @@ -77,6 +77,9 @@ func createRawDeployment(componentMeta metav1.ObjectMeta, }, }, } + if componentExt.DeploymentStrategy != nil { + deployment.Spec.Strategy = *componentExt.DeploymentStrategy + } setDefaultDeploymentSpec(&deployment.Spec) return deployment } @@ -182,7 +185,7 @@ func setDefaultDeploymentSpec(spec *appsv1.DeploymentSpec) { if spec.Strategy.Type == "" { spec.Strategy.Type = appsv1.RollingUpdateDeploymentStrategyType } - if spec.Strategy.RollingUpdate == nil { + if spec.Strategy.Type == appsv1.RollingUpdateDeploymentStrategyType && spec.Strategy.RollingUpdate == nil { spec.Strategy.RollingUpdate = &appsv1.RollingUpdateDeployment{ MaxUnavailable: &intstr.IntOrString{Type: intstr.String, StrVal: "25%"}, MaxSurge: &intstr.IntOrString{Type: intstr.String, StrVal: "25%"}, diff --git a/python/kserve/docs/V1beta1ComponentExtensionSpec.md b/python/kserve/docs/V1beta1ComponentExtensionSpec.md index a44bc4bcc8..3eeb1f170e 100644 --- a/python/kserve/docs/V1beta1ComponentExtensionSpec.md +++ b/python/kserve/docs/V1beta1ComponentExtensionSpec.md @@ -8,6 +8,7 @@ Name | Type | Description | Notes **batcher** | [**V1beta1Batcher**](V1beta1Batcher.md) | | [optional] **canary_traffic_percent** | **int** | CanaryTrafficPercent defines the traffic split percentage between the candidate revision and the last ready revision | [optional] **container_concurrency** | **int** | ContainerConcurrency specifies how many requests can be processed concurrently, this sets the hard limit of the container concurrency(https://knative.dev/docs/serving/autoscaling/concurrency). | [optional] +**deployment_strategy** | [**K8sIoApiAppsV1DeploymentStrategy**](K8sIoApiAppsV1DeploymentStrategy.md) | | [optional] **labels** | **dict(str, str)** | Labels that will be add to the component pod. More info: http://kubernetes.io/docs/user-guide/labels | [optional] **logger** | [**V1beta1LoggerSpec**](V1beta1LoggerSpec.md) | | [optional] **max_replicas** | **int** | Maximum number of replicas for autoscaling. | [optional] diff --git a/python/kserve/docs/V1beta1ExplainerSpec.md b/python/kserve/docs/V1beta1ExplainerSpec.md index 45809ed85e..c6607b2c95 100644 --- a/python/kserve/docs/V1beta1ExplainerSpec.md +++ b/python/kserve/docs/V1beta1ExplainerSpec.md @@ -14,6 +14,7 @@ Name | Type | Description | Notes **canary_traffic_percent** | **int** | CanaryTrafficPercent defines the traffic split percentage between the candidate revision and the last ready revision | [optional] **container_concurrency** | **int** | ContainerConcurrency specifies how many requests can be processed concurrently, this sets the hard limit of the container concurrency(https://knative.dev/docs/serving/autoscaling/concurrency). | [optional] **containers** | [**list[V1Container]**](https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/V1Container.md) | List of containers belonging to the pod. Containers cannot currently be added or removed. There must be at least one container in a Pod. Cannot be updated. | [optional] +**deployment_strategy** | [**K8sIoApiAppsV1DeploymentStrategy**](K8sIoApiAppsV1DeploymentStrategy.md) | | [optional] **dns_config** | [**V1PodDNSConfig**](https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/V1PodDNSConfig.md) | | [optional] **dns_policy** | **str** | Set DNS policy for the pod. Defaults to \"ClusterFirst\". Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'. | [optional] **enable_service_links** | **bool** | EnableServiceLinks indicates whether information about services should be injected into pod's environment variables, matching the syntax of Docker links. Optional: Defaults to true. | [optional] diff --git a/python/kserve/docs/V1beta1PredictorSpec.md b/python/kserve/docs/V1beta1PredictorSpec.md index 21af0d1d9a..74ef7b385d 100644 --- a/python/kserve/docs/V1beta1PredictorSpec.md +++ b/python/kserve/docs/V1beta1PredictorSpec.md @@ -12,6 +12,7 @@ Name | Type | Description | Notes **canary_traffic_percent** | **int** | CanaryTrafficPercent defines the traffic split percentage between the candidate revision and the last ready revision | [optional] **container_concurrency** | **int** | ContainerConcurrency specifies how many requests can be processed concurrently, this sets the hard limit of the container concurrency(https://knative.dev/docs/serving/autoscaling/concurrency). | [optional] **containers** | [**list[V1Container]**](https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/V1Container.md) | List of containers belonging to the pod. Containers cannot currently be added or removed. There must be at least one container in a Pod. Cannot be updated. | [optional] +**deployment_strategy** | [**K8sIoApiAppsV1DeploymentStrategy**](K8sIoApiAppsV1DeploymentStrategy.md) | | [optional] **dns_config** | [**V1PodDNSConfig**](https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/V1PodDNSConfig.md) | | [optional] **dns_policy** | **str** | Set DNS policy for the pod. Defaults to \"ClusterFirst\". Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'. | [optional] **enable_service_links** | **bool** | EnableServiceLinks indicates whether information about services should be injected into pod's environment variables, matching the syntax of Docker links. Optional: Defaults to true. | [optional] diff --git a/python/kserve/docs/V1beta1TransformerSpec.md b/python/kserve/docs/V1beta1TransformerSpec.md index 47fa9aa151..9988cb9063 100644 --- a/python/kserve/docs/V1beta1TransformerSpec.md +++ b/python/kserve/docs/V1beta1TransformerSpec.md @@ -12,6 +12,7 @@ Name | Type | Description | Notes **canary_traffic_percent** | **int** | CanaryTrafficPercent defines the traffic split percentage between the candidate revision and the last ready revision | [optional] **container_concurrency** | **int** | ContainerConcurrency specifies how many requests can be processed concurrently, this sets the hard limit of the container concurrency(https://knative.dev/docs/serving/autoscaling/concurrency). | [optional] **containers** | [**list[V1Container]**](https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/V1Container.md) | List of containers belonging to the pod. Containers cannot currently be added or removed. There must be at least one container in a Pod. Cannot be updated. | [optional] +**deployment_strategy** | [**K8sIoApiAppsV1DeploymentStrategy**](K8sIoApiAppsV1DeploymentStrategy.md) | | [optional] **dns_config** | [**V1PodDNSConfig**](https://github.com/kubernetes-client/python/blob/master/kubernetes/docs/V1PodDNSConfig.md) | | [optional] **dns_policy** | **str** | Set DNS policy for the pod. Defaults to \"ClusterFirst\". Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'. | [optional] **enable_service_links** | **bool** | EnableServiceLinks indicates whether information about services should be injected into pod's environment variables, matching the syntax of Docker links. Optional: Defaults to true. | [optional] diff --git a/python/kserve/kserve/models/v1beta1_component_extension_spec.py b/python/kserve/kserve/models/v1beta1_component_extension_spec.py index cd78e0264e..b92940ba65 100644 --- a/python/kserve/kserve/models/v1beta1_component_extension_spec.py +++ b/python/kserve/kserve/models/v1beta1_component_extension_spec.py @@ -51,6 +51,7 @@ class V1beta1ComponentExtensionSpec(object): 'batcher': 'V1beta1Batcher', 'canary_traffic_percent': 'int', 'container_concurrency': 'int', + 'deployment_strategy': 'K8sIoApiAppsV1DeploymentStrategy', 'labels': 'dict(str, str)', 'logger': 'V1beta1LoggerSpec', 'max_replicas': 'int', @@ -65,6 +66,7 @@ class V1beta1ComponentExtensionSpec(object): 'batcher': 'batcher', 'canary_traffic_percent': 'canaryTrafficPercent', 'container_concurrency': 'containerConcurrency', + 'deployment_strategy': 'deploymentStrategy', 'labels': 'labels', 'logger': 'logger', 'max_replicas': 'maxReplicas', @@ -74,7 +76,7 @@ class V1beta1ComponentExtensionSpec(object): 'timeout': 'timeout' } - def __init__(self, annotations=None, batcher=None, canary_traffic_percent=None, container_concurrency=None, labels=None, logger=None, max_replicas=None, min_replicas=None, scale_metric=None, scale_target=None, timeout=None, local_vars_configuration=None): # noqa: E501 + def __init__(self, annotations=None, batcher=None, canary_traffic_percent=None, container_concurrency=None, deployment_strategy=None, labels=None, logger=None, max_replicas=None, min_replicas=None, scale_metric=None, scale_target=None, timeout=None, local_vars_configuration=None): # noqa: E501 """V1beta1ComponentExtensionSpec - a model defined in OpenAPI""" # noqa: E501 if local_vars_configuration is None: local_vars_configuration = Configuration() @@ -84,6 +86,7 @@ def __init__(self, annotations=None, batcher=None, canary_traffic_percent=None, self._batcher = None self._canary_traffic_percent = None self._container_concurrency = None + self._deployment_strategy = None self._labels = None self._logger = None self._max_replicas = None @@ -101,6 +104,8 @@ def __init__(self, annotations=None, batcher=None, canary_traffic_percent=None, self.canary_traffic_percent = canary_traffic_percent if container_concurrency is not None: self.container_concurrency = container_concurrency + if deployment_strategy is not None: + self.deployment_strategy = deployment_strategy if labels is not None: self.labels = labels if logger is not None: @@ -206,6 +211,27 @@ def container_concurrency(self, container_concurrency): self._container_concurrency = container_concurrency + @property + def deployment_strategy(self): + """Gets the deployment_strategy of this V1beta1ComponentExtensionSpec. # noqa: E501 + + + :return: The deployment_strategy of this V1beta1ComponentExtensionSpec. # noqa: E501 + :rtype: K8sIoApiAppsV1DeploymentStrategy + """ + return self._deployment_strategy + + @deployment_strategy.setter + def deployment_strategy(self, deployment_strategy): + """Sets the deployment_strategy of this V1beta1ComponentExtensionSpec. + + + :param deployment_strategy: The deployment_strategy of this V1beta1ComponentExtensionSpec. # noqa: E501 + :type: K8sIoApiAppsV1DeploymentStrategy + """ + + self._deployment_strategy = deployment_strategy + @property def labels(self): """Gets the labels of this V1beta1ComponentExtensionSpec. # noqa: E501 diff --git a/python/kserve/kserve/models/v1beta1_explainer_spec.py b/python/kserve/kserve/models/v1beta1_explainer_spec.py index fdab1803db..dec7b98db8 100644 --- a/python/kserve/kserve/models/v1beta1_explainer_spec.py +++ b/python/kserve/kserve/models/v1beta1_explainer_spec.py @@ -57,6 +57,7 @@ class V1beta1ExplainerSpec(object): 'canary_traffic_percent': 'int', 'container_concurrency': 'int', 'containers': 'list[V1Container]', + 'deployment_strategy': 'K8sIoApiAppsV1DeploymentStrategy', 'dns_config': 'V1PodDNSConfig', 'dns_policy': 'str', 'enable_service_links': 'bool', @@ -112,6 +113,7 @@ class V1beta1ExplainerSpec(object): 'canary_traffic_percent': 'canaryTrafficPercent', 'container_concurrency': 'containerConcurrency', 'containers': 'containers', + 'deployment_strategy': 'deploymentStrategy', 'dns_config': 'dnsConfig', 'dns_policy': 'dnsPolicy', 'enable_service_links': 'enableServiceLinks', @@ -156,7 +158,7 @@ class V1beta1ExplainerSpec(object): 'volumes': 'volumes' } - def __init__(self, active_deadline_seconds=None, affinity=None, alibi=None, annotations=None, art=None, automount_service_account_token=None, batcher=None, canary_traffic_percent=None, container_concurrency=None, containers=None, dns_config=None, dns_policy=None, enable_service_links=None, ephemeral_containers=None, host_aliases=None, host_ipc=None, host_network=None, host_pid=None, host_users=None, hostname=None, image_pull_secrets=None, init_containers=None, labels=None, logger=None, max_replicas=None, min_replicas=None, node_name=None, node_selector=None, os=None, overhead=None, preemption_policy=None, priority=None, priority_class_name=None, readiness_gates=None, resource_claims=None, restart_policy=None, runtime_class_name=None, scale_metric=None, scale_target=None, scheduler_name=None, scheduling_gates=None, security_context=None, service_account=None, service_account_name=None, set_hostname_as_fqdn=None, share_process_namespace=None, subdomain=None, termination_grace_period_seconds=None, timeout=None, tolerations=None, topology_spread_constraints=None, volumes=None, local_vars_configuration=None): # noqa: E501 + def __init__(self, active_deadline_seconds=None, affinity=None, alibi=None, annotations=None, art=None, automount_service_account_token=None, batcher=None, canary_traffic_percent=None, container_concurrency=None, containers=None, deployment_strategy=None, dns_config=None, dns_policy=None, enable_service_links=None, ephemeral_containers=None, host_aliases=None, host_ipc=None, host_network=None, host_pid=None, host_users=None, hostname=None, image_pull_secrets=None, init_containers=None, labels=None, logger=None, max_replicas=None, min_replicas=None, node_name=None, node_selector=None, os=None, overhead=None, preemption_policy=None, priority=None, priority_class_name=None, readiness_gates=None, resource_claims=None, restart_policy=None, runtime_class_name=None, scale_metric=None, scale_target=None, scheduler_name=None, scheduling_gates=None, security_context=None, service_account=None, service_account_name=None, set_hostname_as_fqdn=None, share_process_namespace=None, subdomain=None, termination_grace_period_seconds=None, timeout=None, tolerations=None, topology_spread_constraints=None, volumes=None, local_vars_configuration=None): # noqa: E501 """V1beta1ExplainerSpec - a model defined in OpenAPI""" # noqa: E501 if local_vars_configuration is None: local_vars_configuration = Configuration() @@ -172,6 +174,7 @@ def __init__(self, active_deadline_seconds=None, affinity=None, alibi=None, anno self._canary_traffic_percent = None self._container_concurrency = None self._containers = None + self._deployment_strategy = None self._dns_config = None self._dns_policy = None self._enable_service_links = None @@ -236,6 +239,8 @@ def __init__(self, active_deadline_seconds=None, affinity=None, alibi=None, anno self.container_concurrency = container_concurrency if containers is not None: self.containers = containers + if deployment_strategy is not None: + self.deployment_strategy = deployment_strategy if dns_config is not None: self.dns_config = dns_config if dns_policy is not None: @@ -543,6 +548,27 @@ def containers(self, containers): self._containers = containers + @property + def deployment_strategy(self): + """Gets the deployment_strategy of this V1beta1ExplainerSpec. # noqa: E501 + + + :return: The deployment_strategy of this V1beta1ExplainerSpec. # noqa: E501 + :rtype: K8sIoApiAppsV1DeploymentStrategy + """ + return self._deployment_strategy + + @deployment_strategy.setter + def deployment_strategy(self, deployment_strategy): + """Sets the deployment_strategy of this V1beta1ExplainerSpec. + + + :param deployment_strategy: The deployment_strategy of this V1beta1ExplainerSpec. # noqa: E501 + :type: K8sIoApiAppsV1DeploymentStrategy + """ + + self._deployment_strategy = deployment_strategy + @property def dns_config(self): """Gets the dns_config of this V1beta1ExplainerSpec. # noqa: E501 diff --git a/python/kserve/kserve/models/v1beta1_predictor_spec.py b/python/kserve/kserve/models/v1beta1_predictor_spec.py index 0c7d531dcd..8d08b873a5 100644 --- a/python/kserve/kserve/models/v1beta1_predictor_spec.py +++ b/python/kserve/kserve/models/v1beta1_predictor_spec.py @@ -55,6 +55,7 @@ class V1beta1PredictorSpec(object): 'canary_traffic_percent': 'int', 'container_concurrency': 'int', 'containers': 'list[V1Container]', + 'deployment_strategy': 'K8sIoApiAppsV1DeploymentStrategy', 'dns_config': 'V1PodDNSConfig', 'dns_policy': 'str', 'enable_service_links': 'bool', @@ -119,6 +120,7 @@ class V1beta1PredictorSpec(object): 'canary_traffic_percent': 'canaryTrafficPercent', 'container_concurrency': 'containerConcurrency', 'containers': 'containers', + 'deployment_strategy': 'deploymentStrategy', 'dns_config': 'dnsConfig', 'dns_policy': 'dnsPolicy', 'enable_service_links': 'enableServiceLinks', @@ -174,7 +176,7 @@ class V1beta1PredictorSpec(object): 'xgboost': 'xgboost' } - def __init__(self, active_deadline_seconds=None, affinity=None, annotations=None, automount_service_account_token=None, batcher=None, canary_traffic_percent=None, container_concurrency=None, containers=None, dns_config=None, dns_policy=None, enable_service_links=None, ephemeral_containers=None, host_aliases=None, host_ipc=None, host_network=None, host_pid=None, host_users=None, hostname=None, huggingface=None, image_pull_secrets=None, init_containers=None, labels=None, lightgbm=None, logger=None, max_replicas=None, min_replicas=None, model=None, node_name=None, node_selector=None, onnx=None, os=None, overhead=None, paddle=None, pmml=None, preemption_policy=None, priority=None, priority_class_name=None, pytorch=None, readiness_gates=None, resource_claims=None, restart_policy=None, runtime_class_name=None, scale_metric=None, scale_target=None, scheduler_name=None, scheduling_gates=None, security_context=None, service_account=None, service_account_name=None, set_hostname_as_fqdn=None, share_process_namespace=None, sklearn=None, subdomain=None, tensorflow=None, termination_grace_period_seconds=None, timeout=None, tolerations=None, topology_spread_constraints=None, triton=None, volumes=None, xgboost=None, local_vars_configuration=None): # noqa: E501 + def __init__(self, active_deadline_seconds=None, affinity=None, annotations=None, automount_service_account_token=None, batcher=None, canary_traffic_percent=None, container_concurrency=None, containers=None, deployment_strategy=None, dns_config=None, dns_policy=None, enable_service_links=None, ephemeral_containers=None, host_aliases=None, host_ipc=None, host_network=None, host_pid=None, host_users=None, hostname=None, huggingface=None, image_pull_secrets=None, init_containers=None, labels=None, lightgbm=None, logger=None, max_replicas=None, min_replicas=None, model=None, node_name=None, node_selector=None, onnx=None, os=None, overhead=None, paddle=None, pmml=None, preemption_policy=None, priority=None, priority_class_name=None, pytorch=None, readiness_gates=None, resource_claims=None, restart_policy=None, runtime_class_name=None, scale_metric=None, scale_target=None, scheduler_name=None, scheduling_gates=None, security_context=None, service_account=None, service_account_name=None, set_hostname_as_fqdn=None, share_process_namespace=None, sklearn=None, subdomain=None, tensorflow=None, termination_grace_period_seconds=None, timeout=None, tolerations=None, topology_spread_constraints=None, triton=None, volumes=None, xgboost=None, local_vars_configuration=None): # noqa: E501 """V1beta1PredictorSpec - a model defined in OpenAPI""" # noqa: E501 if local_vars_configuration is None: local_vars_configuration = Configuration() @@ -188,6 +190,7 @@ def __init__(self, active_deadline_seconds=None, affinity=None, annotations=None self._canary_traffic_percent = None self._container_concurrency = None self._containers = None + self._deployment_strategy = None self._dns_config = None self._dns_policy = None self._enable_service_links = None @@ -259,6 +262,8 @@ def __init__(self, active_deadline_seconds=None, affinity=None, annotations=None self.container_concurrency = container_concurrency if containers is not None: self.containers = containers + if deployment_strategy is not None: + self.deployment_strategy = deployment_strategy if dns_config is not None: self.dns_config = dns_config if dns_policy is not None: @@ -546,6 +551,27 @@ def containers(self, containers): self._containers = containers + @property + def deployment_strategy(self): + """Gets the deployment_strategy of this V1beta1PredictorSpec. # noqa: E501 + + + :return: The deployment_strategy of this V1beta1PredictorSpec. # noqa: E501 + :rtype: K8sIoApiAppsV1DeploymentStrategy + """ + return self._deployment_strategy + + @deployment_strategy.setter + def deployment_strategy(self, deployment_strategy): + """Sets the deployment_strategy of this V1beta1PredictorSpec. + + + :param deployment_strategy: The deployment_strategy of this V1beta1PredictorSpec. # noqa: E501 + :type: K8sIoApiAppsV1DeploymentStrategy + """ + + self._deployment_strategy = deployment_strategy + @property def dns_config(self): """Gets the dns_config of this V1beta1PredictorSpec. # noqa: E501 diff --git a/python/kserve/kserve/models/v1beta1_transformer_spec.py b/python/kserve/kserve/models/v1beta1_transformer_spec.py index ccd878d7cd..c6aa8428f6 100644 --- a/python/kserve/kserve/models/v1beta1_transformer_spec.py +++ b/python/kserve/kserve/models/v1beta1_transformer_spec.py @@ -55,6 +55,7 @@ class V1beta1TransformerSpec(object): 'canary_traffic_percent': 'int', 'container_concurrency': 'int', 'containers': 'list[V1Container]', + 'deployment_strategy': 'K8sIoApiAppsV1DeploymentStrategy', 'dns_config': 'V1PodDNSConfig', 'dns_policy': 'str', 'enable_service_links': 'bool', @@ -108,6 +109,7 @@ class V1beta1TransformerSpec(object): 'canary_traffic_percent': 'canaryTrafficPercent', 'container_concurrency': 'containerConcurrency', 'containers': 'containers', + 'deployment_strategy': 'deploymentStrategy', 'dns_config': 'dnsConfig', 'dns_policy': 'dnsPolicy', 'enable_service_links': 'enableServiceLinks', @@ -152,7 +154,7 @@ class V1beta1TransformerSpec(object): 'volumes': 'volumes' } - def __init__(self, active_deadline_seconds=None, affinity=None, annotations=None, automount_service_account_token=None, batcher=None, canary_traffic_percent=None, container_concurrency=None, containers=None, dns_config=None, dns_policy=None, enable_service_links=None, ephemeral_containers=None, host_aliases=None, host_ipc=None, host_network=None, host_pid=None, host_users=None, hostname=None, image_pull_secrets=None, init_containers=None, labels=None, logger=None, max_replicas=None, min_replicas=None, node_name=None, node_selector=None, os=None, overhead=None, preemption_policy=None, priority=None, priority_class_name=None, readiness_gates=None, resource_claims=None, restart_policy=None, runtime_class_name=None, scale_metric=None, scale_target=None, scheduler_name=None, scheduling_gates=None, security_context=None, service_account=None, service_account_name=None, set_hostname_as_fqdn=None, share_process_namespace=None, subdomain=None, termination_grace_period_seconds=None, timeout=None, tolerations=None, topology_spread_constraints=None, volumes=None, local_vars_configuration=None): # noqa: E501 + def __init__(self, active_deadline_seconds=None, affinity=None, annotations=None, automount_service_account_token=None, batcher=None, canary_traffic_percent=None, container_concurrency=None, containers=None, deployment_strategy=None, dns_config=None, dns_policy=None, enable_service_links=None, ephemeral_containers=None, host_aliases=None, host_ipc=None, host_network=None, host_pid=None, host_users=None, hostname=None, image_pull_secrets=None, init_containers=None, labels=None, logger=None, max_replicas=None, min_replicas=None, node_name=None, node_selector=None, os=None, overhead=None, preemption_policy=None, priority=None, priority_class_name=None, readiness_gates=None, resource_claims=None, restart_policy=None, runtime_class_name=None, scale_metric=None, scale_target=None, scheduler_name=None, scheduling_gates=None, security_context=None, service_account=None, service_account_name=None, set_hostname_as_fqdn=None, share_process_namespace=None, subdomain=None, termination_grace_period_seconds=None, timeout=None, tolerations=None, topology_spread_constraints=None, volumes=None, local_vars_configuration=None): # noqa: E501 """V1beta1TransformerSpec - a model defined in OpenAPI""" # noqa: E501 if local_vars_configuration is None: local_vars_configuration = Configuration() @@ -166,6 +168,7 @@ def __init__(self, active_deadline_seconds=None, affinity=None, annotations=None self._canary_traffic_percent = None self._container_concurrency = None self._containers = None + self._deployment_strategy = None self._dns_config = None self._dns_policy = None self._enable_service_links = None @@ -226,6 +229,8 @@ def __init__(self, active_deadline_seconds=None, affinity=None, annotations=None self.container_concurrency = container_concurrency if containers is not None: self.containers = containers + if deployment_strategy is not None: + self.deployment_strategy = deployment_strategy if dns_config is not None: self.dns_config = dns_config if dns_policy is not None: @@ -491,6 +496,27 @@ def containers(self, containers): self._containers = containers + @property + def deployment_strategy(self): + """Gets the deployment_strategy of this V1beta1TransformerSpec. # noqa: E501 + + + :return: The deployment_strategy of this V1beta1TransformerSpec. # noqa: E501 + :rtype: K8sIoApiAppsV1DeploymentStrategy + """ + return self._deployment_strategy + + @deployment_strategy.setter + def deployment_strategy(self, deployment_strategy): + """Sets the deployment_strategy of this V1beta1TransformerSpec. + + + :param deployment_strategy: The deployment_strategy of this V1beta1TransformerSpec. # noqa: E501 + :type: K8sIoApiAppsV1DeploymentStrategy + """ + + self._deployment_strategy = deployment_strategy + @property def dns_config(self): """Gets the dns_config of this V1beta1TransformerSpec. # noqa: E501 diff --git a/test/crds/serving.kserve.io_inferenceservices.yaml b/test/crds/serving.kserve.io_inferenceservices.yaml index 331eda7e78..c267020493 100644 --- a/test/crds/serving.kserve.io_inferenceservices.yaml +++ b/test/crds/serving.kserve.io_inferenceservices.yaml @@ -1,1898 +1,3 @@ -#apiVersion: apiextensions.k8s.io/v1 -#kind: CustomResourceDefinition -#metadata: -# annotations: -# controller-gen.kubebuilder.io/version: v0.12.0 -# name: clusterservingruntimes.serving.kserve.io -#spec: -# group: serving.kserve.io -# names: -# kind: ClusterServingRuntime -# listKind: ClusterServingRuntimeList -# plural: clusterservingruntimes -# singular: clusterservingruntime -# scope: Cluster -# versions: -# - additionalPrinterColumns: -# - jsonPath: .spec.disabled -# name: Disabled -# type: boolean -# - jsonPath: .spec.supportedModelFormats[*].name -# name: ModelType -# type: string -# - jsonPath: .spec.containers[*].name -# name: Containers -# type: string -# - jsonPath: .metadata.creationTimestamp -# name: Age -# type: date -# name: v1alpha1 -# schema: -# openAPIV3Schema: -# properties: -# apiVersion: -# type: string -# kind: -# type: string -# metadata: -# type: object -# spec: -# properties: -# affinity: -# properties: -# nodeAffinity: -# properties: -# preferredDuringSchedulingIgnoredDuringExecution: -# items: -# properties: -# preference: -# properties: -# matchExpressions: -# items: -# properties: -# key: -# type: string -# operator: -# type: string -# values: -# items: -# type: string -# type: array -# required: -# - key -# - operator -# type: object -# type: array -# matchFields: -# items: -# properties: -# key: -# type: string -# operator: -# type: string -# values: -# items: -# type: string -# type: array -# required: -# - key -# - operator -# type: object -# type: array -# type: object -# x-kubernetes-map-type: atomic -# weight: -# format: int32 -# type: integer -# required: -# - preference -# - weight -# type: object -# type: array -# requiredDuringSchedulingIgnoredDuringExecution: -# properties: -# nodeSelectorTerms: -# items: -# properties: -# matchExpressions: -# items: -# properties: -# key: -# type: string -# operator: -# type: string -# values: -# items: -# type: string -# type: array -# required: -# - key -# - operator -# type: object -# type: array -# matchFields: -# items: -# properties: -# key: -# type: string -# operator: -# type: string -# values: -# items: -# type: string -# type: array -# required: -# - key -# - operator -# type: object -# type: array -# type: object -# x-kubernetes-map-type: atomic -# type: array -# required: -# - nodeSelectorTerms -# type: object -# x-kubernetes-map-type: atomic -# type: object -# podAffinity: -# properties: -# preferredDuringSchedulingIgnoredDuringExecution: -# items: -# properties: -# podAffinityTerm: -# properties: -# labelSelector: -# properties: -# matchExpressions: -# items: -# properties: -# key: -# type: string -# operator: -# type: string -# values: -# items: -# type: string -# type: array -# required: -# - key -# - operator -# type: object -# type: array -# matchLabels: -# additionalProperties: -# type: string -# type: object -# type: object -# x-kubernetes-map-type: atomic -# namespaceSelector: -# properties: -# matchExpressions: -# items: -# properties: -# key: -# type: string -# operator: -# type: string -# values: -# items: -# type: string -# type: array -# required: -# - key -# - operator -# type: object -# type: array -# matchLabels: -# additionalProperties: -# type: string -# type: object -# type: object -# x-kubernetes-map-type: atomic -# namespaces: -# items: -# type: string -# type: array -# topologyKey: -# type: string -# required: -# - topologyKey -# type: object -# weight: -# format: int32 -# type: integer -# required: -# - podAffinityTerm -# - weight -# type: object -# type: array -# requiredDuringSchedulingIgnoredDuringExecution: -# items: -# properties: -# labelSelector: -# properties: -# matchExpressions: -# items: -# properties: -# key: -# type: string -# operator: -# type: string -# values: -# items: -# type: string -# type: array -# required: -# - key -# - operator -# type: object -# type: array -# matchLabels: -# additionalProperties: -# type: string -# type: object -# type: object -# x-kubernetes-map-type: atomic -# namespaceSelector: -# properties: -# matchExpressions: -# items: -# properties: -# key: -# type: string -# operator: -# type: string -# values: -# items: -# type: string -# type: array -# required: -# - key -# - operator -# type: object -# type: array -# matchLabels: -# additionalProperties: -# type: string -# type: object -# type: object -# x-kubernetes-map-type: atomic -# namespaces: -# items: -# type: string -# type: array -# topologyKey: -# type: string -# required: -# - topologyKey -# type: object -# type: array -# type: object -# podAntiAffinity: -# properties: -# preferredDuringSchedulingIgnoredDuringExecution: -# items: -# properties: -# podAffinityTerm: -# properties: -# labelSelector: -# properties: -# matchExpressions: -# items: -# properties: -# key: -# type: string -# operator: -# type: string -# values: -# items: -# type: string -# type: array -# required: -# - key -# - operator -# type: object -# type: array -# matchLabels: -# additionalProperties: -# type: string -# type: object -# type: object -# x-kubernetes-map-type: atomic -# namespaceSelector: -# properties: -# matchExpressions: -# items: -# properties: -# key: -# type: string -# operator: -# type: string -# values: -# items: -# type: string -# type: array -# required: -# - key -# - operator -# type: object -# type: array -# matchLabels: -# additionalProperties: -# type: string -# type: object -# type: object -# x-kubernetes-map-type: atomic -# namespaces: -# items: -# type: string -# type: array -# topologyKey: -# type: string -# required: -# - topologyKey -# type: object -# weight: -# format: int32 -# type: integer -# required: -# - podAffinityTerm -# - weight -# type: object -# type: array -# requiredDuringSchedulingIgnoredDuringExecution: -# items: -# properties: -# labelSelector: -# properties: -# matchExpressions: -# items: -# properties: -# key: -# type: string -# operator: -# type: string -# values: -# items: -# type: string -# type: array -# required: -# - key -# - operator -# type: object -# type: array -# matchLabels: -# additionalProperties: -# type: string -# type: object -# type: object -# x-kubernetes-map-type: atomic -# namespaceSelector: -# properties: -# matchExpressions: -# items: -# properties: -# key: -# type: string -# operator: -# type: string -# values: -# items: -# type: string -# type: array -# required: -# - key -# - operator -# type: object -# type: array -# matchLabels: -# additionalProperties: -# type: string -# type: object -# type: object -# x-kubernetes-map-type: atomic -# namespaces: -# items: -# type: string -# type: array -# topologyKey: -# type: string -# required: -# - topologyKey -# type: object -# type: array -# type: object -# type: object -# annotations: -# additionalProperties: -# type: string -# type: object -# builtInAdapter: -# properties: -# env: -# items: -# properties: -# name: -# type: string -# value: -# type: string -# valueFrom: -# properties: -# configMapKeyRef: -# properties: -# key: -# type: string -# name: -# type: string -# optional: -# type: boolean -# required: -# - key -# type: object -# x-kubernetes-map-type: atomic -# fieldRef: -# properties: -# apiVersion: -# type: string -# fieldPath: -# type: string -# required: -# - fieldPath -# type: object -# x-kubernetes-map-type: atomic -# resourceFieldRef: -# properties: -# containerName: -# type: string -# divisor: -# anyOf: -# - type: integer -# - type: string -# pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ -# x-kubernetes-int-or-string: true -# resource: -# type: string -# required: -# - resource -# type: object -# x-kubernetes-map-type: atomic -# secretKeyRef: -# properties: -# key: -# type: string -# name: -# type: string -# optional: -# type: boolean -# required: -# - key -# type: object -# x-kubernetes-map-type: atomic -# type: object -# required: -# - name -# type: object -# type: array -# memBufferBytes: -# type: integer -# modelLoadingTimeoutMillis: -# type: integer -# runtimeManagementPort: -# type: integer -# serverType: -# type: string -# type: object -# containers: -# items: -# properties: -# args: -# items: -# type: string -# type: array -# command: -# items: -# type: string -# type: array -# env: -# items: -# properties: -# name: -# type: string -# value: -# type: string -# valueFrom: -# properties: -# configMapKeyRef: -# properties: -# key: -# type: string -# name: -# type: string -# optional: -# type: boolean -# required: -# - key -# type: object -# x-kubernetes-map-type: atomic -# fieldRef: -# properties: -# apiVersion: -# type: string -# fieldPath: -# type: string -# required: -# - fieldPath -# type: object -# x-kubernetes-map-type: atomic -# resourceFieldRef: -# properties: -# containerName: -# type: string -# divisor: -# anyOf: -# - type: integer -# - type: string -# pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ -# x-kubernetes-int-or-string: true -# resource: -# type: string -# required: -# - resource -# type: object -# x-kubernetes-map-type: atomic -# secretKeyRef: -# properties: -# key: -# type: string -# name: -# type: string -# optional: -# type: boolean -# required: -# - key -# type: object -# x-kubernetes-map-type: atomic -# type: object -# required: -# - name -# type: object -# type: array -# envFrom: -# items: -# properties: -# configMapRef: -# properties: -# name: -# type: string -# optional: -# type: boolean -# type: object -# x-kubernetes-map-type: atomic -# prefix: -# type: string -# secretRef: -# properties: -# name: -# type: string -# optional: -# type: boolean -# type: object -# x-kubernetes-map-type: atomic -# type: object -# type: array -# image: -# type: string -# imagePullPolicy: -# type: string -# lifecycle: -# properties: -# postStart: -# properties: -# exec: -# properties: -# command: -# items: -# type: string -# type: array -# type: object -# httpGet: -# properties: -# host: -# type: string -# httpHeaders: -# items: -# properties: -# name: -# type: string -# value: -# type: string -# required: -# - name -# - value -# type: object -# type: array -# path: -# type: string -# port: -# anyOf: -# - type: integer -# - type: string -# x-kubernetes-int-or-string: true -# scheme: -# type: string -# required: -# - port -# type: object -# tcpSocket: -# properties: -# host: -# type: string -# port: -# anyOf: -# - type: integer -# - type: string -# x-kubernetes-int-or-string: true -# required: -# - port -# type: object -# type: object -# preStop: -# properties: -# exec: -# properties: -# command: -# items: -# type: string -# type: array -# type: object -# httpGet: -# properties: -# host: -# type: string -# httpHeaders: -# items: -# properties: -# name: -# type: string -# value: -# type: string -# required: -# - name -# - value -# type: object -# type: array -# path: -# type: string -# port: -# anyOf: -# - type: integer -# - type: string -# x-kubernetes-int-or-string: true -# scheme: -# type: string -# required: -# - port -# type: object -# tcpSocket: -# properties: -# host: -# type: string -# port: -# anyOf: -# - type: integer -# - type: string -# x-kubernetes-int-or-string: true -# required: -# - port -# type: object -# type: object -# type: object -# livenessProbe: -# properties: -# exec: -# properties: -# command: -# items: -# type: string -# type: array -# type: object -# failureThreshold: -# format: int32 -# type: integer -# grpc: -# properties: -# port: -# format: int32 -# type: integer -# service: -# type: string -# required: -# - port -# type: object -# httpGet: -# properties: -# host: -# type: string -# httpHeaders: -# items: -# properties: -# name: -# type: string -# value: -# type: string -# required: -# - name -# - value -# type: object -# type: array -# path: -# type: string -# port: -# anyOf: -# - type: integer -# - type: string -# x-kubernetes-int-or-string: true -# scheme: -# type: string -# required: -# - port -# type: object -# initialDelaySeconds: -# format: int32 -# type: integer -# periodSeconds: -# format: int32 -# type: integer -# successThreshold: -# format: int32 -# type: integer -# tcpSocket: -# properties: -# host: -# type: string -# port: -# anyOf: -# - type: integer -# - type: string -# x-kubernetes-int-or-string: true -# required: -# - port -# type: object -# terminationGracePeriodSeconds: -# format: int64 -# type: integer -# timeoutSeconds: -# format: int32 -# type: integer -# type: object -# name: -# type: string -# ports: -# items: -# properties: -# containerPort: -# format: int32 -# type: integer -# hostIP: -# type: string -# hostPort: -# format: int32 -# type: integer -# name: -# type: string -# protocol: -# default: TCP -# type: string -# required: -# - containerPort -# type: object -# type: array -# x-kubernetes-list-map-keys: -# - containerPort -# - protocol -# x-kubernetes-list-type: map -# readinessProbe: -# properties: -# exec: -# properties: -# command: -# items: -# type: string -# type: array -# type: object -# failureThreshold: -# format: int32 -# type: integer -# grpc: -# properties: -# port: -# format: int32 -# type: integer -# service: -# type: string -# required: -# - port -# type: object -# httpGet: -# properties: -# host: -# type: string -# httpHeaders: -# items: -# properties: -# name: -# type: string -# value: -# type: string -# required: -# - name -# - value -# type: object -# type: array -# path: -# type: string -# port: -# anyOf: -# - type: integer -# - type: string -# x-kubernetes-int-or-string: true -# scheme: -# type: string -# required: -# - port -# type: object -# initialDelaySeconds: -# format: int32 -# type: integer -# periodSeconds: -# format: int32 -# type: integer -# successThreshold: -# format: int32 -# type: integer -# tcpSocket: -# properties: -# host: -# type: string -# port: -# anyOf: -# - type: integer -# - type: string -# x-kubernetes-int-or-string: true -# required: -# - port -# type: object -# terminationGracePeriodSeconds: -# format: int64 -# type: integer -# timeoutSeconds: -# format: int32 -# type: integer -# type: object -# resizePolicy: -# items: -# properties: -# resourceName: -# type: string -# restartPolicy: -# type: string -# required: -# - resourceName -# - restartPolicy -# type: object -# type: array -# x-kubernetes-list-type: atomic -# resources: -# properties: -# claims: -# items: -# properties: -# name: -# type: string -# required: -# - name -# type: object -# type: array -# x-kubernetes-list-map-keys: -# - name -# x-kubernetes-list-type: map -# limits: -# additionalProperties: -# anyOf: -# - type: integer -# - type: string -# pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ -# x-kubernetes-int-or-string: true -# type: object -# requests: -# additionalProperties: -# anyOf: -# - type: integer -# - type: string -# pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ -# x-kubernetes-int-or-string: true -# type: object -# type: object -# restartPolicy: -# type: string -# securityContext: -# properties: -# allowPrivilegeEscalation: -# type: boolean -# capabilities: -# properties: -# add: -# items: -# type: string -# type: array -# drop: -# items: -# type: string -# type: array -# type: object -# privileged: -# type: boolean -# procMount: -# type: string -# readOnlyRootFilesystem: -# type: boolean -# runAsGroup: -# format: int64 -# type: integer -# runAsNonRoot: -# type: boolean -# runAsUser: -# format: int64 -# type: integer -# seLinuxOptions: -# properties: -# level: -# type: string -# role: -# type: string -# type: -# type: string -# user: -# type: string -# type: object -# seccompProfile: -# properties: -# localhostProfile: -# type: string -# type: -# type: string -# required: -# - type -# type: object -# windowsOptions: -# properties: -# gmsaCredentialSpec: -# type: string -# gmsaCredentialSpecName: -# type: string -# hostProcess: -# type: boolean -# runAsUserName: -# type: string -# type: object -# type: object -# startupProbe: -# properties: -# exec: -# properties: -# command: -# items: -# type: string -# type: array -# type: object -# failureThreshold: -# format: int32 -# type: integer -# grpc: -# properties: -# port: -# format: int32 -# type: integer -# service: -# type: string -# required: -# - port -# type: object -# httpGet: -# properties: -# host: -# type: string -# httpHeaders: -# items: -# properties: -# name: -# type: string -# value: -# type: string -# required: -# - name -# - value -# type: object -# type: array -# path: -# type: string -# port: -# anyOf: -# - type: integer -# - type: string -# x-kubernetes-int-or-string: true -# scheme: -# type: string -# required: -# - port -# type: object -# initialDelaySeconds: -# format: int32 -# type: integer -# periodSeconds: -# format: int32 -# type: integer -# successThreshold: -# format: int32 -# type: integer -# tcpSocket: -# properties: -# host: -# type: string -# port: -# anyOf: -# - type: integer -# - type: string -# x-kubernetes-int-or-string: true -# required: -# - port -# type: object -# terminationGracePeriodSeconds: -# format: int64 -# type: integer -# timeoutSeconds: -# format: int32 -# type: integer -# type: object -# stdin: -# type: boolean -# stdinOnce: -# type: boolean -# terminationMessagePath: -# type: string -# terminationMessagePolicy: -# type: string -# tty: -# type: boolean -# volumeDevices: -# items: -# properties: -# devicePath: -# type: string -# name: -# type: string -# required: -# - devicePath -# - name -# type: object -# type: array -# volumeMounts: -# items: -# properties: -# mountPath: -# type: string -# mountPropagation: -# type: string -# name: -# type: string -# readOnly: -# type: boolean -# subPath: -# type: string -# subPathExpr: -# type: string -# required: -# - mountPath -# - name -# type: object -# type: array -# workingDir: -# type: string -# required: -# - name -# type: object -# type: array -# disabled: -# type: boolean -# grpcDataEndpoint: -# type: string -# grpcEndpoint: -# type: string -# httpDataEndpoint: -# type: string -# imagePullSecrets: -# items: -# properties: -# name: -# type: string -# type: object -# x-kubernetes-map-type: atomic -# type: array -# labels: -# additionalProperties: -# type: string -# type: object -# multiModel: -# type: boolean -# nodeSelector: -# additionalProperties: -# type: string -# type: object -# protocolVersions: -# items: -# type: string -# type: array -# replicas: -# type: integer -# storageHelper: -# properties: -# disabled: -# type: boolean -# type: object -# supportedModelFormats: -# items: -# properties: -# autoSelect: -# type: boolean -# name: -# type: string -# priority: -# format: int32 -# minimum: 1 -# type: integer -# version: -# type: string -# required: -# - name -# type: object -# type: array -# tolerations: -# items: -# properties: -# effect: -# type: string -# key: -# type: string -# operator: -# type: string -# tolerationSeconds: -# format: int64 -# type: integer -# value: -# type: string -# type: object -# type: array -# volumes: -# items: -# properties: -# awsElasticBlockStore: -# properties: -# fsType: -# type: string -# partition: -# format: int32 -# type: integer -# readOnly: -# type: boolean -# volumeID: -# type: string -# required: -# - volumeID -# type: object -# azureDisk: -# properties: -# cachingMode: -# type: string -# diskName: -# type: string -# diskURI: -# type: string -# fsType: -# type: string -# kind: -# type: string -# readOnly: -# type: boolean -# required: -# - diskName -# - diskURI -# type: object -# azureFile: -# properties: -# readOnly: -# type: boolean -# secretName: -# type: string -# shareName: -# type: string -# required: -# - secretName -# - shareName -# type: object -# cephfs: -# properties: -# monitors: -# items: -# type: string -# type: array -# path: -# type: string -# readOnly: -# type: boolean -# secretFile: -# type: string -# secretRef: -# properties: -# name: -# type: string -# type: object -# x-kubernetes-map-type: atomic -# user: -# type: string -# required: -# - monitors -# type: object -# cinder: -# properties: -# fsType: -# type: string -# readOnly: -# type: boolean -# secretRef: -# properties: -# name: -# type: string -# type: object -# x-kubernetes-map-type: atomic -# volumeID: -# type: string -# required: -# - volumeID -# type: object -# configMap: -# properties: -# defaultMode: -# format: int32 -# type: integer -# items: -# items: -# properties: -# key: -# type: string -# mode: -# format: int32 -# type: integer -# path: -# type: string -# required: -# - key -# - path -# type: object -# type: array -# name: -# type: string -# optional: -# type: boolean -# type: object -# x-kubernetes-map-type: atomic -# csi: -# properties: -# driver: -# type: string -# fsType: -# type: string -# nodePublishSecretRef: -# properties: -# name: -# type: string -# type: object -# x-kubernetes-map-type: atomic -# readOnly: -# type: boolean -# volumeAttributes: -# additionalProperties: -# type: string -# type: object -# required: -# - driver -# type: object -# downwardAPI: -# properties: -# defaultMode: -# format: int32 -# type: integer -# items: -# items: -# properties: -# fieldRef: -# properties: -# apiVersion: -# type: string -# fieldPath: -# type: string -# required: -# - fieldPath -# type: object -# x-kubernetes-map-type: atomic -# mode: -# format: int32 -# type: integer -# path: -# type: string -# resourceFieldRef: -# properties: -# containerName: -# type: string -# divisor: -# anyOf: -# - type: integer -# - type: string -# pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ -# x-kubernetes-int-or-string: true -# resource: -# type: string -# required: -# - resource -# type: object -# x-kubernetes-map-type: atomic -# required: -# - path -# type: object -# type: array -# type: object -# emptyDir: -# properties: -# medium: -# type: string -# sizeLimit: -# anyOf: -# - type: integer -# - type: string -# pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ -# x-kubernetes-int-or-string: true -# type: object -# ephemeral: -# properties: -# volumeClaimTemplate: -# properties: -# metadata: -# type: object -# spec: -# properties: -# accessModes: -# items: -# type: string -# type: array -# dataSource: -# properties: -# apiGroup: -# type: string -# kind: -# type: string -# name: -# type: string -# required: -# - kind -# - name -# type: object -# x-kubernetes-map-type: atomic -# dataSourceRef: -# properties: -# apiGroup: -# type: string -# kind: -# type: string -# name: -# type: string -# namespace: -# type: string -# required: -# - kind -# - name -# type: object -# resources: -# properties: -# claims: -# items: -# properties: -# name: -# type: string -# required: -# - name -# type: object -# type: array -# x-kubernetes-list-map-keys: -# - name -# x-kubernetes-list-type: map -# limits: -# additionalProperties: -# anyOf: -# - type: integer -# - type: string -# pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ -# x-kubernetes-int-or-string: true -# type: object -# requests: -# additionalProperties: -# anyOf: -# - type: integer -# - type: string -# pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ -# x-kubernetes-int-or-string: true -# type: object -# type: object -# selector: -# properties: -# matchExpressions: -# items: -# properties: -# key: -# type: string -# operator: -# type: string -# values: -# items: -# type: string -# type: array -# required: -# - key -# - operator -# type: object -# type: array -# matchLabels: -# additionalProperties: -# type: string -# type: object -# type: object -# x-kubernetes-map-type: atomic -# storageClassName: -# type: string -# volumeMode: -# type: string -# volumeName: -# type: string -# type: object -# required: -# - spec -# type: object -# type: object -# fc: -# properties: -# fsType: -# type: string -# lun: -# format: int32 -# type: integer -# readOnly: -# type: boolean -# targetWWNs: -# items: -# type: string -# type: array -# wwids: -# items: -# type: string -# type: array -# type: object -# flexVolume: -# properties: -# driver: -# type: string -# fsType: -# type: string -# options: -# additionalProperties: -# type: string -# type: object -# readOnly: -# type: boolean -# secretRef: -# properties: -# name: -# type: string -# type: object -# x-kubernetes-map-type: atomic -# required: -# - driver -# type: object -# flocker: -# properties: -# datasetName: -# type: string -# datasetUUID: -# type: string -# type: object -# gcePersistentDisk: -# properties: -# fsType: -# type: string -# partition: -# format: int32 -# type: integer -# pdName: -# type: string -# readOnly: -# type: boolean -# required: -# - pdName -# type: object -# gitRepo: -# properties: -# directory: -# type: string -# repository: -# type: string -# revision: -# type: string -# required: -# - repository -# type: object -# glusterfs: -# properties: -# endpoints: -# type: string -# path: -# type: string -# readOnly: -# type: boolean -# required: -# - endpoints -# - path -# type: object -# hostPath: -# properties: -# path: -# type: string -# type: -# type: string -# required: -# - path -# type: object -# iscsi: -# properties: -# chapAuthDiscovery: -# type: boolean -# chapAuthSession: -# type: boolean -# fsType: -# type: string -# initiatorName: -# type: string -# iqn: -# type: string -# iscsiInterface: -# type: string -# lun: -# format: int32 -# type: integer -# portals: -# items: -# type: string -# type: array -# readOnly: -# type: boolean -# secretRef: -# properties: -# name: -# type: string -# type: object -# x-kubernetes-map-type: atomic -# targetPortal: -# type: string -# required: -# - iqn -# - lun -# - targetPortal -# type: object -# name: -# type: string -# nfs: -# properties: -# path: -# type: string -# readOnly: -# type: boolean -# server: -# type: string -# required: -# - path -# - server -# type: object -# persistentVolumeClaim: -# properties: -# claimName: -# type: string -# readOnly: -# type: boolean -# required: -# - claimName -# type: object -# photonPersistentDisk: -# properties: -# fsType: -# type: string -# pdID: -# type: string -# required: -# - pdID -# type: object -# portworxVolume: -# properties: -# fsType: -# type: string -# readOnly: -# type: boolean -# volumeID: -# type: string -# required: -# - volumeID -# type: object -# projected: -# properties: -# defaultMode: -# format: int32 -# type: integer -# sources: -# items: -# properties: -# configMap: -# properties: -# items: -# items: -# properties: -# key: -# type: string -# mode: -# format: int32 -# type: integer -# path: -# type: string -# required: -# - key -# - path -# type: object -# type: array -# name: -# type: string -# optional: -# type: boolean -# type: object -# x-kubernetes-map-type: atomic -# downwardAPI: -# properties: -# items: -# items: -# properties: -# fieldRef: -# properties: -# apiVersion: -# type: string -# fieldPath: -# type: string -# required: -# - fieldPath -# type: object -# x-kubernetes-map-type: atomic -# mode: -# format: int32 -# type: integer -# path: -# type: string -# resourceFieldRef: -# properties: -# containerName: -# type: string -# divisor: -# anyOf: -# - type: integer -# - type: string -# pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ -# x-kubernetes-int-or-string: true -# resource: -# type: string -# required: -# - resource -# type: object -# x-kubernetes-map-type: atomic -# required: -# - path -# type: object -# type: array -# type: object -# secret: -# properties: -# items: -# items: -# properties: -# key: -# type: string -# mode: -# format: int32 -# type: integer -# path: -# type: string -# required: -# - key -# - path -# type: object -# type: array -# name: -# type: string -# optional: -# type: boolean -# type: object -# x-kubernetes-map-type: atomic -# serviceAccountToken: -# properties: -# audience: -# type: string -# expirationSeconds: -# format: int64 -# type: integer -# path: -# type: string -# required: -# - path -# type: object -# type: object -# type: array -# type: object -# quobyte: -# properties: -# group: -# type: string -# readOnly: -# type: boolean -# registry: -# type: string -# tenant: -# type: string -# user: -# type: string -# volume: -# type: string -# required: -# - registry -# - volume -# type: object -# rbd: -# properties: -# fsType: -# type: string -# image: -# type: string -# keyring: -# type: string -# monitors: -# items: -# type: string -# type: array -# pool: -# type: string -# readOnly: -# type: boolean -# secretRef: -# properties: -# name: -# type: string -# type: object -# x-kubernetes-map-type: atomic -# user: -# type: string -# required: -# - image -# - monitors -# type: object -# scaleIO: -# properties: -# fsType: -# type: string -# gateway: -# type: string -# protectionDomain: -# type: string -# readOnly: -# type: boolean -# secretRef: -# properties: -# name: -# type: string -# type: object -# x-kubernetes-map-type: atomic -# sslEnabled: -# type: boolean -# storageMode: -# type: string -# storagePool: -# type: string -# system: -# type: string -# volumeName: -# type: string -# required: -# - gateway -# - secretRef -# - system -# type: object -# secret: -# properties: -# defaultMode: -# format: int32 -# type: integer -# items: -# items: -# properties: -# key: -# type: string -# mode: -# format: int32 -# type: integer -# path: -# type: string -# required: -# - key -# - path -# type: object -# type: array -# optional: -# type: boolean -# secretName: -# type: string -# type: object -# storageos: -# properties: -# fsType: -# type: string -# readOnly: -# type: boolean -# secretRef: -# properties: -# name: -# type: string -# type: object -# x-kubernetes-map-type: atomic -# volumeName: -# type: string -# volumeNamespace: -# type: string -# type: object -# vsphereVolume: -# properties: -# fsType: -# type: string -# storagePolicyID: -# type: string -# storagePolicyName: -# type: string -# volumePath: -# type: string -# required: -# - volumePath -# type: object -# required: -# - name -# type: object -# type: array -# required: -# - containers -# type: object -# status: -# type: object -# type: object -# served: true -# storage: true -# subresources: {} -#--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: @@ -5408,6 +3513,24 @@ spec: - name type: object type: array + deploymentStrategy: + properties: + rollingUpdate: + properties: + maxSurge: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: + type: string + type: object dnsConfig: properties: nameservers: @@ -8053,6 +6176,24 @@ spec: - name type: object type: array + deploymentStrategy: + properties: + rollingUpdate: + properties: + maxSurge: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: + type: string + type: object dnsConfig: properties: nameservers: @@ -17606,6 +15747,24 @@ spec: - name type: object type: array + deploymentStrategy: + properties: + rollingUpdate: + properties: + maxSurge: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + maxUnavailable: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + type: + type: string + type: object dnsConfig: properties: nameservers: