diff --git a/pkg/controller/v1beta1/inferenceservice/controller_test.go b/pkg/controller/v1beta1/inferenceservice/controller_test.go index 45867332a0b..1ed952abc5e 100644 --- a/pkg/controller/v1beta1/inferenceservice/controller_test.go +++ b/pkg/controller/v1beta1/inferenceservice/controller_test.go @@ -25,9 +25,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/kserve/kserve/pkg/apis/serving/v1alpha1" - "github.com/kserve/kserve/pkg/apis/serving/v1beta1" - "github.com/kserve/kserve/pkg/constants" . "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" . "github.com/onsi/gomega" @@ -46,6 +43,10 @@ import ( 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" ) var _ = Describe("v1beta1 inference service controller", func() { diff --git a/pkg/controller/v1beta1/inferenceservice/reconcilers/ingress/ingress_reconciler.go b/pkg/controller/v1beta1/inferenceservice/reconcilers/ingress/ingress_reconciler.go index a80aa14150e..c7774c32834 100644 --- a/pkg/controller/v1beta1/inferenceservice/reconcilers/ingress/ingress_reconciler.go +++ b/pkg/controller/v1beta1/inferenceservice/reconcilers/ingress/ingress_reconciler.go @@ -55,7 +55,20 @@ import ( var ( log = logf.Log.WithName("IngressReconciler") // probeTimeout defines the maximum amount of time a request will wait - probeTimeout = 1 * time.Second + probeTimeout = 1 * time.Second + Transport http.RoundTripper = &http.Transport{ + Proxy: http.ProxyFromEnvironment, + TLSClientConfig: &tls.Config{ + // #nosec G402 + // We only want to know that the Ingress is configured, not that the configuration is valid. + // Therefore, we can safely ignore any TLS certificate validation. + InsecureSkipVerify: true, + }, + TLSHandshakeTimeout: 2 * time.Second, + DisableKeepAlives: true, + IdleConnTimeout: 1 * time.Second, + ResponseHeaderTimeout: 1 * time.Second, + } ) type IngressReconciler struct { @@ -466,17 +479,9 @@ func probeIngress(url string) (bool, error) { isReady := false // Probes Queue-Proxy or Activator target := url + knnethttp.HealthCheckPath - transport := http.DefaultTransport.(*http.Transport).Clone() - transport.TLSClientConfig = &tls.Config{ - // #nosec G402 - // We only want to know that the Ingress is configured, not that the configuration is valid. - // Therefore, we can safely ignore any TLS certificate validation. - InsecureSkipVerify: true, - } - transport.DisableKeepAlives = true ctx, cancel := context.WithTimeout(context.TODO(), probeTimeout) defer cancel() - req, err := http.NewRequest(http.MethodGet, target, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, target, nil) if err != nil { return isReady, errors.Wrapf(err, "failed to probe ingress %s", target) } @@ -486,19 +491,17 @@ func probeIngress(url string) (bool, error) { req.Header.Add(knheader.HashKey, knheader.HashValueOverride) // IngressReadinessUserAgent is the user-agent header value set in probe requests for Ingress status. req.Header.Add(knheader.UserAgentKey, knheader.IngressReadinessUserAgent) - req = req.WithContext(ctx) - resp, err := transport.RoundTrip(req) + resp, err := Transport.RoundTrip(req) if err != nil { return isReady, errors.Wrapf(err, "failed to probe ingress %s", target) } - log.Info("ingress probe result", "statuscode", resp.StatusCode) if resp.StatusCode == http.StatusOK { isReady = true } return isReady, nil } -func (ir *IngressReconciler) isIngressReady(isvc *v1beta1.InferenceService) bool { +func isIngressReady(isvc *v1beta1.InferenceService) bool { return isvc.Generation == isvc.Status.ObservedGeneration && isvc.Status.GetCondition(v1beta1.IngressReady).IsTrue() } @@ -578,7 +581,7 @@ func (ir *IngressReconciler) Reconcile(isvc *v1beta1.InferenceService) (ctrl.Res Scheme: scheme, }, } - if ir.isIngressReady(isvc) { + if isIngressReady(isvc) { // When the ingress has already been marked Ready for this generation, // then it must have been successfully probed. This exception necessary for the case // of global resyncs. diff --git a/pkg/controller/v1beta1/inferenceservice/reconcilers/ingress/ingress_reconciler_test.go b/pkg/controller/v1beta1/inferenceservice/reconcilers/ingress/ingress_reconciler_test.go index a9a701ae28c..fc7a0b8815d 100644 --- a/pkg/controller/v1beta1/inferenceservice/reconcilers/ingress/ingress_reconciler_test.go +++ b/pkg/controller/v1beta1/inferenceservice/reconcilers/ingress/ingress_reconciler_test.go @@ -18,9 +18,12 @@ package ingress import ( "fmt" + "k8s.io/client-go/kubernetes/scheme" + "net/url" + ctrl "sigs.k8s.io/controller-runtime" + "testing" + "github.com/google/go-cmp/cmp" - "github.com/kserve/kserve/pkg/apis/serving/v1beta1" - "github.com/kserve/kserve/pkg/constants" "github.com/onsi/gomega" gomegaTypes "github.com/onsi/gomega/types" "google.golang.org/protobuf/testing/protocmp" @@ -31,8 +34,9 @@ import ( "knative.dev/pkg/apis" duckv1 "knative.dev/pkg/apis/duck/v1" "knative.dev/pkg/network" - "net/url" - "testing" + + "github.com/kserve/kserve/pkg/apis/serving/v1beta1" + "github.com/kserve/kserve/pkg/constants" ) func TestCreateVirtualService(t *testing.T) { @@ -1696,3 +1700,601 @@ func TestGetHostPrefix(t *testing.T) { }) } } + +func TestProbeIngress(t *testing.T) { + g := gomega.NewGomegaWithT(t) + + scenarios := map[string]struct { + url string + expected bool + expectedErr gomega.OmegaMatcher + }{ + "Probe successful": { + url: "http://ready-isvc.default.svc.cluster.local", + expected: true, + expectedErr: gomega.BeNil(), + }, + "Probe failed": { + url: "http://" + probeNotReadyHostPrefix + ".default.svc.cluster.local", + expected: false, + expectedErr: gomega.BeNil(), + }, + } + + for name, scenario := range scenarios { + t.Run(name, func(t *testing.T) { + res, err := probeIngress(scenario.url) + g.Expect(res).To(gomega.Equal(scenario.expected)) + g.Expect(err).To(scenario.expectedErr) + }) + } + +} + +func TestIsIngressReady(t *testing.T) { + g := gomega.NewGomegaWithT(t) + serviceName := "my-model" + namespace := "test" + isvcAnnotations := map[string]string{"test": "test", "kubectl.kubernetes.io/last-applied-configuration": "test"} + labels := map[string]string{"test": "test"} + predictorUrl, _ := url.Parse("http://my-model-predictor-default.example.com") + scenarios := map[string]struct { + isvc *v1beta1.InferenceService + expected bool + }{ + "isvc with ingress ready while generation is same": { + isvc: &v1beta1.InferenceService{ + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Annotations: isvcAnnotations, + Labels: labels, + Generation: int64(1), + }, + Spec: v1beta1.InferenceServiceSpec{ + Predictor: v1beta1.PredictorSpec{ + SKLearn: &v1beta1.SKLearnSpec{}, + }, + }, + Status: v1beta1.InferenceServiceStatus{ + Status: duckv1.Status{ + ObservedGeneration: int64(1), + Conditions: duckv1.Conditions{ + { + Type: v1beta1.PredictorReady, + Status: corev1.ConditionTrue, + }, + { + Type: v1beta1.IngressReady, + Status: corev1.ConditionTrue, + }, + }, + }, + Address: nil, + URL: nil, + Components: map[v1beta1.ComponentType]v1beta1.ComponentStatusSpec{ + v1beta1.PredictorComponent: v1beta1.ComponentStatusSpec{ + URL: (*apis.URL)(predictorUrl), + }, + }, + }, + }, + expected: true, + }, + "isvc with ingress not ready while generation is same": { + isvc: &v1beta1.InferenceService{ + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Annotations: isvcAnnotations, + Labels: labels, + Generation: int64(1), + }, + Spec: v1beta1.InferenceServiceSpec{ + Predictor: v1beta1.PredictorSpec{ + SKLearn: &v1beta1.SKLearnSpec{}, + }, + }, + Status: v1beta1.InferenceServiceStatus{ + Status: duckv1.Status{ + ObservedGeneration: int64(1), + Conditions: duckv1.Conditions{ + { + Type: v1beta1.PredictorReady, + Status: corev1.ConditionTrue, + }, + { + Type: v1beta1.IngressReady, + Status: corev1.ConditionFalse, + }, + }, + }, + Address: nil, + URL: nil, + Components: map[v1beta1.ComponentType]v1beta1.ComponentStatusSpec{ + v1beta1.PredictorComponent: v1beta1.ComponentStatusSpec{ + URL: (*apis.URL)(predictorUrl), + }, + }, + }, + }, + expected: false, + }, + "isvc with different observed generation": { + isvc: &v1beta1.InferenceService{ + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Annotations: isvcAnnotations, + Labels: labels, + Generation: int64(2), + }, + Spec: v1beta1.InferenceServiceSpec{ + Predictor: v1beta1.PredictorSpec{ + SKLearn: &v1beta1.SKLearnSpec{}, + }, + }, + Status: v1beta1.InferenceServiceStatus{ + Status: duckv1.Status{ + ObservedGeneration: int64(1), + Conditions: duckv1.Conditions{ + { + Type: v1beta1.PredictorReady, + Status: corev1.ConditionTrue, + }, + { + Type: v1beta1.IngressReady, + Status: corev1.ConditionTrue, + }, + }, + }, + Address: nil, + URL: nil, + Components: map[v1beta1.ComponentType]v1beta1.ComponentStatusSpec{ + v1beta1.PredictorComponent: v1beta1.ComponentStatusSpec{ + URL: (*apis.URL)(predictorUrl), + }, + }, + }, + }, + expected: false, + }, + "isvc with ingress condition not found": { + isvc: &v1beta1.InferenceService{ + ObjectMeta: metav1.ObjectMeta{ + Name: serviceName, + Namespace: namespace, + Annotations: isvcAnnotations, + Labels: labels, + Generation: int64(1), + }, + Spec: v1beta1.InferenceServiceSpec{ + Predictor: v1beta1.PredictorSpec{ + SKLearn: &v1beta1.SKLearnSpec{}, + }, + }, + Status: v1beta1.InferenceServiceStatus{ + Status: duckv1.Status{ + ObservedGeneration: int64(1), + Conditions: duckv1.Conditions{ + { + Type: v1beta1.PredictorReady, + Status: corev1.ConditionTrue, + }, + }, + }, + Address: nil, + URL: nil, + Components: map[v1beta1.ComponentType]v1beta1.ComponentStatusSpec{ + v1beta1.PredictorComponent: v1beta1.ComponentStatusSpec{ + URL: (*apis.URL)(predictorUrl), + }, + }, + }, + }, + expected: false, + }, + } + + for name, scenario := range scenarios { + t.Run(name, func(t *testing.T) { + res := isIngressReady(scenario.isvc) + g.Expect(scenario.expected).To(gomega.Equal(res)) + }) + } +} + +func TestIngressReconcile(t *testing.T) { + g := gomega.NewGomegaWithT(t) + ingressConfig := &v1beta1.IngressConfig{ + IngressGateway: constants.KnativeIngressGateway, + IngressServiceName: "someIngressServiceName", + LocalGateway: constants.KnativeLocalGateway, + LocalGatewayServiceName: "knative-local-gateway.istio-system.svc.cluster.local", + DisableIstioVirtualHost: false, + } + readyServiceName := "ready-isvc" + notReadyServiceName := probeNotReadyHostPrefix + namespace := "test" + isvcAnnotations := map[string]string{"test": "test", "kubectl.kubernetes.io/last-applied-configuration": "test"} + labels := map[string]string{"test": "test"} + readyPredictorUrl, err := url.Parse("http://ready-isvc-predictor.example.com") + if err != nil { + t.Fatalf("Unable to parse url") + } + notReadyPredictorUrl, err := url.Parse("http://" + probeNotReadyHostPrefix + "-predictor.example.com") + if err != nil { + t.Fatalf("Unable to parse url") + } + ir := NewIngressReconciler(fakeClient, scheme.Scheme, ingressConfig) + + scenarios := map[string]struct { + isvc *v1beta1.InferenceService + expectedRes ctrl.Result + expectedReadyCondition bool + expectedErrMatcher gomega.OmegaMatcher + }{ + "Ready Isvc status with ingress condition marked true and same generation Should not be probed again": { + isvc: &v1beta1.InferenceService{ + ObjectMeta: metav1.ObjectMeta{ + Name: readyServiceName, + Namespace: namespace, + Annotations: isvcAnnotations, + Labels: labels, + Generation: int64(1), + }, + Spec: v1beta1.InferenceServiceSpec{ + Predictor: v1beta1.PredictorSpec{ + SKLearn: &v1beta1.SKLearnSpec{}, + }, + }, + Status: v1beta1.InferenceServiceStatus{ + Status: duckv1.Status{ + ObservedGeneration: int64(1), + Conditions: duckv1.Conditions{ + { + Type: v1beta1.PredictorReady, + Status: corev1.ConditionTrue, + }, + { + Type: v1beta1.IngressReady, + Status: corev1.ConditionTrue, + }, + }, + }, + Address: nil, + URL: nil, + Components: map[v1beta1.ComponentType]v1beta1.ComponentStatusSpec{ + v1beta1.PredictorComponent: { + URL: (*apis.URL)(readyPredictorUrl), + }, + }, + }, + }, + expectedRes: ctrl.Result{}, + expectedReadyCondition: true, + expectedErrMatcher: gomega.BeNil(), + }, "Ready Isvc status with ingress condition marked false and same generation should be marked ready": { + isvc: &v1beta1.InferenceService{ + ObjectMeta: metav1.ObjectMeta{ + Name: readyServiceName, + Namespace: namespace, + Annotations: isvcAnnotations, + Labels: labels, + Generation: int64(1), + }, + Spec: v1beta1.InferenceServiceSpec{ + Predictor: v1beta1.PredictorSpec{ + SKLearn: &v1beta1.SKLearnSpec{}, + }, + }, + Status: v1beta1.InferenceServiceStatus{ + Status: duckv1.Status{ + ObservedGeneration: int64(1), + Conditions: duckv1.Conditions{ + { + Type: v1beta1.PredictorReady, + Status: corev1.ConditionTrue, + }, + { + Type: v1beta1.IngressReady, + Status: corev1.ConditionTrue, + }, + }, + }, + Address: nil, + URL: nil, + Components: map[v1beta1.ComponentType]v1beta1.ComponentStatusSpec{ + v1beta1.PredictorComponent: { + URL: (*apis.URL)(readyPredictorUrl), + }, + }, + }, + }, + expectedRes: ctrl.Result{}, + expectedReadyCondition: true, + expectedErrMatcher: gomega.BeNil(), + }, "Ready Isvc status with ingress condition missing and same generation should be marked ready": { + isvc: &v1beta1.InferenceService{ + ObjectMeta: metav1.ObjectMeta{ + Name: readyServiceName, + Namespace: namespace, + Annotations: isvcAnnotations, + Labels: labels, + Generation: int64(1), + }, + Spec: v1beta1.InferenceServiceSpec{ + Predictor: v1beta1.PredictorSpec{ + SKLearn: &v1beta1.SKLearnSpec{}, + }, + }, + Status: v1beta1.InferenceServiceStatus{ + Status: duckv1.Status{ + ObservedGeneration: int64(1), + Conditions: duckv1.Conditions{ + { + Type: v1beta1.PredictorReady, + Status: corev1.ConditionTrue, + }, + }, + }, + Address: nil, + URL: nil, + Components: map[v1beta1.ComponentType]v1beta1.ComponentStatusSpec{ + v1beta1.PredictorComponent: { + URL: (*apis.URL)(readyPredictorUrl), + }, + }, + }, + }, + expectedRes: ctrl.Result{}, + expectedReadyCondition: true, + expectedErrMatcher: gomega.BeNil(), + }, "Not Ready Isvc status with ingress condition marked false and same generation should be marked false and requeued": { + isvc: &v1beta1.InferenceService{ + ObjectMeta: metav1.ObjectMeta{ + Name: notReadyServiceName, + Namespace: namespace, + Annotations: isvcAnnotations, + Labels: labels, + Generation: int64(1), + }, + Spec: v1beta1.InferenceServiceSpec{ + Predictor: v1beta1.PredictorSpec{ + SKLearn: &v1beta1.SKLearnSpec{}, + }, + }, + Status: v1beta1.InferenceServiceStatus{ + Status: duckv1.Status{ + ObservedGeneration: int64(1), + Conditions: duckv1.Conditions{ + { + Type: v1beta1.PredictorReady, + Status: corev1.ConditionTrue, + }, + { + Type: v1beta1.IngressReady, + Status: corev1.ConditionFalse, + }, + }, + }, + Address: nil, + URL: nil, + Components: map[v1beta1.ComponentType]v1beta1.ComponentStatusSpec{ + v1beta1.PredictorComponent: { + URL: (*apis.URL)(notReadyPredictorUrl), + }, + }, + }, + }, + expectedRes: ctrl.Result{Requeue: true}, + expectedReadyCondition: false, + expectedErrMatcher: gomega.BeNil(), + }, "Not Ready Isvc status with ingress condition misssing and same generation should be marked false and requeued": { + isvc: &v1beta1.InferenceService{ + ObjectMeta: metav1.ObjectMeta{ + Name: notReadyServiceName, + Namespace: namespace, + Annotations: isvcAnnotations, + Labels: labels, + Generation: int64(1), + }, + Spec: v1beta1.InferenceServiceSpec{ + Predictor: v1beta1.PredictorSpec{ + SKLearn: &v1beta1.SKLearnSpec{}, + }, + }, + Status: v1beta1.InferenceServiceStatus{ + Status: duckv1.Status{ + ObservedGeneration: int64(1), + Conditions: duckv1.Conditions{ + { + Type: v1beta1.PredictorReady, + Status: corev1.ConditionTrue, + }, + }, + }, + Address: nil, + URL: nil, + Components: map[v1beta1.ComponentType]v1beta1.ComponentStatusSpec{ + v1beta1.PredictorComponent: { + URL: (*apis.URL)(notReadyPredictorUrl), + }, + }, + }, + }, + expectedRes: ctrl.Result{Requeue: true}, + expectedReadyCondition: false, + expectedErrMatcher: gomega.BeNil(), + }, "Ready Isvc status with ingress condition marked true and different generation Should be Probed": { + isvc: &v1beta1.InferenceService{ + ObjectMeta: metav1.ObjectMeta{ + Name: readyServiceName, + Namespace: namespace, + Annotations: isvcAnnotations, + Labels: labels, + Generation: int64(2), + }, + Spec: v1beta1.InferenceServiceSpec{ + Predictor: v1beta1.PredictorSpec{ + SKLearn: &v1beta1.SKLearnSpec{}, + }, + }, + Status: v1beta1.InferenceServiceStatus{ + Status: duckv1.Status{ + ObservedGeneration: int64(1), + Conditions: duckv1.Conditions{ + { + Type: v1beta1.PredictorReady, + Status: corev1.ConditionTrue, + }, + { + Type: v1beta1.IngressReady, + Status: corev1.ConditionTrue, + }, + }, + }, + Address: nil, + URL: nil, + Components: map[v1beta1.ComponentType]v1beta1.ComponentStatusSpec{ + v1beta1.PredictorComponent: { + URL: (*apis.URL)(readyPredictorUrl), + }, + }, + }, + }, + expectedRes: ctrl.Result{}, + expectedReadyCondition: true, + expectedErrMatcher: gomega.BeNil(), + }, "Ready Isvc status with ingress condition marked false and different generation Should be Probed and marked true": { + isvc: &v1beta1.InferenceService{ + ObjectMeta: metav1.ObjectMeta{ + Name: readyServiceName, + Namespace: namespace, + Annotations: isvcAnnotations, + Labels: labels, + Generation: int64(2), + }, + Spec: v1beta1.InferenceServiceSpec{ + Predictor: v1beta1.PredictorSpec{ + SKLearn: &v1beta1.SKLearnSpec{}, + }, + }, + Status: v1beta1.InferenceServiceStatus{ + Status: duckv1.Status{ + ObservedGeneration: int64(1), + Conditions: duckv1.Conditions{ + { + Type: v1beta1.PredictorReady, + Status: corev1.ConditionTrue, + }, + { + Type: v1beta1.IngressReady, + Status: corev1.ConditionFalse, + }, + }, + }, + Address: nil, + URL: nil, + Components: map[v1beta1.ComponentType]v1beta1.ComponentStatusSpec{ + v1beta1.PredictorComponent: { + URL: (*apis.URL)(readyPredictorUrl), + }, + }, + }, + }, + expectedRes: ctrl.Result{}, + expectedReadyCondition: true, + expectedErrMatcher: gomega.BeNil(), + }, "Not Ready Isvc status with ingress condition marked false and different generation should be marked false and requeued": { + isvc: &v1beta1.InferenceService{ + ObjectMeta: metav1.ObjectMeta{ + Name: notReadyServiceName, + Namespace: namespace, + Annotations: isvcAnnotations, + Labels: labels, + Generation: int64(2), + }, + Spec: v1beta1.InferenceServiceSpec{ + Predictor: v1beta1.PredictorSpec{ + SKLearn: &v1beta1.SKLearnSpec{}, + }, + }, + Status: v1beta1.InferenceServiceStatus{ + Status: duckv1.Status{ + ObservedGeneration: int64(1), + Conditions: duckv1.Conditions{ + { + Type: v1beta1.PredictorReady, + Status: corev1.ConditionTrue, + }, + { + Type: v1beta1.IngressReady, + Status: corev1.ConditionFalse, + }, + }, + }, + Address: nil, + URL: nil, + Components: map[v1beta1.ComponentType]v1beta1.ComponentStatusSpec{ + v1beta1.PredictorComponent: { + URL: (*apis.URL)(notReadyPredictorUrl), + }, + }, + }, + }, + expectedRes: ctrl.Result{Requeue: true}, + expectedReadyCondition: false, + expectedErrMatcher: gomega.BeNil(), + }, "Not Ready Isvc status with ingress condition marked true and different generation should be marked false and requeued": { + isvc: &v1beta1.InferenceService{ + ObjectMeta: metav1.ObjectMeta{ + Name: notReadyServiceName, + Namespace: namespace, + Annotations: isvcAnnotations, + Labels: labels, + Generation: int64(2), + }, + Spec: v1beta1.InferenceServiceSpec{ + Predictor: v1beta1.PredictorSpec{ + SKLearn: &v1beta1.SKLearnSpec{}, + }, + }, + Status: v1beta1.InferenceServiceStatus{ + Status: duckv1.Status{ + ObservedGeneration: int64(1), + Conditions: duckv1.Conditions{ + { + Type: v1beta1.PredictorReady, + Status: corev1.ConditionTrue, + }, + { + Type: v1beta1.IngressReady, + Status: corev1.ConditionTrue, + }, + }, + }, + Address: nil, + URL: nil, + Components: map[v1beta1.ComponentType]v1beta1.ComponentStatusSpec{ + v1beta1.PredictorComponent: { + URL: (*apis.URL)(notReadyPredictorUrl), + }, + }, + }, + }, + expectedRes: ctrl.Result{Requeue: true}, + expectedReadyCondition: false, + expectedErrMatcher: gomega.BeNil(), + }, + } + + for name, scenario := range scenarios { + t.Run(name, func(t *testing.T) { + res, err := ir.Reconcile(scenario.isvc) + g.Expect(res).To(gomega.Equal(scenario.expectedRes)) + g.Expect(err).To(scenario.expectedErrMatcher) + g.Expect(scenario.isvc.Status.GetCondition(v1beta1.IngressReady).IsTrue()).To(gomega.Equal(scenario.expectedReadyCondition)) + }) + } +} diff --git a/pkg/controller/v1beta1/inferenceservice/reconcilers/ingress/suite_test.go b/pkg/controller/v1beta1/inferenceservice/reconcilers/ingress/suite_test.go new file mode 100644 index 00000000000..47370be875c --- /dev/null +++ b/pkg/controller/v1beta1/inferenceservice/reconcilers/ingress/suite_test.go @@ -0,0 +1,50 @@ +package ingress + +import ( + "net/http" + "os" + "strings" + "testing" + + istioclientv1beta1 "istio.io/client-go/pkg/apis/networking/v1beta1" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/kserve/kserve/pkg/apis/serving/v1beta1" +) + +// mockTransport is a mock HTTP transport used for ingress probing. +type mockTransport struct{} + +var ( + probeNotReadyHostPrefix = "not-ready-isvc" + fakeClient client.Client +) + +func (t *mockTransport) RoundTrip(req *http.Request) (*http.Response, error) { + if strings.Contains(req.Host, probeNotReadyHostPrefix) { + return &http.Response{ + StatusCode: http.StatusNotFound, + }, nil + } else { + return &http.Response{ + StatusCode: http.StatusOK, + }, nil + } +} + +func TestMain(m *testing.M) { + err := v1beta1.AddToScheme(scheme.Scheme) + if err != nil { + panic(err) + } + if err := istioclientv1beta1.SchemeBuilder.AddToScheme(scheme.Scheme); err != nil { + log.Error(err, "Failed to add istio scheme") + } + fakeClient = fake.NewClientBuilder().WithScheme(scheme.Scheme).Build() + // Mock transport for ingress probing + Transport = &mockTransport{} + + os.Exit(m.Run()) +} diff --git a/pkg/controller/v1beta1/inferenceservice/suite_test.go b/pkg/controller/v1beta1/inferenceservice/suite_test.go index 96014c82ad1..f0c3bb5e5cb 100644 --- a/pkg/controller/v1beta1/inferenceservice/suite_test.go +++ b/pkg/controller/v1beta1/inferenceservice/suite_test.go @@ -18,18 +18,20 @@ package inferenceservice import ( "context" + "net/http" "testing" netv1 "k8s.io/api/networking/v1" - kfservingv1alpha1 "github.com/kserve/kserve/pkg/apis/serving/v1alpha1" - "github.com/kserve/kserve/pkg/constants" - pkgtest "github.com/kserve/kserve/pkg/testing" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" - "github.com/kserve/kserve/pkg/apis/serving/v1beta1" + kfservingv1alpha1 "github.com/kserve/kserve/pkg/apis/serving/v1alpha1" + "github.com/kserve/kserve/pkg/constants" + "github.com/kserve/kserve/pkg/controller/v1beta1/inferenceservice/reconcilers/ingress" + pkgtest "github.com/kserve/kserve/pkg/testing" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "k8s.io/client-go/kubernetes/scheme" @@ -40,6 +42,8 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + + "github.com/kserve/kserve/pkg/apis/serving/v1beta1" ) // These tests use Ginkgo (BDD-style Go testing framework). Refer to @@ -59,6 +63,16 @@ func TestV1beta1APIs(t *testing.T) { RunSpecs(t, "v1beta1 Controller Suite") } +// mockTransport is a mock HTTP transport used for ingress probing. +type mockTransport struct{} + +func (t *mockTransport) RoundTrip(req *http.Request) (*http.Response, error) { + // Simulate a successful response with status OK (200). + return &http.Response{ + StatusCode: http.StatusOK, + }, nil +} + var _ = BeforeSuite(func() { logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) ctx, cancel = context.WithCancel(context.TODO()) @@ -121,6 +135,8 @@ var _ = BeforeSuite(func() { k8sClient = k8sManager.GetClient() Expect(k8sClient).ToNot(BeNil()) + // Mock transport for ingress probe + ingress.Transport = &mockTransport{} }) var _ = AfterSuite(func() {