mirror of
https://github.com/hanzo-ml/kserve.git
synced 2026-10-30 20:53:49 +00:00
1620 lines
59 KiB
Go
1620 lines
59 KiB
Go
/*
|
|
Copyright 2021 The KServe Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package inferencegraph
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
. "github.com/onsi/ginkgo/v2"
|
|
. "github.com/onsi/gomega"
|
|
"google.golang.org/protobuf/proto"
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
apierr "k8s.io/apimachinery/pkg/api/errors"
|
|
"k8s.io/apimachinery/pkg/api/resource"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"knative.dev/pkg/apis"
|
|
"knative.dev/pkg/kmp"
|
|
"knative.dev/serving/pkg/apis/autoscaling"
|
|
knservingv1 "knative.dev/serving/pkg/apis/serving/v1"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
|
|
|
"github.com/kserve/kserve/pkg/apis/serving/v1alpha1"
|
|
"github.com/kserve/kserve/pkg/apis/serving/v1beta1"
|
|
"github.com/kserve/kserve/pkg/constants"
|
|
"github.com/kserve/kserve/pkg/utils"
|
|
)
|
|
|
|
var _ = Describe("Inference Graph controller test", func() {
|
|
// Define utility constants for object names and testing timeouts/durations and intervals.
|
|
const (
|
|
timeout = time.Second * 10
|
|
interval = time.Millisecond * 250
|
|
domain = "example.com"
|
|
)
|
|
|
|
configs := map[string]string{
|
|
"router": `{
|
|
"image": "kserve/router:v0.10.0",
|
|
"memoryRequest": "100Mi",
|
|
"memoryLimit": "500Mi",
|
|
"cpuRequest": "100m",
|
|
"cpuLimit": "100m",
|
|
"headers": {
|
|
"propagate": [
|
|
"Authorization",
|
|
"Intuit_tid"
|
|
]
|
|
}
|
|
}`,
|
|
"ingress": `{
|
|
"kserveIngressGateway": "kserve/kserve-ingress-gateway",
|
|
"ingressGateway": "knative-serving/knative-ingress-gateway",
|
|
"localGateway": "knative-serving/knative-local-gateway",
|
|
"localGatewayService": "knative-local-gateway.istio-system.svc.cluster.local"
|
|
}`,
|
|
"storageInitializer": `{
|
|
"image" : "kserve/storage-initializer:latest",
|
|
"memoryRequest": "100Mi",
|
|
"memoryLimit": "1Gi",
|
|
"cpuRequest": "100m",
|
|
"cpuLimit": "1",
|
|
"cpuModelcar": "10m",
|
|
"memoryModelcar": "15Mi",
|
|
"CaBundleConfigMapName": "",
|
|
"caBundleVolumeMountPath": "/etc/ssl/custom-certs"
|
|
}`,
|
|
}
|
|
|
|
expectedReadinessProbe := constants.GetRouterReadinessProbe()
|
|
|
|
Context("with knative configured to not allow zero initial scale", func() {
|
|
When("a Knative InferenceGraph is created with an initial scale annotation and value of zero", func() {
|
|
It("should ignore the annotation", func() {
|
|
// Create configmap
|
|
configMap := &corev1.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 InferenceGraph
|
|
graphName := "initialscale1"
|
|
expectedRequest := reconcile.Request{NamespacedName: types.NamespacedName{Name: graphName, Namespace: "default"}}
|
|
serviceKey := expectedRequest.NamespacedName
|
|
ctx := context.Background()
|
|
var minScale int32 = 2
|
|
ig := &v1alpha1.InferenceGraph{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: serviceKey.Name,
|
|
Namespace: serviceKey.Namespace,
|
|
Annotations: map[string]string{
|
|
"serving.kserve.io/deploymentMode": string(constants.Knative),
|
|
autoscaling.InitialScaleAnnotationKey: "0",
|
|
},
|
|
},
|
|
Spec: v1alpha1.InferenceGraphSpec{
|
|
MinReplicas: &minScale,
|
|
Nodes: map[string]v1alpha1.InferenceRouter{
|
|
v1alpha1.GraphRootNodeName: {
|
|
RouterType: v1alpha1.Sequence,
|
|
Steps: []v1alpha1.InferenceStep{
|
|
{
|
|
InferenceTarget: v1alpha1.InferenceTarget{
|
|
ServiceURL: "http://someservice.example.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
Expect(k8sClient.Create(ctx, ig)).Should(Succeed())
|
|
defer k8sClient.Delete(ctx, ig)
|
|
|
|
actualService := &knservingv1.Service{}
|
|
Eventually(func() error {
|
|
return k8sClient.Get(context.TODO(), serviceKey, actualService)
|
|
}, timeout).
|
|
Should(Succeed())
|
|
|
|
Expect(actualService.Spec.Template.Annotations).NotTo(HaveKey(autoscaling.InitialScaleAnnotationKey))
|
|
})
|
|
})
|
|
When("a Knative InferenceGraph is created with an initial scale annotation and valid non-zero integer value", func() {
|
|
It("should override the default initial scale value with the annotation value", func() {
|
|
// Create configmap
|
|
configMap := &corev1.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 InferenceGraph
|
|
graphName := "initialscale2"
|
|
expectedRequest := reconcile.Request{NamespacedName: types.NamespacedName{Name: graphName, Namespace: "default"}}
|
|
serviceKey := expectedRequest.NamespacedName
|
|
ctx := context.Background()
|
|
var minScale int32 = 2
|
|
ig := &v1alpha1.InferenceGraph{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: serviceKey.Name,
|
|
Namespace: serviceKey.Namespace,
|
|
Annotations: map[string]string{
|
|
"serving.kserve.io/deploymentMode": string(constants.Knative),
|
|
autoscaling.InitialScaleAnnotationKey: "3",
|
|
},
|
|
},
|
|
Spec: v1alpha1.InferenceGraphSpec{
|
|
MinReplicas: &minScale,
|
|
Nodes: map[string]v1alpha1.InferenceRouter{
|
|
v1alpha1.GraphRootNodeName: {
|
|
RouterType: v1alpha1.Sequence,
|
|
Steps: []v1alpha1.InferenceStep{
|
|
{
|
|
InferenceTarget: v1alpha1.InferenceTarget{
|
|
ServiceURL: "http://someservice.example.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
Expect(k8sClient.Create(ctx, ig)).Should(Succeed())
|
|
defer k8sClient.Delete(ctx, ig)
|
|
|
|
actualService := &knservingv1.Service{}
|
|
Eventually(func() error {
|
|
return k8sClient.Get(context.TODO(), serviceKey, actualService)
|
|
}, timeout).
|
|
Should(Succeed())
|
|
|
|
Expect(actualService.Spec.Template.Annotations[autoscaling.InitialScaleAnnotationKey]).To(Equal("3"))
|
|
})
|
|
})
|
|
When("a Knative InferenceGraph is created with an initial scale annotation and invalid non-integer value", func() {
|
|
It("should ignore the annotation", func() {
|
|
// Create configmap
|
|
configMap := &corev1.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 InferenceGraph
|
|
graphName := "initialscale3"
|
|
expectedRequest := reconcile.Request{NamespacedName: types.NamespacedName{Name: graphName, Namespace: "default"}}
|
|
serviceKey := expectedRequest.NamespacedName
|
|
ctx := context.Background()
|
|
var minScale int32 = 2
|
|
ig := &v1alpha1.InferenceGraph{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: serviceKey.Name,
|
|
Namespace: serviceKey.Namespace,
|
|
Annotations: map[string]string{
|
|
"serving.kserve.io/deploymentMode": string(constants.Knative),
|
|
autoscaling.InitialScaleAnnotationKey: "non-integer",
|
|
},
|
|
},
|
|
Spec: v1alpha1.InferenceGraphSpec{
|
|
MinReplicas: &minScale,
|
|
Nodes: map[string]v1alpha1.InferenceRouter{
|
|
v1alpha1.GraphRootNodeName: {
|
|
RouterType: v1alpha1.Sequence,
|
|
Steps: []v1alpha1.InferenceStep{
|
|
{
|
|
InferenceTarget: v1alpha1.InferenceTarget{
|
|
ServiceURL: "http://someservice.example.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
Expect(k8sClient.Create(ctx, ig)).Should(Succeed())
|
|
defer k8sClient.Delete(ctx, ig)
|
|
|
|
actualService := &knservingv1.Service{}
|
|
Eventually(func() error {
|
|
return k8sClient.Get(context.TODO(), serviceKey, actualService)
|
|
}, timeout).
|
|
Should(Succeed())
|
|
|
|
Expect(actualService.Spec.Template.Annotations).NotTo(HaveKey(autoscaling.InitialScaleAnnotationKey))
|
|
})
|
|
})
|
|
})
|
|
Context("with knative configured to allow zero initial scale", func() {
|
|
BeforeEach(func() {
|
|
time.Sleep(10 * time.Second)
|
|
// Patch the existing config-autoscaler configmap to set allow-zero-initial-scale to true
|
|
configAutoscaler := &corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: constants.AutoscalerConfigmapName,
|
|
Namespace: constants.AutoscalerConfigmapNamespace,
|
|
},
|
|
}
|
|
configPatch := []byte(`{"data":{"allow-zero-initial-scale":"true"}}`)
|
|
Eventually(func() error {
|
|
return k8sClient.Patch(context.TODO(), configAutoscaler, client.RawPatch(types.StrategicMergePatchType, configPatch))
|
|
}, timeout).Should(Succeed())
|
|
})
|
|
AfterEach(func() {
|
|
time.Sleep(10 * time.Second)
|
|
// Restore the default autoscaling configuration
|
|
configAutoscaler := &corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: constants.AutoscalerConfigmapName,
|
|
Namespace: constants.AutoscalerConfigmapNamespace,
|
|
},
|
|
}
|
|
configPatch := []byte(`{"data":{}}`)
|
|
Eventually(func() error {
|
|
return k8sClient.Patch(context.TODO(), configAutoscaler, client.RawPatch(types.StrategicMergePatchType, configPatch))
|
|
}, timeout).Should(Succeed())
|
|
})
|
|
When("a Knative InferenceGraph is created with an initial scale annotation and value of zero", func() {
|
|
It("should override the default initial scale value with the annotation value", func() {
|
|
// Create configmap
|
|
configMap := &corev1.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)
|
|
|
|
graphName := "initialscale4"
|
|
expectedRequest := reconcile.Request{NamespacedName: types.NamespacedName{Name: graphName, Namespace: "default"}}
|
|
serviceKey := expectedRequest.NamespacedName
|
|
ctx := context.Background()
|
|
var minScale int32 = 2
|
|
ig := &v1alpha1.InferenceGraph{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: serviceKey.Name,
|
|
Namespace: serviceKey.Namespace,
|
|
Annotations: map[string]string{
|
|
"serving.kserve.io/deploymentMode": string(constants.Knative),
|
|
autoscaling.InitialScaleAnnotationKey: "0",
|
|
},
|
|
},
|
|
Spec: v1alpha1.InferenceGraphSpec{
|
|
MinReplicas: &minScale,
|
|
Nodes: map[string]v1alpha1.InferenceRouter{
|
|
v1alpha1.GraphRootNodeName: {
|
|
RouterType: v1alpha1.Sequence,
|
|
Steps: []v1alpha1.InferenceStep{
|
|
{
|
|
InferenceTarget: v1alpha1.InferenceTarget{
|
|
ServiceURL: "http://someservice.example.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
Expect(k8sClient.Create(ctx, ig)).Should(Succeed())
|
|
defer k8sClient.Delete(ctx, ig)
|
|
|
|
actualService := &knservingv1.Service{}
|
|
Eventually(func() error {
|
|
return k8sClient.Get(context.TODO(), serviceKey, actualService)
|
|
}, timeout).
|
|
Should(Succeed())
|
|
|
|
Expect(actualService.Spec.Template.Annotations[autoscaling.InitialScaleAnnotationKey]).To(Equal("0"))
|
|
})
|
|
})
|
|
When("a Knative InferenceGraph is created with an initial scale annotation and valid non-zero integer value", func() {
|
|
It("should override the default initial scale value with the annotation value", func() {
|
|
// Create configmap
|
|
configMap := &corev1.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)
|
|
|
|
graphName := "initialscale5"
|
|
expectedRequest := reconcile.Request{NamespacedName: types.NamespacedName{Name: graphName, Namespace: "default"}}
|
|
serviceKey := expectedRequest.NamespacedName
|
|
ctx := context.Background()
|
|
var minScale int32 = 2
|
|
ig := &v1alpha1.InferenceGraph{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: serviceKey.Name,
|
|
Namespace: serviceKey.Namespace,
|
|
Annotations: map[string]string{
|
|
"serving.kserve.io/deploymentMode": string(constants.Knative),
|
|
autoscaling.InitialScaleAnnotationKey: "3",
|
|
},
|
|
},
|
|
Spec: v1alpha1.InferenceGraphSpec{
|
|
MinReplicas: &minScale,
|
|
Nodes: map[string]v1alpha1.InferenceRouter{
|
|
v1alpha1.GraphRootNodeName: {
|
|
RouterType: v1alpha1.Sequence,
|
|
Steps: []v1alpha1.InferenceStep{
|
|
{
|
|
InferenceTarget: v1alpha1.InferenceTarget{
|
|
ServiceURL: "http://someservice.example.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
Expect(k8sClient.Create(ctx, ig)).Should(Succeed())
|
|
defer k8sClient.Delete(ctx, ig)
|
|
|
|
actualService := &knservingv1.Service{}
|
|
Eventually(func() error {
|
|
return k8sClient.Get(context.TODO(), serviceKey, actualService)
|
|
}, timeout).
|
|
Should(Succeed())
|
|
|
|
Expect(actualService.Spec.Template.Annotations[autoscaling.InitialScaleAnnotationKey]).To(Equal("3"))
|
|
})
|
|
})
|
|
When("a Knative InferenceGraph is created with an initial scale annotation and invalid non-integer value", func() {
|
|
It("should ignore the annotation", func() {
|
|
// Create configmap
|
|
configMap := &corev1.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)
|
|
|
|
graphName := "initialscale6"
|
|
expectedRequest := reconcile.Request{NamespacedName: types.NamespacedName{Name: graphName, Namespace: "default"}}
|
|
serviceKey := expectedRequest.NamespacedName
|
|
ctx := context.Background()
|
|
var minScale int32 = 2
|
|
ig := &v1alpha1.InferenceGraph{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: serviceKey.Name,
|
|
Namespace: serviceKey.Namespace,
|
|
Annotations: map[string]string{
|
|
"serving.kserve.io/deploymentMode": string(constants.Knative),
|
|
autoscaling.InitialScaleAnnotationKey: "non-integer",
|
|
},
|
|
},
|
|
Spec: v1alpha1.InferenceGraphSpec{
|
|
MinReplicas: &minScale,
|
|
Nodes: map[string]v1alpha1.InferenceRouter{
|
|
v1alpha1.GraphRootNodeName: {
|
|
RouterType: v1alpha1.Sequence,
|
|
Steps: []v1alpha1.InferenceStep{
|
|
{
|
|
InferenceTarget: v1alpha1.InferenceTarget{
|
|
ServiceURL: "http://someservice.example.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
Expect(k8sClient.Create(ctx, ig)).Should(Succeed())
|
|
defer k8sClient.Delete(ctx, ig)
|
|
|
|
actualService := &knservingv1.Service{}
|
|
Eventually(func() error {
|
|
return k8sClient.Get(context.TODO(), serviceKey, actualService)
|
|
}, timeout).
|
|
Should(Succeed())
|
|
|
|
Expect(actualService.Spec.Template.Annotations).NotTo(HaveKey(autoscaling.InitialScaleAnnotationKey))
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("When creating an inferencegraph with headers in global config", func() {
|
|
It("Should create a knative service with headers as env var of podspec", func() {
|
|
By("By creating a new InferenceGraph")
|
|
configMap := &corev1.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)
|
|
graphName := "singlenode1"
|
|
expectedRequest := reconcile.Request{NamespacedName: types.NamespacedName{Name: graphName, Namespace: "default"}}
|
|
serviceKey := expectedRequest.NamespacedName
|
|
ctx := context.Background()
|
|
ig := &v1alpha1.InferenceGraph{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: serviceKey.Name,
|
|
Namespace: serviceKey.Namespace,
|
|
Annotations: map[string]string{
|
|
"serving.kserve.io/deploymentMode": string(constants.Knative),
|
|
},
|
|
},
|
|
Spec: v1alpha1.InferenceGraphSpec{
|
|
Nodes: map[string]v1alpha1.InferenceRouter{
|
|
v1alpha1.GraphRootNodeName: {
|
|
RouterType: v1alpha1.Sequence,
|
|
Steps: []v1alpha1.InferenceStep{
|
|
{
|
|
InferenceTarget: v1alpha1.InferenceTarget{
|
|
ServiceURL: "http://someservice.example.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
Expect(k8sClient.Create(ctx, ig)).Should(Succeed())
|
|
defer k8sClient.Delete(ctx, ig)
|
|
inferenceGraphSubmitted := &v1alpha1.InferenceGraph{}
|
|
Eventually(func() bool {
|
|
err := k8sClient.Get(ctx, serviceKey, inferenceGraphSubmitted)
|
|
return err == nil
|
|
}, timeout, interval).Should(BeTrue())
|
|
|
|
actualKnServiceCreated := &knservingv1.Service{}
|
|
Eventually(func() error {
|
|
return k8sClient.Get(context.TODO(), serviceKey, actualKnServiceCreated)
|
|
}, timeout).
|
|
Should(Succeed())
|
|
|
|
expectedKnService := &knservingv1.Service{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: serviceKey.Name,
|
|
Namespace: serviceKey.Namespace,
|
|
},
|
|
Spec: knservingv1.ServiceSpec{
|
|
ConfigurationSpec: knservingv1.ConfigurationSpec{
|
|
Template: knservingv1.RevisionTemplateSpec{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{
|
|
"serving.kserve.io/inferencegraph": graphName,
|
|
constants.KServeWorkloadKind: "InferenceGraph",
|
|
},
|
|
Annotations: map[string]string{
|
|
"autoscaling.knative.dev/min-scale": "1",
|
|
"autoscaling.knative.dev/class": "kpa.autoscaling.knative.dev",
|
|
"serving.kserve.io/deploymentMode": "Knative",
|
|
},
|
|
},
|
|
Spec: knservingv1.RevisionSpec{
|
|
ContainerConcurrency: nil,
|
|
TimeoutSeconds: nil,
|
|
PodSpec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Image: "kserve/router:v0.10.0",
|
|
Env: []corev1.EnvVar{
|
|
{
|
|
Name: "PROPAGATE_HEADERS",
|
|
Value: "Authorization,Intuit_tid",
|
|
},
|
|
},
|
|
Args: []string{
|
|
"--graph-json",
|
|
"{\"nodes\":{\"root\":{\"routerType\":\"Sequence\",\"steps\":[{\"serviceUrl\":\"http://someservice.example.com\"}]}},\"resources\":{}}",
|
|
},
|
|
Resources: corev1.ResourceRequirements{
|
|
Limits: corev1.ResourceList{
|
|
corev1.ResourceCPU: resource.MustParse("100m"),
|
|
corev1.ResourceMemory: resource.MustParse("500Mi"),
|
|
},
|
|
Requests: corev1.ResourceList{
|
|
corev1.ResourceCPU: resource.MustParse("100m"),
|
|
corev1.ResourceMemory: resource.MustParse("100Mi"),
|
|
},
|
|
},
|
|
ReadinessProbe: expectedReadinessProbe,
|
|
SecurityContext: &corev1.SecurityContext{
|
|
Privileged: proto.Bool(false),
|
|
RunAsNonRoot: proto.Bool(true),
|
|
ReadOnlyRootFilesystem: proto.Bool(true),
|
|
AllowPrivilegeEscalation: proto.Bool(false),
|
|
Capabilities: &corev1.Capabilities{
|
|
Drop: []corev1.Capability{corev1.Capability("ALL")},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
AutomountServiceAccountToken: proto.Bool(false),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
// Set ResourceVersion which is required for update operation.
|
|
expectedKnService.ResourceVersion = actualKnServiceCreated.ResourceVersion
|
|
|
|
// Do a dry-run update. This will populate our local knative service object with any default values
|
|
// that are present on the remote version.
|
|
err := k8sClient.Update(context.TODO(), expectedKnService, client.DryRunAll)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
Expect(kmp.SafeDiff(actualKnServiceCreated.Spec, expectedKnService.Spec)).To(Equal(""))
|
|
})
|
|
})
|
|
|
|
Context("When creating an IG with resource requirements in the spec", func() {
|
|
It("Should propagate to underlying pod", func() {
|
|
configMap := &corev1.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)
|
|
graphName := "singlenode2"
|
|
expectedRequest := reconcile.Request{NamespacedName: types.NamespacedName{Name: graphName, Namespace: "default"}}
|
|
serviceKey := expectedRequest.NamespacedName
|
|
ctx := context.Background()
|
|
ig := &v1alpha1.InferenceGraph{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: serviceKey.Name,
|
|
Namespace: serviceKey.Namespace,
|
|
Annotations: map[string]string{
|
|
"serving.kserve.io/deploymentMode": string(constants.Knative),
|
|
},
|
|
},
|
|
Spec: v1alpha1.InferenceGraphSpec{
|
|
Resources: corev1.ResourceRequirements{
|
|
Limits: corev1.ResourceList{
|
|
corev1.ResourceCPU: resource.MustParse("123m"),
|
|
corev1.ResourceMemory: resource.MustParse("123Mi"),
|
|
},
|
|
Requests: corev1.ResourceList{
|
|
corev1.ResourceCPU: resource.MustParse("123m"),
|
|
corev1.ResourceMemory: resource.MustParse("123Mi"),
|
|
},
|
|
},
|
|
Nodes: map[string]v1alpha1.InferenceRouter{
|
|
v1alpha1.GraphRootNodeName: {
|
|
RouterType: v1alpha1.Sequence,
|
|
Steps: []v1alpha1.InferenceStep{
|
|
{
|
|
InferenceTarget: v1alpha1.InferenceTarget{
|
|
ServiceURL: "http://someservice.example.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
Expect(k8sClient.Create(ctx, ig)).Should(Succeed())
|
|
defer k8sClient.Delete(ctx, ig)
|
|
inferenceGraphSubmitted := &v1alpha1.InferenceGraph{}
|
|
Eventually(func() bool {
|
|
err := k8sClient.Get(ctx, serviceKey, inferenceGraphSubmitted)
|
|
return err == nil
|
|
}, timeout, interval).Should(BeTrue())
|
|
|
|
actualKnServiceCreated := &knservingv1.Service{}
|
|
Eventually(func() error {
|
|
return k8sClient.Get(context.TODO(), serviceKey, actualKnServiceCreated)
|
|
}, timeout).
|
|
Should(Succeed())
|
|
|
|
expectedKnService := &knservingv1.Service{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: serviceKey.Name,
|
|
Namespace: serviceKey.Namespace,
|
|
},
|
|
Spec: knservingv1.ServiceSpec{
|
|
ConfigurationSpec: knservingv1.ConfigurationSpec{
|
|
Template: knservingv1.RevisionTemplateSpec{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{
|
|
"serving.kserve.io/inferencegraph": graphName,
|
|
constants.KServeWorkloadKind: "InferenceGraph",
|
|
},
|
|
Annotations: map[string]string{
|
|
"autoscaling.knative.dev/min-scale": "1",
|
|
"autoscaling.knative.dev/class": "kpa.autoscaling.knative.dev",
|
|
"serving.kserve.io/deploymentMode": "Knative",
|
|
},
|
|
},
|
|
Spec: knservingv1.RevisionSpec{
|
|
ContainerConcurrency: nil,
|
|
TimeoutSeconds: nil,
|
|
PodSpec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Image: "kserve/router:v0.10.0",
|
|
Env: []corev1.EnvVar{
|
|
{
|
|
Name: "PROPAGATE_HEADERS",
|
|
Value: "Authorization,Intuit_tid",
|
|
},
|
|
},
|
|
Args: []string{
|
|
"--graph-json",
|
|
"{\"nodes\":{\"root\":{\"routerType\":\"Sequence\",\"steps\":[{\"serviceUrl\":\"http://someservice.example.com\"}]}},\"resources\":{\"limits\":{\"cpu\":\"123m\",\"memory\":\"123Mi\"},\"requests\":{\"cpu\":\"123m\",\"memory\":\"123Mi\"}}}",
|
|
},
|
|
Resources: corev1.ResourceRequirements{
|
|
Limits: corev1.ResourceList{
|
|
corev1.ResourceCPU: resource.MustParse("123m"),
|
|
corev1.ResourceMemory: resource.MustParse("123Mi"),
|
|
},
|
|
Requests: corev1.ResourceList{
|
|
corev1.ResourceCPU: resource.MustParse("123m"),
|
|
corev1.ResourceMemory: resource.MustParse("123Mi"),
|
|
},
|
|
},
|
|
ReadinessProbe: expectedReadinessProbe,
|
|
SecurityContext: &corev1.SecurityContext{
|
|
Privileged: proto.Bool(false),
|
|
RunAsNonRoot: proto.Bool(true),
|
|
ReadOnlyRootFilesystem: proto.Bool(true),
|
|
AllowPrivilegeEscalation: proto.Bool(false),
|
|
Capabilities: &corev1.Capabilities{
|
|
Drop: []corev1.Capability{corev1.Capability("ALL")},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
AutomountServiceAccountToken: proto.Bool(false),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
// Set ResourceVersion which is required for update operation.
|
|
expectedKnService.ResourceVersion = actualKnServiceCreated.ResourceVersion
|
|
|
|
// Do a dry-run update. This will populate our local knative service object with any default values
|
|
// that are present on the remote version.
|
|
err := k8sClient.Update(context.TODO(), expectedKnService, client.DryRunAll)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
Expect(kmp.SafeDiff(actualKnServiceCreated.Spec, expectedKnService.Spec)).To(Equal(""))
|
|
})
|
|
})
|
|
|
|
Context("When creating an IG with podaffinity in the spec", func() {
|
|
It("Should propagate to underlying pod", func() {
|
|
configMap := &corev1.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)
|
|
graphName := "singlenode3"
|
|
expectedRequest := reconcile.Request{NamespacedName: types.NamespacedName{Name: graphName, Namespace: "default"}}
|
|
serviceKey := expectedRequest.NamespacedName
|
|
ctx := context.Background()
|
|
ig := &v1alpha1.InferenceGraph{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: serviceKey.Name,
|
|
Namespace: serviceKey.Namespace,
|
|
Annotations: map[string]string{
|
|
"serving.kserve.io/deploymentMode": string(constants.Knative),
|
|
},
|
|
},
|
|
|
|
Spec: v1alpha1.InferenceGraphSpec{
|
|
Affinity: &corev1.Affinity{
|
|
PodAffinity: &corev1.PodAffinity{
|
|
PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{
|
|
{
|
|
Weight: 100,
|
|
PodAffinityTerm: corev1.PodAffinityTerm{
|
|
LabelSelector: &metav1.LabelSelector{
|
|
MatchExpressions: []metav1.LabelSelectorRequirement{
|
|
{
|
|
Key: "serving.kserve.io/inferencegraph",
|
|
Operator: metav1.LabelSelectorOpIn,
|
|
Values: []string{
|
|
graphName,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
TopologyKey: "topology.kubernetes.io/zone",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Nodes: map[string]v1alpha1.InferenceRouter{
|
|
v1alpha1.GraphRootNodeName: {
|
|
RouterType: v1alpha1.Sequence,
|
|
Steps: []v1alpha1.InferenceStep{
|
|
{
|
|
InferenceTarget: v1alpha1.InferenceTarget{
|
|
ServiceURL: "http://someservice.example.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
Expect(k8sClient.Create(ctx, ig)).Should(Succeed())
|
|
defer k8sClient.Delete(ctx, ig)
|
|
inferenceGraphSubmitted := &v1alpha1.InferenceGraph{}
|
|
Eventually(func() bool {
|
|
err := k8sClient.Get(ctx, serviceKey, inferenceGraphSubmitted)
|
|
return err == nil
|
|
}, timeout, interval).Should(BeTrue())
|
|
|
|
actualKnServiceCreated := &knservingv1.Service{}
|
|
Eventually(func() error {
|
|
return k8sClient.Get(context.TODO(), serviceKey, actualKnServiceCreated)
|
|
}, timeout).
|
|
Should(Succeed())
|
|
|
|
expectedKnService := &knservingv1.Service{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: serviceKey.Name,
|
|
Namespace: serviceKey.Namespace,
|
|
},
|
|
Spec: knservingv1.ServiceSpec{
|
|
ConfigurationSpec: knservingv1.ConfigurationSpec{
|
|
Template: knservingv1.RevisionTemplateSpec{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{
|
|
"serving.kserve.io/inferencegraph": graphName,
|
|
constants.KServeWorkloadKind: "InferenceGraph",
|
|
},
|
|
Annotations: map[string]string{
|
|
"autoscaling.knative.dev/min-scale": "1",
|
|
"autoscaling.knative.dev/class": "kpa.autoscaling.knative.dev",
|
|
"serving.kserve.io/deploymentMode": "Knative",
|
|
},
|
|
},
|
|
Spec: knservingv1.RevisionSpec{
|
|
ContainerConcurrency: nil,
|
|
TimeoutSeconds: nil,
|
|
PodSpec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Image: "kserve/router:v0.10.0",
|
|
Env: []corev1.EnvVar{
|
|
{
|
|
Name: "PROPAGATE_HEADERS",
|
|
Value: "Authorization,Intuit_tid",
|
|
},
|
|
},
|
|
Args: []string{
|
|
"--graph-json",
|
|
"{\"nodes\":{\"root\":{\"routerType\":\"Sequence\",\"steps\":[{\"serviceUrl\":\"http://someservice.example.com\"}]}},\"resources\":{},\"affinity\":{\"podAffinity\":{\"preferredDuringSchedulingIgnoredDuringExecution\":[{\"weight\":100,\"podAffinityTerm\":{\"labelSelector\":{\"matchExpressions\":[{\"key\":\"serving.kserve.io/inferencegraph\",\"operator\":\"In\",\"values\":[\"singlenode3\"]}]},\"topologyKey\":\"topology.kubernetes.io/zone\"}}]}}}",
|
|
},
|
|
Resources: corev1.ResourceRequirements{
|
|
Limits: corev1.ResourceList{
|
|
corev1.ResourceCPU: resource.MustParse("100m"),
|
|
corev1.ResourceMemory: resource.MustParse("500Mi"),
|
|
},
|
|
Requests: corev1.ResourceList{
|
|
corev1.ResourceCPU: resource.MustParse("100m"),
|
|
corev1.ResourceMemory: resource.MustParse("100Mi"),
|
|
},
|
|
},
|
|
SecurityContext: &corev1.SecurityContext{
|
|
Privileged: proto.Bool(false),
|
|
RunAsNonRoot: proto.Bool(true),
|
|
ReadOnlyRootFilesystem: proto.Bool(true),
|
|
AllowPrivilegeEscalation: proto.Bool(false),
|
|
Capabilities: &corev1.Capabilities{
|
|
Drop: []corev1.Capability{corev1.Capability("ALL")},
|
|
},
|
|
},
|
|
ReadinessProbe: expectedReadinessProbe,
|
|
},
|
|
},
|
|
Affinity: &corev1.Affinity{
|
|
PodAffinity: &corev1.PodAffinity{
|
|
PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{
|
|
{
|
|
Weight: 100,
|
|
PodAffinityTerm: corev1.PodAffinityTerm{
|
|
LabelSelector: &metav1.LabelSelector{
|
|
MatchExpressions: []metav1.LabelSelectorRequirement{
|
|
{
|
|
Key: "serving.kserve.io/inferencegraph",
|
|
Operator: metav1.LabelSelectorOpIn,
|
|
Values: []string{
|
|
graphName,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
TopologyKey: "topology.kubernetes.io/zone",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
AutomountServiceAccountToken: proto.Bool(false),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
// Set ResourceVersion which is required for update operation.
|
|
expectedKnService.ResourceVersion = actualKnServiceCreated.ResourceVersion
|
|
|
|
// Do a dry-run update. This will populate our local knative service object with any default values
|
|
// that are present on the remote version.
|
|
err := k8sClient.Update(context.TODO(), expectedKnService, client.DryRunAll)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
Expect(kmp.SafeDiff(actualKnServiceCreated.Spec, expectedKnService.Spec)).To(Equal(""))
|
|
})
|
|
})
|
|
|
|
Context("When creating an inferencegraph in Raw deployment mode with annotations", func() {
|
|
It("Should create a raw k8s resources with podspec", func() {
|
|
By("By creating a new InferenceGraph")
|
|
configMap := &corev1.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)
|
|
graphName := "igraw1"
|
|
expectedRequest := reconcile.Request{NamespacedName: types.NamespacedName{Name: graphName, Namespace: "default"}}
|
|
serviceKey := expectedRequest.NamespacedName
|
|
ctx := context.Background()
|
|
ig := &v1alpha1.InferenceGraph{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: serviceKey.Name,
|
|
Namespace: serviceKey.Namespace,
|
|
Annotations: map[string]string{
|
|
"serving.kserve.io/deploymentMode": string(constants.Standard),
|
|
},
|
|
},
|
|
Spec: v1alpha1.InferenceGraphSpec{
|
|
Nodes: map[string]v1alpha1.InferenceRouter{
|
|
v1alpha1.GraphRootNodeName: {
|
|
RouterType: v1alpha1.Sequence,
|
|
Steps: []v1alpha1.InferenceStep{
|
|
{
|
|
InferenceTarget: v1alpha1.InferenceTarget{
|
|
ServiceURL: "http://someservice.example.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
Expect(k8sClient.Create(ctx, ig)).Should(Succeed())
|
|
defer k8sClient.Delete(ctx, ig)
|
|
inferenceGraphSubmitted := &v1alpha1.InferenceGraph{}
|
|
Eventually(func() bool {
|
|
err := k8sClient.Get(ctx, serviceKey, inferenceGraphSubmitted)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
By("Inference graph retrieved")
|
|
return true
|
|
}, timeout, interval).Should(BeTrue())
|
|
|
|
actualK8sDeploymentCreated := &appsv1.Deployment{}
|
|
Eventually(func() bool {
|
|
if err := k8sClient.Get(ctx, serviceKey, actualK8sDeploymentCreated); err != nil {
|
|
return false
|
|
}
|
|
By("K8s Deployment retrieved")
|
|
return true
|
|
}, timeout, interval).Should(BeTrue())
|
|
|
|
actualK8sServiceCreated := &corev1.Service{}
|
|
Eventually(func() bool {
|
|
if err := k8sClient.Get(ctx, serviceKey, actualK8sServiceCreated); err != nil {
|
|
return false
|
|
}
|
|
By("K8s Service retrieved")
|
|
return true
|
|
}, timeout, interval).Should(BeTrue())
|
|
|
|
// No Knative Service should get created in Raw deployment mode
|
|
actualKnServiceCreated := &knservingv1.Service{}
|
|
Eventually(func() bool {
|
|
if err := k8sClient.Get(context.TODO(), serviceKey, actualKnServiceCreated); err != nil {
|
|
By("Knative Service not retrieved")
|
|
return false
|
|
}
|
|
return true
|
|
}, timeout).
|
|
Should(BeFalse())
|
|
|
|
// No Knative Route should get created in Raw deployment mode
|
|
actualKnRouteCreated := &knservingv1.Route{}
|
|
Eventually(func() bool {
|
|
if err := k8sClient.Get(context.TODO(), serviceKey, actualKnRouteCreated); err != nil {
|
|
return false
|
|
}
|
|
return true
|
|
}, timeout).
|
|
Should(BeFalse())
|
|
|
|
result := int32(1)
|
|
Expect(actualK8sDeploymentCreated.Name).To(Equal(graphName))
|
|
Expect(actualK8sDeploymentCreated.Spec.Replicas).To(Equal(&result))
|
|
Expect(actualK8sDeploymentCreated.Spec.Template.Spec.Containers).To(Not(BeNil()))
|
|
Expect(actualK8sDeploymentCreated.Spec.Template.Spec.Containers[0].Image).To(Not(BeNil()))
|
|
Expect(actualK8sDeploymentCreated.Spec.Template.Spec.Containers[0].Args).To(Not(BeNil()))
|
|
})
|
|
})
|
|
|
|
Context("When creating an InferenceGraph in Knative mode", func() {
|
|
It("Should fail if Knative Serving is not installed", func() {
|
|
// Simulate Knative Serving is absent by setting to false the relevant item in utils.gvResourcesCache variable
|
|
servingResources, getServingResourcesErr := utils.GetAvailableResourcesForApi(cfg, knservingv1.SchemeGroupVersion.String())
|
|
Expect(getServingResourcesErr).ToNot(HaveOccurred())
|
|
defer utils.SetAvailableResourcesForApi(knservingv1.SchemeGroupVersion.String(), servingResources)
|
|
utils.SetAvailableResourcesForApi(knservingv1.SchemeGroupVersion.String(), nil)
|
|
|
|
By("By creating a new InferenceGraph")
|
|
configMap := &corev1.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)
|
|
|
|
graphName := "singlenode1"
|
|
expectedRequest := reconcile.Request{NamespacedName: types.NamespacedName{Name: graphName, Namespace: "default"}}
|
|
serviceKey := expectedRequest.NamespacedName
|
|
ctx := context.Background()
|
|
ig := &v1alpha1.InferenceGraph{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: serviceKey.Name,
|
|
Namespace: serviceKey.Namespace,
|
|
Annotations: map[string]string{
|
|
"serving.kserve.io/deploymentMode": string(constants.Knative),
|
|
},
|
|
},
|
|
Spec: v1alpha1.InferenceGraphSpec{
|
|
Nodes: map[string]v1alpha1.InferenceRouter{
|
|
v1alpha1.GraphRootNodeName: {
|
|
RouterType: v1alpha1.Sequence,
|
|
Steps: []v1alpha1.InferenceStep{
|
|
{
|
|
InferenceTarget: v1alpha1.InferenceTarget{
|
|
ServiceURL: "http://someservice.example.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
Expect(k8sClient.Create(ctx, ig)).Should(Succeed())
|
|
defer k8sClient.Delete(ctx, ig)
|
|
|
|
Eventually(func() bool {
|
|
events := &corev1.EventList{}
|
|
err := k8sClient.List(ctx, events, client.InNamespace(serviceKey.Namespace))
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
for _, event := range events.Items {
|
|
if event.InvolvedObject.Kind == "InferenceGraph" &&
|
|
event.InvolvedObject.Name == serviceKey.Name &&
|
|
event.Reason == "ServerlessModeRejected" {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}, timeout, interval).Should(BeTrue())
|
|
})
|
|
})
|
|
|
|
Context("When creating an InferenceGraph with `serving.kserve.io/stop`", func() {
|
|
// --- Default values ---
|
|
createIGConfigMap := func() *corev1.ConfigMap {
|
|
configMap := &corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: constants.InferenceServiceConfigMapName,
|
|
Namespace: constants.KServeNamespace,
|
|
},
|
|
Data: configs,
|
|
}
|
|
return configMap
|
|
}
|
|
|
|
// --- Reusable Check Functions ---
|
|
// Wait for the IG to exist.
|
|
expectIGToExist := func(ctx context.Context, serviceKey types.NamespacedName) v1alpha1.InferenceGraph {
|
|
actualIG := &v1alpha1.InferenceGraph{}
|
|
Eventually(func() bool {
|
|
err := k8sClient.Get(ctx, serviceKey, actualIG)
|
|
return err == nil
|
|
}, timeout, interval).Should(BeTrue())
|
|
|
|
return *actualIG
|
|
}
|
|
|
|
// Waits for any Kubernetes object to be found
|
|
expectResourceToExist := func(ctx context.Context, obj client.Object, objKey types.NamespacedName) {
|
|
Eventually(func() bool {
|
|
err := k8sClient.Get(ctx, objKey, obj)
|
|
return err == nil
|
|
}, timeout, interval).Should(BeTrue(), "%T %s should exist", obj, objKey.Name)
|
|
}
|
|
|
|
// Checks that any Kubernetes object to be not found.
|
|
expectResourceIsDeleted := func(ctx context.Context, obj client.Object, objKey types.NamespacedName) {
|
|
Consistently(func() bool {
|
|
err := k8sClient.Get(ctx, objKey, obj)
|
|
return apierr.IsNotFound(err)
|
|
}, timeout, interval).Should(BeTrue(), "%T %s should not be created", obj, objKey.Name)
|
|
}
|
|
|
|
// Wait for any Kubernetes object to be not found.
|
|
expectResourceToBeDeleted := func(ctx context.Context, obj client.Object, objKey types.NamespacedName) {
|
|
Eventually(func() bool {
|
|
err := k8sClient.Get(ctx, objKey, obj)
|
|
return apierr.IsNotFound(err)
|
|
}, timeout, interval).Should(BeTrue(), "%T %s should be deleted", obj, objKey.Name)
|
|
}
|
|
|
|
// Wait for a specific condition on an InferenceGraph to reach the desired status
|
|
expectIGConditionStatus := func(ctx context.Context, serviceKey types.NamespacedName, conditionType apis.ConditionType, expectedStatus corev1.ConditionStatus) {
|
|
message := fmt.Sprintf("The '%s' condition for InferenceGraph '%s' should be '%s'",
|
|
conditionType, serviceKey.Name, expectedStatus)
|
|
|
|
actualIg := &v1alpha1.InferenceGraph{}
|
|
Eventually(func() bool {
|
|
err := k8sClient.Get(ctx, serviceKey, actualIg)
|
|
if err == nil {
|
|
cond := actualIg.Status.GetCondition(conditionType)
|
|
if cond != nil && cond.Status == expectedStatus {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}, timeout, interval).Should(BeTrue(), message)
|
|
}
|
|
|
|
Describe("in Knative mode", func() {
|
|
// --- Default values ---
|
|
defaultIG := func(serviceKey types.NamespacedName) *v1alpha1.InferenceGraph {
|
|
ig := &v1alpha1.InferenceGraph{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: serviceKey.Name,
|
|
Namespace: serviceKey.Namespace,
|
|
Annotations: map[string]string{
|
|
"serving.kserve.io/deploymentMode": string(constants.Knative),
|
|
},
|
|
},
|
|
Spec: v1alpha1.InferenceGraphSpec{
|
|
Nodes: map[string]v1alpha1.InferenceRouter{
|
|
v1alpha1.GraphRootNodeName: {
|
|
RouterType: v1alpha1.Sequence,
|
|
Steps: []v1alpha1.InferenceStep{
|
|
{
|
|
InferenceTarget: v1alpha1.InferenceTarget{
|
|
ServiceURL: "http://someservice.example.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
return ig
|
|
}
|
|
|
|
It("Should keep the knative service when the StopAnnotationKey annotation is set to false", func() {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
DeferCleanup(cancel)
|
|
|
|
// Config map
|
|
configMap := createIGConfigMap()
|
|
Expect(k8sClient.Create(context.TODO(), configMap)).NotTo(HaveOccurred())
|
|
defer k8sClient.Delete(context.TODO(), configMap)
|
|
|
|
// Define InferenceGraph
|
|
serviceNamespace := "default"
|
|
graphName := "stop-false-ig"
|
|
graphExpectedRequest := reconcile.Request{NamespacedName: types.NamespacedName{Name: graphName, Namespace: serviceNamespace}}
|
|
graphServiceKey := graphExpectedRequest.NamespacedName
|
|
ig := defaultIG(graphServiceKey)
|
|
ig.Annotations[constants.StopAnnotationKey] = "false"
|
|
Expect(k8sClient.Create(ctx, ig)).Should(Succeed())
|
|
defer k8sClient.Delete(ctx, ig)
|
|
|
|
// Check the inference graph
|
|
expectResourceToExist(context.Background(), &knservingv1.Service{}, graphServiceKey)
|
|
expectIGToExist(context.Background(), graphServiceKey)
|
|
|
|
expectIGConditionStatus(ctx, graphServiceKey, v1beta1.Stopped, corev1.ConditionFalse)
|
|
})
|
|
|
|
It("Should not create the knative service when the StopAnnotationKey annotation is set to true", func() {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
DeferCleanup(cancel)
|
|
|
|
configMap := createIGConfigMap()
|
|
Expect(k8sClient.Create(ctx, configMap)).NotTo(HaveOccurred())
|
|
defer k8sClient.Delete(ctx, configMap)
|
|
|
|
graphName := "stop-true-ig"
|
|
serviceNamespace := "default"
|
|
expectedRequest := reconcile.Request{NamespacedName: types.NamespacedName{Name: graphName, Namespace: serviceNamespace}}
|
|
graphServiceKey := expectedRequest.NamespacedName
|
|
ig := defaultIG(graphServiceKey)
|
|
ig.Annotations[constants.StopAnnotationKey] = "true"
|
|
Expect(k8sClient.Create(context.Background(), ig)).Should(Succeed())
|
|
defer k8sClient.Delete(context.Background(), ig)
|
|
|
|
// Check that the knative service was not created
|
|
expectResourceIsDeleted(context.Background(), &knservingv1.Service{}, graphServiceKey)
|
|
|
|
// Check the inference graph
|
|
expectIGToExist(context.Background(), graphServiceKey)
|
|
expectIGConditionStatus(ctx, graphServiceKey, v1beta1.Stopped, corev1.ConditionTrue)
|
|
})
|
|
|
|
It("Should delete the knative service when the StopAnnotationKey annotation is updated to true on an existing IG", func() {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
DeferCleanup(cancel)
|
|
|
|
// Config map
|
|
configMap := createIGConfigMap()
|
|
Expect(k8sClient.Create(context.TODO(), configMap)).NotTo(HaveOccurred())
|
|
defer k8sClient.Delete(context.TODO(), configMap)
|
|
|
|
// Define InferenceGraph
|
|
serviceNamespace := "default"
|
|
graphName := "stop-update-true-ig"
|
|
graphExpectedRequest := reconcile.Request{NamespacedName: types.NamespacedName{Name: graphName, Namespace: serviceNamespace}}
|
|
graphServiceKey := graphExpectedRequest.NamespacedName
|
|
ig := defaultIG(graphServiceKey)
|
|
ig.Annotations[constants.StopAnnotationKey] = "false"
|
|
Expect(k8sClient.Create(ctx, ig)).Should(Succeed())
|
|
defer k8sClient.Delete(ctx, ig)
|
|
|
|
// Check the inference graph
|
|
expectResourceToExist(context.Background(), &knservingv1.Service{}, graphServiceKey)
|
|
expectIGToExist(context.Background(), graphServiceKey)
|
|
|
|
expectIGConditionStatus(ctx, graphServiceKey, v1beta1.Stopped, corev1.ConditionFalse)
|
|
|
|
// Stop the inference graph
|
|
actualIG := expectIGToExist(ctx, graphServiceKey)
|
|
updatedIG := actualIG.DeepCopy()
|
|
updatedIG.Annotations[constants.StopAnnotationKey] = "true"
|
|
Expect(k8sClient.Update(ctx, updatedIG)).NotTo(HaveOccurred())
|
|
|
|
// Check that the knative service was deleted
|
|
expectResourceToBeDeleted(context.Background(), &knservingv1.Service{}, graphServiceKey)
|
|
|
|
// Check the inference graph
|
|
expectIGToExist(context.Background(), graphServiceKey)
|
|
expectIGConditionStatus(ctx, graphServiceKey, v1beta1.Stopped, corev1.ConditionTrue)
|
|
})
|
|
|
|
It("Should create the knative service when the StopAnnotationKey annotation is updated to false on an existing IG", func() {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
DeferCleanup(cancel)
|
|
|
|
configMap := createIGConfigMap()
|
|
Expect(k8sClient.Create(ctx, configMap)).NotTo(HaveOccurred())
|
|
defer k8sClient.Delete(ctx, configMap)
|
|
|
|
graphName := "stop-update-false-ig"
|
|
serviceNamespace := "default"
|
|
expectedRequest := reconcile.Request{NamespacedName: types.NamespacedName{Name: graphName, Namespace: serviceNamespace}}
|
|
graphServiceKey := expectedRequest.NamespacedName
|
|
ig := defaultIG(graphServiceKey)
|
|
ig.Annotations[constants.StopAnnotationKey] = "true"
|
|
Expect(k8sClient.Create(context.Background(), ig)).Should(Succeed())
|
|
defer k8sClient.Delete(context.Background(), ig)
|
|
|
|
// Check that the knative service was not created
|
|
expectResourceIsDeleted(context.Background(), &knservingv1.Service{}, graphServiceKey)
|
|
|
|
// Check the inference graph
|
|
expectIGToExist(context.Background(), graphServiceKey)
|
|
expectIGConditionStatus(ctx, graphServiceKey, v1beta1.Stopped, corev1.ConditionTrue)
|
|
|
|
// Resume the inference graph
|
|
actualIG := expectIGToExist(ctx, graphServiceKey)
|
|
updatedIG := actualIG.DeepCopy()
|
|
updatedIG.Annotations[constants.StopAnnotationKey] = "false"
|
|
Expect(k8sClient.Update(ctx, updatedIG)).NotTo(HaveOccurred())
|
|
|
|
// Check the inference graph
|
|
expectResourceToExist(context.Background(), &knservingv1.Service{}, graphServiceKey)
|
|
expectIGToExist(context.Background(), graphServiceKey)
|
|
|
|
expectIGConditionStatus(ctx, graphServiceKey, v1beta1.Stopped, corev1.ConditionFalse)
|
|
})
|
|
})
|
|
|
|
Describe("in Raw Deployment mode", func() {
|
|
// --- Default values ---
|
|
defaultRawIG := func(serviceKey types.NamespacedName) *v1alpha1.InferenceGraph {
|
|
ig := &v1alpha1.InferenceGraph{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: serviceKey.Name,
|
|
Namespace: serviceKey.Namespace,
|
|
Annotations: map[string]string{
|
|
"serving.kserve.io/deploymentMode": string(constants.Standard),
|
|
},
|
|
},
|
|
Spec: v1alpha1.InferenceGraphSpec{
|
|
Nodes: map[string]v1alpha1.InferenceRouter{
|
|
v1alpha1.GraphRootNodeName: {
|
|
RouterType: v1alpha1.Sequence,
|
|
Steps: []v1alpha1.InferenceStep{
|
|
{
|
|
InferenceTarget: v1alpha1.InferenceTarget{
|
|
ServiceURL: "http://someservice.example.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
return ig
|
|
}
|
|
|
|
// --- Reusable Check Functions ---
|
|
// Updates the status of the deployment to Ready
|
|
// and applies the change to the Kubernetes API server.
|
|
expectDeploymentToBeReady := func(ctx context.Context, serviceKey types.NamespacedName) {
|
|
actualDeployment := &appsv1.Deployment{}
|
|
Eventually(func() error { return k8sClient.Get(ctx, serviceKey, actualDeployment) }, timeout).
|
|
Should(Succeed())
|
|
|
|
updatedDeployment := actualDeployment.DeepCopy()
|
|
updatedDeployment.Status.Conditions = []appsv1.DeploymentCondition{
|
|
{
|
|
Type: appsv1.DeploymentAvailable,
|
|
Status: corev1.ConditionTrue,
|
|
},
|
|
}
|
|
Expect(k8sClient.Status().Update(ctx, updatedDeployment)).NotTo(HaveOccurred())
|
|
}
|
|
|
|
It("Should keep the service/deployment when the StopAnnotationKey annotation is set to false", func() {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
DeferCleanup(cancel)
|
|
|
|
// Config map
|
|
configMap := createIGConfigMap()
|
|
Expect(k8sClient.Create(context.TODO(), configMap)).NotTo(HaveOccurred())
|
|
defer k8sClient.Delete(context.TODO(), configMap)
|
|
|
|
// Define InferenceGraph
|
|
serviceNamespace := "default"
|
|
graphName := "stop-raw-false-ig"
|
|
graphExpectedRequest := reconcile.Request{NamespacedName: types.NamespacedName{Name: graphName, Namespace: serviceNamespace}}
|
|
graphServiceKey := graphExpectedRequest.NamespacedName
|
|
ig := defaultRawIG(graphServiceKey)
|
|
ig.Annotations[constants.StopAnnotationKey] = "false"
|
|
Expect(k8sClient.Create(ctx, ig)).Should(Succeed())
|
|
defer k8sClient.Delete(ctx, ig)
|
|
|
|
// Check the inference graph
|
|
expectResourceToExist(context.Background(), &appsv1.Deployment{}, graphServiceKey)
|
|
expectDeploymentToBeReady(context.Background(), graphServiceKey)
|
|
|
|
expectResourceToExist(context.Background(), &corev1.Service{}, graphServiceKey)
|
|
expectIGToExist(context.Background(), graphServiceKey)
|
|
|
|
expectIGConditionStatus(ctx, graphServiceKey, v1beta1.Stopped, corev1.ConditionFalse)
|
|
})
|
|
|
|
It("Should not create the service/deployment when the StopAnnotationKey annotation is set to true", func() {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
DeferCleanup(cancel)
|
|
|
|
configMap := createIGConfigMap()
|
|
Expect(k8sClient.Create(ctx, configMap)).NotTo(HaveOccurred())
|
|
defer k8sClient.Delete(ctx, configMap)
|
|
|
|
graphName := "stop-raw-true-ig"
|
|
serviceNamespace := "default"
|
|
expectedRequest := reconcile.Request{NamespacedName: types.NamespacedName{Name: graphName, Namespace: serviceNamespace}}
|
|
graphServiceKey := expectedRequest.NamespacedName
|
|
ig := defaultRawIG(graphServiceKey)
|
|
ig.Annotations[constants.StopAnnotationKey] = "true"
|
|
Expect(k8sClient.Create(context.Background(), ig)).Should(Succeed())
|
|
defer k8sClient.Delete(context.Background(), ig)
|
|
|
|
// Check that the service and deployment were not created
|
|
expectResourceIsDeleted(context.Background(), &corev1.Service{}, graphServiceKey)
|
|
expectResourceIsDeleted(context.Background(), &appsv1.Deployment{}, graphServiceKey)
|
|
|
|
// Check the inference graph
|
|
expectIGToExist(context.Background(), graphServiceKey)
|
|
expectIGConditionStatus(ctx, graphServiceKey, v1beta1.Stopped, corev1.ConditionTrue)
|
|
})
|
|
|
|
It("Should delete the service/deployment when the StopAnnotationKey annotation is updated to true on an existing IG", func() {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
DeferCleanup(cancel)
|
|
|
|
// Config map
|
|
configMap := createIGConfigMap()
|
|
Expect(k8sClient.Create(context.TODO(), configMap)).NotTo(HaveOccurred())
|
|
defer k8sClient.Delete(context.TODO(), configMap)
|
|
|
|
// Define InferenceGraph
|
|
serviceNamespace := "default"
|
|
graphName := "stop-raw-update-true-ig"
|
|
graphExpectedRequest := reconcile.Request{NamespacedName: types.NamespacedName{Name: graphName, Namespace: serviceNamespace}}
|
|
graphServiceKey := graphExpectedRequest.NamespacedName
|
|
ig := defaultRawIG(graphServiceKey)
|
|
ig.Annotations[constants.StopAnnotationKey] = "false"
|
|
Expect(k8sClient.Create(ctx, ig)).Should(Succeed())
|
|
defer k8sClient.Delete(ctx, ig)
|
|
|
|
// Check the inference graph
|
|
expectResourceToExist(context.Background(), &appsv1.Deployment{}, graphServiceKey)
|
|
expectDeploymentToBeReady(context.Background(), graphServiceKey)
|
|
expectResourceToExist(context.Background(), &corev1.Service{}, graphServiceKey)
|
|
expectIGToExist(context.Background(), graphServiceKey)
|
|
|
|
expectIGConditionStatus(ctx, graphServiceKey, v1beta1.Stopped, corev1.ConditionFalse)
|
|
|
|
// Stop the inference graph
|
|
actualIG := expectIGToExist(ctx, graphServiceKey)
|
|
updatedIG := actualIG.DeepCopy()
|
|
updatedIG.Annotations[constants.StopAnnotationKey] = "true"
|
|
Expect(k8sClient.Update(ctx, updatedIG)).NotTo(HaveOccurred())
|
|
|
|
// Check that the service and deployment were deleted
|
|
expectResourceToBeDeleted(context.Background(), &corev1.Service{}, graphServiceKey)
|
|
expectResourceToBeDeleted(context.Background(), &appsv1.Deployment{}, graphServiceKey)
|
|
|
|
// Check the inference graph
|
|
expectIGToExist(context.Background(), graphServiceKey)
|
|
expectIGConditionStatus(ctx, graphServiceKey, v1beta1.Stopped, corev1.ConditionTrue)
|
|
})
|
|
|
|
It("Should create the service/deployment when the StopAnnotationKey annotation is updated to false on an existing IG", func() {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
DeferCleanup(cancel)
|
|
|
|
configMap := createIGConfigMap()
|
|
Expect(k8sClient.Create(ctx, configMap)).NotTo(HaveOccurred())
|
|
defer k8sClient.Delete(ctx, configMap)
|
|
|
|
graphName := "stop-raw-update-false-ig"
|
|
serviceNamespace := "default"
|
|
expectedRequest := reconcile.Request{NamespacedName: types.NamespacedName{Name: graphName, Namespace: serviceNamespace}}
|
|
graphServiceKey := expectedRequest.NamespacedName
|
|
ig := defaultRawIG(graphServiceKey)
|
|
ig.Annotations[constants.StopAnnotationKey] = "true"
|
|
Expect(k8sClient.Create(context.Background(), ig)).Should(Succeed())
|
|
defer k8sClient.Delete(context.Background(), ig)
|
|
|
|
// Check that the service and deployment were not created
|
|
expectResourceIsDeleted(context.Background(), &corev1.Service{}, graphServiceKey)
|
|
expectResourceIsDeleted(context.Background(), &appsv1.Deployment{}, graphServiceKey)
|
|
|
|
// Check the inference graph
|
|
expectIGToExist(context.Background(), graphServiceKey)
|
|
expectIGConditionStatus(ctx, graphServiceKey, v1beta1.Stopped, corev1.ConditionTrue)
|
|
|
|
// Resume the inference graph
|
|
actualIG := expectIGToExist(ctx, graphServiceKey)
|
|
updatedIG := actualIG.DeepCopy()
|
|
updatedIG.Annotations[constants.StopAnnotationKey] = "false"
|
|
Expect(k8sClient.Update(ctx, updatedIG)).NotTo(HaveOccurred())
|
|
|
|
// Check the inference graph
|
|
expectResourceToExist(context.Background(), &appsv1.Deployment{}, graphServiceKey)
|
|
expectDeploymentToBeReady(context.Background(), graphServiceKey)
|
|
expectResourceToExist(context.Background(), &corev1.Service{}, graphServiceKey)
|
|
expectIGToExist(context.Background(), graphServiceKey)
|
|
|
|
expectIGConditionStatus(ctx, graphServiceKey, v1beta1.Stopped, corev1.ConditionFalse)
|
|
})
|
|
})
|
|
})
|
|
|
|
Context("When creating an IG with tolerations in the spec", func() {
|
|
It("Should propagate to underlying pod", func() {
|
|
configMap := &corev1.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)
|
|
graphName := "singlenode4"
|
|
expectedRequest := reconcile.Request{NamespacedName: types.NamespacedName{Name: graphName, Namespace: "default"}}
|
|
serviceKey := expectedRequest.NamespacedName
|
|
ctx := context.Background()
|
|
ig := &v1alpha1.InferenceGraph{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: serviceKey.Name,
|
|
Namespace: serviceKey.Namespace,
|
|
Annotations: map[string]string{
|
|
"serving.kserve.io/deploymentMode": string(constants.Knative),
|
|
},
|
|
},
|
|
|
|
Spec: v1alpha1.InferenceGraphSpec{
|
|
Nodes: map[string]v1alpha1.InferenceRouter{
|
|
v1alpha1.GraphRootNodeName: {
|
|
RouterType: v1alpha1.Sequence,
|
|
Steps: []v1alpha1.InferenceStep{
|
|
{
|
|
InferenceTarget: v1alpha1.InferenceTarget{
|
|
ServiceURL: "http://someservice.example.com",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Tolerations: []corev1.Toleration{
|
|
{
|
|
Key: "key1",
|
|
Operator: corev1.TolerationOpEqual,
|
|
Value: "value1",
|
|
Effect: corev1.TaintEffectNoSchedule,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
Expect(k8sClient.Create(ctx, ig)).Should(Succeed())
|
|
defer k8sClient.Delete(ctx, ig)
|
|
inferenceGraphSubmitted := &v1alpha1.InferenceGraph{}
|
|
Eventually(func() bool {
|
|
err := k8sClient.Get(ctx, serviceKey, inferenceGraphSubmitted)
|
|
return err == nil
|
|
}, timeout, interval).Should(BeTrue())
|
|
|
|
actualKnServiceCreated := &knservingv1.Service{}
|
|
Eventually(func() error {
|
|
return k8sClient.Get(context.TODO(), serviceKey, actualKnServiceCreated)
|
|
}, timeout).
|
|
Should(Succeed())
|
|
|
|
expectedKnService := &knservingv1.Service{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: serviceKey.Name,
|
|
Namespace: serviceKey.Namespace,
|
|
},
|
|
Spec: knservingv1.ServiceSpec{
|
|
ConfigurationSpec: knservingv1.ConfigurationSpec{
|
|
Template: knservingv1.RevisionTemplateSpec{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{
|
|
"serving.kserve.io/inferencegraph": graphName,
|
|
constants.KServeWorkloadKind: "InferenceGraph",
|
|
},
|
|
Annotations: map[string]string{
|
|
"autoscaling.knative.dev/min-scale": "1",
|
|
"autoscaling.knative.dev/class": "kpa.autoscaling.knative.dev",
|
|
"serving.kserve.io/deploymentMode": "Knative",
|
|
},
|
|
},
|
|
Spec: knservingv1.RevisionSpec{
|
|
ContainerConcurrency: nil,
|
|
TimeoutSeconds: nil,
|
|
PodSpec: corev1.PodSpec{
|
|
Containers: []corev1.Container{
|
|
{
|
|
Image: "kserve/router:v0.10.0",
|
|
Env: []corev1.EnvVar{
|
|
{
|
|
Name: "PROPAGATE_HEADERS",
|
|
Value: "Authorization,Intuit_tid",
|
|
},
|
|
},
|
|
Args: []string{
|
|
"--graph-json",
|
|
"{\"nodes\":{\"root\":{\"routerType\":\"Sequence\",\"steps\":[{\"serviceUrl\":\"http://someservice.example.com\"}]}},\"resources\":{},\"tolerations\":[{\"key\":\"key1\",\"operator\":\"Equal\",\"value\":\"value1\",\"effect\":\"NoSchedule\"}]}",
|
|
},
|
|
Resources: corev1.ResourceRequirements{
|
|
Limits: corev1.ResourceList{
|
|
corev1.ResourceCPU: resource.MustParse("100m"),
|
|
corev1.ResourceMemory: resource.MustParse("500Mi"),
|
|
},
|
|
Requests: corev1.ResourceList{
|
|
corev1.ResourceCPU: resource.MustParse("100m"),
|
|
corev1.ResourceMemory: resource.MustParse("100Mi"),
|
|
},
|
|
},
|
|
ReadinessProbe: expectedReadinessProbe,
|
|
SecurityContext: &corev1.SecurityContext{
|
|
Privileged: proto.Bool(false),
|
|
RunAsNonRoot: proto.Bool(true),
|
|
ReadOnlyRootFilesystem: proto.Bool(true),
|
|
AllowPrivilegeEscalation: proto.Bool(false),
|
|
Capabilities: &corev1.Capabilities{
|
|
Drop: []corev1.Capability{corev1.Capability("ALL")},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Tolerations: []corev1.Toleration{
|
|
{
|
|
Key: "key1",
|
|
Operator: corev1.TolerationOpEqual,
|
|
Value: "value1",
|
|
Effect: corev1.TaintEffectNoSchedule,
|
|
},
|
|
},
|
|
AutomountServiceAccountToken: proto.Bool(false),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
// Set ResourceVersion which is required for update operation.
|
|
expectedKnService.ResourceVersion = actualKnServiceCreated.ResourceVersion
|
|
|
|
// Do a dry-run update. This will populate our local knative service object with any default values
|
|
// that are present on the remote version.
|
|
err := k8sClient.Update(context.TODO(), expectedKnService, client.DryRunAll)
|
|
Expect(err).ShouldNot(HaveOccurred())
|
|
Expect(actualKnServiceCreated.Spec).To(BeComparableTo(expectedKnService.Spec))
|
|
})
|
|
})
|
|
})
|