diff --git a/pkg/controller/revision/queueing_test.go b/pkg/controller/revision/queueing_test.go
index 937e551cb13a..f4fa21c9dc0d 100644
--- a/pkg/controller/revision/queueing_test.go
+++ b/pkg/controller/revision/queueing_test.go
@@ -20,19 +20,191 @@ import (
"testing"
"time"
+ fakebuildclientset "github.com/knative/build/pkg/client/clientset/versioned/fake"
+ buildinformers "github.com/knative/build/pkg/client/informers/externalversions"
+ "github.com/knative/serving/pkg"
+ "github.com/knative/serving/pkg/apis/serving"
+ "github.com/knative/serving/pkg/apis/serving/v1alpha1"
+ "github.com/knative/serving/pkg/autoscaler"
+ fakeclientset "github.com/knative/serving/pkg/client/clientset/versioned/fake"
+ informers "github.com/knative/serving/pkg/client/informers/externalversions"
+ "github.com/knative/serving/pkg/configmap"
+ ctrl "github.com/knative/serving/pkg/controller"
+ "github.com/knative/serving/pkg/logging"
+ appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
+ fakevpaclientset "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/clientset/versioned/fake"
+ vpainformers "k8s.io/autoscaler/vertical-pod-autoscaler/pkg/client/informers/externalversions"
+ kubeinformers "k8s.io/client-go/informers"
+ fakekubeclientset "k8s.io/client-go/kubernetes/fake"
. "github.com/knative/serving/pkg/controller/testing"
+ . "github.com/knative/serving/pkg/logging/testing"
)
-/* TODO tests:
-- syncHandler returns error (in processNextWorkItem)
-- invalid key in workqueue (in processNextWorkItem)
-- object cannot be converted to key (in enqueueConfiguration)
-- invalid key given to syncHandler
-- resource doesn't exist in lister (from syncHandler)
-*/
+type nopResolver struct{}
+
+func (r *nopResolver) Resolve(_ *appsv1.Deployment) error {
+ return nil
+}
+
+const (
+ testAutoscalerImage = "autoscalerImage"
+ testFluentdImage = "fluentdImage"
+ testFluentdSidecarOutputConfig = `
+
+ @type elasticsearch
+
+`
+ testNamespace = "test"
+ testQueueImage = "queueImage"
+)
+
+func getTestRevision() *v1alpha1.Revision {
+ return &v1alpha1.Revision{
+ ObjectMeta: metav1.ObjectMeta{
+ SelfLink: "/apis/serving/v1alpha1/namespaces/test/revisions/test-rev",
+ Name: "test-rev",
+ Namespace: testNamespace,
+ Labels: map[string]string{
+ "testLabel1": "foo",
+ "testLabel2": "bar",
+ serving.RouteLabelKey: "test-route",
+ },
+ Annotations: map[string]string{
+ "testAnnotation": "test",
+ },
+ UID: "test-rev-uid",
+ },
+ Spec: v1alpha1.RevisionSpec{
+ // corev1.Container has a lot of setting. We try to pass many
+ // of them here to verify that we pass through the settings to
+ // derived objects.
+ Container: corev1.Container{
+ Image: "gcr.io/repo/image",
+ Command: []string{"echo"},
+ Args: []string{"hello", "world"},
+ WorkingDir: "/tmp",
+ Env: []corev1.EnvVar{{
+ Name: "EDITOR",
+ Value: "emacs",
+ }},
+ LivenessProbe: &corev1.Probe{
+ TimeoutSeconds: 42,
+ },
+ ReadinessProbe: &corev1.Probe{
+ Handler: corev1.Handler{
+ HTTPGet: &corev1.HTTPGetAction{
+ Path: "health",
+ },
+ },
+ TimeoutSeconds: 43,
+ },
+ TerminationMessagePath: "/dev/null",
+ },
+ ServingState: v1alpha1.RevisionServingStateActive,
+ ConcurrencyModel: v1alpha1.RevisionRequestConcurrencyModelMulti,
+ },
+ }
+}
+
+func getTestControllerConfig() *ControllerConfig {
+ return &ControllerConfig{
+ QueueSidecarImage: testQueueImage,
+ AutoscalerImage: testAutoscalerImage,
+ }
+}
+
+func newTestController(t *testing.T, servingObjects ...runtime.Object) (
+ kubeClient *fakekubeclientset.Clientset,
+ buildClient *fakebuildclientset.Clientset,
+ servingClient *fakeclientset.Clientset,
+ vpaClient *fakevpaclientset.Clientset,
+ controller *Controller,
+ kubeInformer kubeinformers.SharedInformerFactory,
+ buildInformer buildinformers.SharedInformerFactory,
+ servingInformer informers.SharedInformerFactory,
+ configMapWatcher configmap.Watcher,
+ vpaInformer vpainformers.SharedInformerFactory) {
+
+ controllerConfig := getTestControllerConfig()
+
+ // Create fake clients
+ kubeClient = fakekubeclientset.NewSimpleClientset()
+ buildClient = fakebuildclientset.NewSimpleClientset()
+ servingClient = fakeclientset.NewSimpleClientset(servingObjects...)
+ vpaClient = fakevpaclientset.NewSimpleClientset()
+
+ configMapWatcher = configmap.NewFixedWatcher(&corev1.ConfigMap{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: pkg.GetServingSystemNamespace(),
+ Name: ctrl.GetNetworkConfigMapName(),
+ },
+ }, &corev1.ConfigMap{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: pkg.GetServingSystemNamespace(),
+ Name: logging.ConfigName,
+ },
+ Data: map[string]string{
+ "zap-logger-config": "{\"level\": \"error\",\n\"outputPaths\": [\"stdout\"],\n\"errorOutputPaths\": [\"stderr\"],\n\"encoding\": \"json\"}",
+ "loglevel.queueproxy": "info",
+ },
+ }, &corev1.ConfigMap{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: pkg.GetServingSystemNamespace(),
+ Name: ctrl.GetObservabilityConfigMapName(),
+ },
+ Data: map[string]string{
+ "logging.enable-var-log-collection": "true",
+ "logging.fluentd-sidecar-image": testFluentdImage,
+ "logging.fluentd-sidecar-output-config": testFluentdSidecarOutputConfig,
+ },
+ }, &corev1.ConfigMap{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: pkg.GetServingSystemNamespace(),
+ Name: autoscaler.ConfigName,
+ },
+ Data: map[string]string{
+ "max-scale-up-rate": "1.0",
+ "single-concurrency-target": "1.0",
+ "multi-concurrency-target": "1.0",
+ "stable-window": "5m",
+ "panic-window": "10s",
+ "scale-to-zero-threshold": "10m",
+ "concurrency-quantum-of-time": "100ms",
+ },
+ })
+
+ // Create informer factories with fake clients. The second parameter sets the
+ // resync period to zero, disabling it.
+ kubeInformer = kubeinformers.NewSharedInformerFactory(kubeClient, 0)
+ buildInformer = buildinformers.NewSharedInformerFactory(buildClient, 0)
+ servingInformer = informers.NewSharedInformerFactory(servingClient, 0)
+ vpaInformer = vpainformers.NewSharedInformerFactory(vpaClient, 0)
+
+ controller = NewController(
+ ctrl.Options{
+ KubeClientSet: kubeClient,
+ ServingClientSet: servingClient,
+ ConfigMapWatcher: configMapWatcher,
+ Logger: TestLogger(t),
+ },
+ vpaClient,
+ servingInformer.Serving().V1alpha1().Revisions(),
+ buildInformer.Build().V1alpha1().Builds(),
+ kubeInformer.Apps().V1().Deployments(),
+ kubeInformer.Core().V1().Services(),
+ kubeInformer.Core().V1().Endpoints(),
+ vpaInformer.Poc().V1alpha1().VerticalPodAutoscalers(),
+ controllerConfig,
+ )
+
+ controller.resolver = &nopResolver{}
+
+ return
+}
func TestNewRevisionCallsSyncHandler(t *testing.T) {
rev := getTestRevision()
diff --git a/pkg/controller/revision/revision.go b/pkg/controller/revision/revision.go
index a1f3512caee9..3ba9411274b2 100644
--- a/pkg/controller/revision/revision.go
+++ b/pkg/controller/revision/revision.go
@@ -25,10 +25,6 @@ import (
"sync"
"time"
- // TODO(mattmoor): Used by the commented checkAndUpdateDeployment logic below.
- // "github.com/google/go-cmp/cmp/cmpopts"
- // "k8s.io/apimachinery/pkg/api/resource"
-
"github.com/knative/serving/pkg"
"github.com/google/go-cmp/cmp"
@@ -460,8 +456,8 @@ func (c *Controller) createDeployment(ctx context.Context, rev *v1alpha1.Revisio
if rev.Spec.ServingState == v1alpha1.RevisionServingStateReserve {
replicaCount = 0
}
- deployment := MakeServingDeployment(rev, c.getLoggingConfig(), c.getNetworkConfig(), c.getObservabilityConfig(),
- c.getAutoscalerConfig(), c.controllerConfig, replicaCount)
+ deployment := MakeServingDeployment(rev, c.getLoggingConfig(), c.getNetworkConfig(),
+ c.getObservabilityConfig(), c.getAutoscalerConfig(), c.controllerConfig, replicaCount)
// Resolve tag image references to digests.
if err := c.resolver.Resolve(deployment); err != nil {
@@ -921,8 +917,7 @@ func (c *Controller) deleteVPA(ctx context.Context, vpa *vpav1alpha1.VerticalPod
}
func (c *Controller) updateStatus(rev *v1alpha1.Revision) (*v1alpha1.Revision, error) {
- prClient := c.ServingClientSet.ServingV1alpha1().Revisions(rev.Namespace)
- newRev, err := prClient.Get(rev.Name, metav1.GetOptions{})
+ newRev, err := c.revisionLister.Revisions(rev.Namespace).Get(rev.Name)
if err != nil {
return nil, err
}
@@ -931,7 +926,7 @@ func (c *Controller) updateStatus(rev *v1alpha1.Revision) (*v1alpha1.Revision, e
newRev.Status = rev.Status
// TODO: for CRD there's no updatestatus, so use normal update
- return prClient.Update(newRev)
+ return c.ServingClientSet.ServingV1alpha1().Revisions(rev.Namespace).Update(newRev)
// return prClient.UpdateStatus(newRev)
}
return rev, nil
diff --git a/pkg/controller/revision/revision_test.go b/pkg/controller/revision/revision_test.go
index 20060388abc2..3f7e8b4548b0 100644
--- a/pkg/controller/revision/revision_test.go
+++ b/pkg/controller/revision/revision_test.go
@@ -63,65 +63,6 @@ import (
. "github.com/knative/serving/pkg/controller/testing"
)
-const (
- testAutoscalerImage = "autoscalerImage"
- testFluentdImage = "fluentdImage"
- testFluentdSidecarOutputConfig = `
-
- @type elasticsearch
-
-`
- testNamespace = "test"
- testQueueImage = "queueImage"
-)
-
-func getTestRevision() *v1alpha1.Revision {
- return &v1alpha1.Revision{
- ObjectMeta: metav1.ObjectMeta{
- SelfLink: "/apis/serving/v1alpha1/namespaces/test/revisions/test-rev",
- Name: "test-rev",
- Namespace: testNamespace,
- Labels: map[string]string{
- "testLabel1": "foo",
- "testLabel2": "bar",
- },
- Annotations: map[string]string{
- "testAnnotation": "test",
- },
- UID: "test-rev-uid",
- },
- Spec: v1alpha1.RevisionSpec{
- // corev1.Container has a lot of setting. We try to pass many
- // of them here to verify that we pass through the settings to
- // derived objects.
- Container: corev1.Container{
- Image: "gcr.io/repo/image",
- Command: []string{"echo"},
- Args: []string{"hello", "world"},
- WorkingDir: "/tmp",
- Env: []corev1.EnvVar{{
- Name: "EDITOR",
- Value: "emacs",
- }},
- LivenessProbe: &corev1.Probe{
- TimeoutSeconds: 42,
- },
- ReadinessProbe: &corev1.Probe{
- Handler: corev1.Handler{
- HTTPGet: &corev1.HTTPGetAction{
- Path: "health",
- },
- },
- TimeoutSeconds: 43,
- },
- TerminationMessagePath: "/dev/null",
- },
- ServingState: v1alpha1.RevisionServingStateActive,
- ConcurrencyModel: v1alpha1.RevisionRequestConcurrencyModelMulti,
- },
- }
-}
-
func getTestConfiguration() *v1alpha1.Configuration {
return &v1alpha1.Configuration{
ObjectMeta: metav1.ObjectMeta{
@@ -192,19 +133,6 @@ func sumMaps(a map[string]string, b map[string]string) map[string]string {
return summedMap
}
-func getTestControllerConfig() ControllerConfig {
- return ControllerConfig{
- QueueSidecarImage: testQueueImage,
- AutoscalerImage: testAutoscalerImage,
- }
-}
-
-type nopResolver struct{}
-
-func (r *nopResolver) Resolve(_ *appsv1.Deployment) error {
- return nil
-}
-
func newTestControllerWithConfig(t *testing.T, controllerConfig *ControllerConfig, configs ...*corev1.ConfigMap) (
kubeClient *fakekubeclientset.Clientset,
buildClient *fakebuildclientset.Clientset,
@@ -298,93 +226,6 @@ func newTestControllerWithConfig(t *testing.T, controllerConfig *ControllerConfi
return
}
-func newTestController(t *testing.T, servingObjects ...runtime.Object) (
- kubeClient *fakekubeclientset.Clientset,
- buildClient *fakebuildclientset.Clientset,
- servingClient *fakeclientset.Clientset,
- vpaClient *fakevpaclientset.Clientset,
- controller *Controller,
- kubeInformer kubeinformers.SharedInformerFactory,
- buildInformer buildinformers.SharedInformerFactory,
- servingInformer informers.SharedInformerFactory,
- configMapWatcher configmap.Watcher,
- vpaInformer vpainformers.SharedInformerFactory) {
- testControllerConfig := getTestControllerConfig()
-
- // Create fake clients
- kubeClient = fakekubeclientset.NewSimpleClientset()
- buildClient = fakebuildclientset.NewSimpleClientset()
- servingClient = fakeclientset.NewSimpleClientset(servingObjects...)
- vpaClient = fakevpaclientset.NewSimpleClientset()
-
- configMapWatcher = configmap.NewFixedWatcher(&corev1.ConfigMap{
- ObjectMeta: metav1.ObjectMeta{
- Namespace: pkg.GetServingSystemNamespace(),
- Name: ctrl.GetNetworkConfigMapName(),
- },
- }, &corev1.ConfigMap{
- ObjectMeta: metav1.ObjectMeta{
- Namespace: pkg.GetServingSystemNamespace(),
- Name: logging.ConfigName,
- },
- Data: map[string]string{
- "zap-logger-config": "{\"level\": \"error\",\n\"outputPaths\": [\"stdout\"],\n\"errorOutputPaths\": [\"stderr\"],\n\"encoding\": \"json\"}",
- "loglevel.queueproxy": "info",
- },
- }, &corev1.ConfigMap{
- ObjectMeta: metav1.ObjectMeta{
- Namespace: pkg.GetServingSystemNamespace(),
- Name: ctrl.GetObservabilityConfigMapName(),
- },
- Data: map[string]string{
- "logging.enable-var-log-collection": "true",
- "logging.fluentd-sidecar-image": testFluentdImage,
- "logging.fluentd-sidecar-output-config": testFluentdSidecarOutputConfig,
- },
- }, &corev1.ConfigMap{
- ObjectMeta: metav1.ObjectMeta{
- Namespace: pkg.GetServingSystemNamespace(),
- Name: autoscaler.ConfigName,
- },
- Data: map[string]string{
- "max-scale-up-rate": "1.0",
- "single-concurrency-target": "1.0",
- "multi-concurrency-target": "1.0",
- "stable-window": "5m",
- "panic-window": "10s",
- "scale-to-zero-threshold": "10m",
- "concurrency-quantum-of-time": "100ms",
- },
- })
-
- // Create informer factories with fake clients. The second parameter sets the
- // resync period to zero, disabling it.
- kubeInformer = kubeinformers.NewSharedInformerFactory(kubeClient, 0)
- buildInformer = buildinformers.NewSharedInformerFactory(buildClient, 0)
- servingInformer = informers.NewSharedInformerFactory(servingClient, 0)
- vpaInformer = vpainformers.NewSharedInformerFactory(vpaClient, 0)
-
- controller = NewController(
- ctrl.Options{
- KubeClientSet: kubeClient,
- ServingClientSet: servingClient,
- ConfigMapWatcher: configMapWatcher,
- Logger: TestLogger(t),
- },
- vpaClient,
- servingInformer.Serving().V1alpha1().Revisions(),
- buildInformer.Build().V1alpha1().Builds(),
- kubeInformer.Apps().V1().Deployments(),
- kubeInformer.Core().V1().Services(),
- kubeInformer.Core().V1().Endpoints(),
- vpaInformer.Poc().V1alpha1().VerticalPodAutoscalers(),
- &testControllerConfig,
- )
-
- controller.resolver = &nopResolver{}
- return
-}
-
func createRevision(t *testing.T,
kubeClient *fakekubeclientset.Clientset, kubeInformer kubeinformers.SharedInformerFactory,
servingClient *fakeclientset.Clientset, servingInformer informers.SharedInformerFactory,
@@ -489,7 +330,7 @@ func (r *fixedResolver) Resolve(deploy *appsv1.Deployment) error {
func TestCreateRevCreatesStuff(t *testing.T) {
controllerConfig := getTestControllerConfig()
- kubeClient, _, servingClient, _, controller, kubeInformer, _, servingInformer, _, _ := newTestControllerWithConfig(t, &controllerConfig)
+ kubeClient, _, servingClient, _, controller, kubeInformer, _, servingInformer, _, _ := newTestControllerWithConfig(t, controllerConfig)
// Resolve image references to this "digest"
digest := "foo@sha256:deadbeef"
@@ -839,7 +680,7 @@ func TestResolutionFailed(t *testing.T) {
func TestCreateRevDoesNotSetUpFluentdSidecarIfVarLogCollectionDisabled(t *testing.T) {
controllerConfig := getTestControllerConfig()
- kubeClient, _, servingClient, _, controller, kubeInformer, _, servingInformer, _, _ := newTestControllerWithConfig(t, &controllerConfig, &corev1.ConfigMap{
+ kubeClient, _, servingClient, _, controller, kubeInformer, _, servingInformer, _, _ := newTestControllerWithConfig(t, controllerConfig, &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: pkg.GetServingSystemNamespace(),
Name: ctrl.GetObservabilityConfigMapName(),
@@ -953,7 +794,7 @@ func TestCreateRevUpdateConfigMap_NewRevOwnerReference(t *testing.T) {
func TestCreateRevWithWithLoggingURL(t *testing.T) {
controllerConfig := getTestControllerConfig()
- kubeClient, _, servingClient, _, controller, kubeInformer, _, servingInformer, _, _ := newTestControllerWithConfig(t, &controllerConfig, &corev1.ConfigMap{
+ kubeClient, _, servingClient, _, controller, kubeInformer, _, servingInformer, _, _ := newTestControllerWithConfig(t, controllerConfig, &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: pkg.GetServingSystemNamespace(),
Name: ctrl.GetObservabilityConfigMapName(),
@@ -983,7 +824,7 @@ func TestCreateRevWithWithLoggingURL(t *testing.T) {
func TestCreateRevWithVPA(t *testing.T) {
controllerConfig := getTestControllerConfig()
- kubeClient, _, servingClient, vpaClient, controller, kubeInformer, _, servingInformer, _, _ := newTestControllerWithConfig(t, &controllerConfig, &corev1.ConfigMap{
+ kubeClient, _, servingClient, vpaClient, controller, kubeInformer, _, servingInformer, _, _ := newTestControllerWithConfig(t, controllerConfig, &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: pkg.GetServingSystemNamespace(),
Name: autoscaler.ConfigName,
@@ -1025,7 +866,7 @@ func TestCreateRevWithVPA(t *testing.T) {
func TestUpdateRevWithWithUpdatedLoggingURL(t *testing.T) {
controllerConfig := getTestControllerConfig()
- kubeClient, _, servingClient, _, controller, kubeInformer, _, servingInformer, _, _ := newTestControllerWithConfig(t, &controllerConfig, &corev1.ConfigMap{
+ kubeClient, _, servingClient, _, controller, kubeInformer, _, servingInformer, _, _ := newTestControllerWithConfig(t, controllerConfig, &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: pkg.GetServingSystemNamespace(),
Name: ctrl.GetObservabilityConfigMapName(),
@@ -1854,7 +1695,7 @@ func TestReserveToActiveRevisionCreatesStuff(t *testing.T) {
func TestNoAutoscalerImageCreatesNoAutoscalers(t *testing.T) {
controllerConfig := getTestControllerConfig()
controllerConfig.AutoscalerImage = ""
- kubeClient, _, servingClient, _, controller, kubeInformer, _, servingInformer, _, _ := newTestControllerWithConfig(t, &controllerConfig)
+ kubeClient, _, servingClient, _, controller, kubeInformer, _, servingInformer, _, _ := newTestControllerWithConfig(t, controllerConfig)
rev := getTestRevision()
config := getTestConfiguration()
@@ -2006,7 +1847,7 @@ func TestReconcileReplicaCount(t *testing.T) {
func getPodAnnotationsForConfig(t *testing.T, configMapValue string, configAnnotationOverride string) map[string]string {
controllerConfig := getTestControllerConfig()
- kubeClient, _, servingClient, _, controller, kubeInformer, _, servingInformer, _, _ := newTestControllerWithConfig(t, &controllerConfig)
+ kubeClient, _, servingClient, _, controller, kubeInformer, _, servingInformer, _, _ := newTestControllerWithConfig(t, controllerConfig)
// Resolve image references to this "digest"
digest := "foo@sha256:deadbeef"
diff --git a/pkg/controller/revision/table_test.go b/pkg/controller/revision/table_test.go
new file mode 100644
index 000000000000..34e5f9280b0a
--- /dev/null
+++ b/pkg/controller/revision/table_test.go
@@ -0,0 +1,395 @@
+/*
+Copyright 2018 The Knative 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 revision
+
+import (
+ "strings"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/google/go-cmp/cmp/cmpopts"
+ fakebuildclientset "github.com/knative/build/pkg/client/clientset/versioned/fake"
+ "github.com/knative/serving/pkg"
+ "github.com/knative/serving/pkg/apis/serving/v1alpha1"
+ "github.com/knative/serving/pkg/autoscaler"
+ fakeclientset "github.com/knative/serving/pkg/client/clientset/versioned/fake"
+ "github.com/knative/serving/pkg/controller"
+ "github.com/knative/serving/pkg/logging"
+ . "github.com/knative/serving/pkg/logging/testing"
+ appsv1 "k8s.io/api/apps/v1"
+ corev1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/api/resource"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ fakekubeclientset "k8s.io/client-go/kubernetes/fake"
+ clientgotesting "k8s.io/client-go/testing"
+ "k8s.io/client-go/tools/cache"
+
+ . "github.com/knative/serving/pkg/controller/testing"
+)
+
+// This is heavily based on the way the OpenShift Ingress controller tests its reconciliation method.
+func TestReconcileActive(t *testing.T) {
+ networkConfig := &NetworkConfig{IstioOutboundIPRanges: "*"}
+ loggingConfig := &logging.Config{}
+ observabilityConfig := &ObservabilityConfig{}
+ autoscalerConfig := &autoscaler.Config{}
+ controllerConfig := getTestControllerConfig()
+
+ // Create short-hand aliases that pass through the above config and Active to getRev and friends.
+ rev := func(namespace, name, image string) *v1alpha1.Revision {
+ return getRev(namespace, name, v1alpha1.RevisionServingStateActive, image,
+ loggingConfig, networkConfig, observabilityConfig,
+ autoscalerConfig, controllerConfig)
+ }
+ deploy := func(namespace, name, image string) *appsv1.Deployment {
+ return getDeploy(namespace, name, v1alpha1.RevisionServingStateActive, image,
+ loggingConfig, networkConfig, observabilityConfig,
+ autoscalerConfig, controllerConfig)
+ }
+ svc := func(namespace, name, image string) *corev1.Service {
+ return getService(namespace, name, v1alpha1.RevisionServingStateActive, image,
+ loggingConfig, networkConfig, observabilityConfig,
+ autoscalerConfig, controllerConfig)
+ }
+ deployAS := func(namespace, name, image string) *appsv1.Deployment {
+ return getASDeploy(namespace, name, v1alpha1.RevisionServingStateActive, image,
+ loggingConfig, networkConfig, observabilityConfig,
+ autoscalerConfig, controllerConfig)
+ }
+ svcAS := func(namespace, name, image string) *corev1.Service {
+ return getASService(namespace, name, v1alpha1.RevisionServingStateActive, image,
+ loggingConfig, networkConfig, observabilityConfig,
+ autoscalerConfig, controllerConfig)
+ }
+
+ type fields struct {
+ r *RevisionLister
+ b *BuildLister
+ d *DeploymentLister
+ s *K8sServiceLister
+ e *EndpointsLister
+ }
+ tests := []struct {
+ name string
+ fields fields
+ key string
+ wantErr bool
+ wantCreates []metav1.Object
+ wantUpdates []clientgotesting.UpdateActionImpl
+ wantDeletes []clientgotesting.DeleteActionImpl
+ wantQueue []string
+ }{{
+ name: "bad workqueue key",
+ key: "too/many/parts",
+ }, {
+ name: "key not found",
+ key: "foo/not-found",
+ }, {
+ name: "first revision reconciliation",
+ fields: fields{
+ r: &RevisionLister{
+ Items: []*v1alpha1.Revision{rev("foo", "first-reconcile", "busybox")},
+ },
+ },
+ wantCreates: []metav1.Object{
+ // The first reconciliation of a Revision creates the following resources.
+ deploy("foo", "first-reconcile", "busybox"),
+ svc("foo", "first-reconcile", "busybox"),
+ deployAS("foo", "first-reconcile", "busybox"),
+ svcAS("foo", "first-reconcile", "busybox"),
+ },
+ wantUpdates: []clientgotesting.UpdateActionImpl{{
+ Object: makeStatus(
+ rev("foo", "first-reconcile", "busybox"),
+ // After the first reconciliation of a Revision the status looks like this.
+ v1alpha1.RevisionStatus{
+ ServiceName: svc("foo", "first-reconcile", "busybox").Name,
+ Conditions: []v1alpha1.RevisionCondition{{
+ Type: "ResourcesAvailable",
+ Status: "Unknown",
+ Reason: "Deploying",
+ }, {
+ Type: "ContainerHealthy",
+ Status: "Unknown",
+ Reason: "Deploying",
+ }, {
+ Type: "Ready",
+ Status: "Unknown",
+ Reason: "Deploying",
+ }},
+ }),
+ }},
+ key: "foo/first-reconcile",
+ }}
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ // Default the FooLister fields to reduce boilerplate
+ rl := tt.fields.r
+ if rl == nil {
+ rl = &RevisionLister{}
+ }
+ bl := tt.fields.b
+ if bl == nil {
+ bl = &BuildLister{}
+ }
+ dl := tt.fields.d
+ if dl == nil {
+ dl = &DeploymentLister{}
+ }
+ sl := tt.fields.s
+ if sl == nil {
+ sl = &K8sServiceLister{}
+ }
+ el := tt.fields.e
+ if el == nil {
+ el = &EndpointsLister{}
+ }
+ // Add all of the items in our lister fakes into our
+ // fake clients.
+ var objs []runtime.Object
+ var buildObjs []runtime.Object
+ var kubeObjs []runtime.Object
+ for _, r := range rl.Items {
+ objs = append(objs, r)
+ }
+ for _, r := range bl.Items {
+ buildObjs = append(buildObjs, r)
+ }
+ for _, r := range dl.Items {
+ kubeObjs = append(kubeObjs, r)
+ }
+ for _, r := range sl.Items {
+ kubeObjs = append(kubeObjs, r)
+ }
+ for _, r := range el.Items {
+ kubeObjs = append(kubeObjs, r)
+ }
+ kubeClient := fakekubeclientset.NewSimpleClientset(kubeObjs...)
+ client := fakeclientset.NewSimpleClientset(objs...)
+ buildClient := fakebuildclientset.NewSimpleClientset(buildObjs...)
+ // Set up our Controller from the fakes.
+ c := &Controller{
+ Base: controller.NewBase(controller.Options{
+ KubeClientSet: kubeClient,
+ BuildClientSet: buildClient,
+ ServingClientSet: client,
+ Logger: TestLogger(t),
+ }, controllerAgentName, "Revisions"),
+ revisionLister: rl,
+ buildLister: bl,
+ deploymentLister: dl,
+ serviceLister: sl,
+ endpointsLister: el,
+ controllerConfig: controllerConfig,
+ networkConfig: networkConfig,
+ loggingConfig: loggingConfig,
+ observabilityConfig: observabilityConfig,
+ autoscalerConfig: autoscalerConfig,
+ resolver: &nopResolver{},
+ }
+
+ // Run the Reconcile we're testing.
+ if err := c.Reconcile(tt.key); (err != nil) != tt.wantErr {
+ t.Errorf("Reconcile() error = %v, wantErr %v", err, tt.wantErr)
+ }
+ // Now check that the Reconcile had the desired effects.
+ expectedNamespace, _, _ := cache.SplitMetaNamespaceKey(tt.key)
+
+ c.WorkQueue.ShutDown()
+ var hasQueue []string
+ for {
+ key, shutdown := c.WorkQueue.Get()
+ if shutdown {
+ break
+ }
+ hasQueue = append(hasQueue, key.(string))
+ }
+ if diff := cmp.Diff(tt.wantQueue, hasQueue); diff != "" {
+ t.Errorf("unexpected queue (-want +got): %s", diff)
+ }
+
+ var createActions []clientgotesting.CreateAction
+ var updateActions []clientgotesting.UpdateAction
+ var deleteActions []clientgotesting.DeleteAction
+
+ type HasActions interface {
+ Actions() []clientgotesting.Action
+ }
+ for _, c := range []HasActions{buildClient, client, kubeClient} {
+ for _, action := range c.Actions() {
+ switch action.GetVerb() {
+ case "create":
+ createActions = append(createActions,
+ action.(clientgotesting.CreateAction))
+ case "update":
+ updateActions = append(updateActions,
+ action.(clientgotesting.UpdateAction))
+ case "delete":
+ deleteActions = append(deleteActions,
+ action.(clientgotesting.DeleteAction))
+ default:
+ t.Errorf("Unexpected verb %v: %+v", action.GetVerb(), action)
+ }
+ }
+ }
+
+ for i, want := range tt.wantCreates {
+ if i >= len(createActions) {
+ t.Errorf("Missing create: %v", want)
+ continue
+ }
+ got := createActions[i]
+ if got.GetNamespace() != expectedNamespace && got.GetNamespace() != pkg.GetServingSystemNamespace() {
+ t.Errorf("unexpected action[%d]: %#v", i, got)
+ }
+ obj := got.GetObject()
+ if diff := cmp.Diff(want, obj, ignoreLastTransitionTime, safeDeployDiff); diff != "" {
+ t.Errorf("unexpected create (-want +got): %s", diff)
+ }
+ }
+ if got, want := len(createActions), len(tt.wantCreates); got > want {
+ for _, extra := range createActions[want:] {
+ t.Errorf("Extra create: %v", extra)
+ }
+ }
+
+ for i, want := range tt.wantUpdates {
+ if i >= len(updateActions) {
+ t.Errorf("Missing update: %v", want)
+ continue
+ }
+ got := updateActions[i]
+ if diff := cmp.Diff(want.GetObject(), got.GetObject(), ignoreLastTransitionTime); diff != "" {
+ t.Errorf("unexpected update (-want +got): %s", diff)
+ }
+ }
+ if got, want := len(updateActions), len(tt.wantUpdates); got > want {
+ for _, extra := range updateActions[want:] {
+ t.Errorf("Extra update: %v", extra)
+ }
+ }
+
+ for i, want := range tt.wantDeletes {
+ if i >= len(deleteActions) {
+ t.Errorf("Missing delete: %v", want)
+ continue
+ }
+ got := deleteActions[i]
+ if got.GetName() != want.Name || got.GetNamespace() != expectedNamespace {
+ t.Errorf("unexpected delete[%d]: %#v", i, got)
+ }
+ }
+ if got, want := len(deleteActions), len(tt.wantDeletes); got > want {
+ for _, extra := range deleteActions[want:] {
+ t.Errorf("Extra delete: %v", extra)
+ }
+ }
+ })
+ }
+}
+
+var ignoreLastTransitionTime = cmp.FilterPath(func(p cmp.Path) bool {
+ return strings.HasSuffix(p.String(), "LastTransitionTime.Time")
+}, cmp.Ignore())
+
+var safeDeployDiff = cmpopts.IgnoreUnexported(resource.Quantity{})
+
+var (
+ boolTrue = true
+)
+
+func or(name string) []metav1.OwnerReference {
+ return []metav1.OwnerReference{{
+ APIVersion: v1alpha1.SchemeGroupVersion.String(),
+ Kind: "Revision",
+ Name: name,
+ Controller: &boolTrue,
+ BlockOwnerDeletion: &boolTrue,
+ }}
+}
+
+func om(namespace, name string) metav1.ObjectMeta {
+ return metav1.ObjectMeta{
+ Name: name,
+ Namespace: namespace,
+ }
+}
+
+func makeStatus(rev *v1alpha1.Revision, status v1alpha1.RevisionStatus) *v1alpha1.Revision {
+ rev.Status = status
+ return rev
+}
+
+// The input signatures of these functions should be kept in sync for readability.
+func getRev(namespace, name string, servingState v1alpha1.RevisionServingStateType, image string,
+ loggingConfig *logging.Config, networkConfig *NetworkConfig, observabilityConfig *ObservabilityConfig,
+ autoscalerConfig *autoscaler.Config, controllerConfig *ControllerConfig) *v1alpha1.Revision {
+ return &v1alpha1.Revision{
+ ObjectMeta: om(namespace, name),
+ Spec: v1alpha1.RevisionSpec{
+ Container: corev1.Container{Image: image},
+ ServingState: servingState,
+ },
+ }
+}
+
+func getDeploy(namespace, name string, servingState v1alpha1.RevisionServingStateType, image string,
+ loggingConfig *logging.Config, networkConfig *NetworkConfig, observabilityConfig *ObservabilityConfig,
+ autoscalerConfig *autoscaler.Config, controllerConfig *ControllerConfig) *appsv1.Deployment {
+
+ var replicaCount int32 = 1
+ if servingState == v1alpha1.RevisionServingStateReserve {
+ replicaCount = 0
+ }
+ rev := getRev(namespace, name, servingState, image, loggingConfig, networkConfig, observabilityConfig,
+ autoscalerConfig, controllerConfig)
+ return MakeServingDeployment(rev, loggingConfig, networkConfig, observabilityConfig,
+ autoscalerConfig, controllerConfig, replicaCount)
+}
+
+func getService(namespace, name string, servingState v1alpha1.RevisionServingStateType, image string,
+ loggingConfig *logging.Config, networkConfig *NetworkConfig, observabilityConfig *ObservabilityConfig,
+ autoscalerConfig *autoscaler.Config, controllerConfig *ControllerConfig) *corev1.Service {
+
+ rev := getRev(namespace, name, servingState, image, loggingConfig, networkConfig, observabilityConfig,
+ autoscalerConfig, controllerConfig)
+ return MakeRevisionK8sService(rev)
+}
+
+func getASDeploy(namespace, name string, servingState v1alpha1.RevisionServingStateType, image string,
+ loggingConfig *logging.Config, networkConfig *NetworkConfig, observabilityConfig *ObservabilityConfig,
+ autoscalerConfig *autoscaler.Config, controllerConfig *ControllerConfig) *appsv1.Deployment {
+
+ var replicaCount int32 = 1
+ if servingState == v1alpha1.RevisionServingStateReserve {
+ replicaCount = 0
+ }
+ rev := getRev(namespace, name, servingState, image, loggingConfig, networkConfig, observabilityConfig,
+ autoscalerConfig, controllerConfig)
+ return MakeServingAutoscalerDeployment(rev, controllerConfig.AutoscalerImage, replicaCount)
+}
+
+func getASService(namespace, name string, servingState v1alpha1.RevisionServingStateType, image string,
+ loggingConfig *logging.Config, networkConfig *NetworkConfig, observabilityConfig *ObservabilityConfig,
+ autoscalerConfig *autoscaler.Config, controllerConfig *ControllerConfig) *corev1.Service {
+
+ rev := getRev(namespace, name, servingState, image, loggingConfig, networkConfig, observabilityConfig,
+ autoscalerConfig, controllerConfig)
+ return MakeServingAutoscalerService(rev)
+}
diff --git a/pkg/controller/testing/listers.go b/pkg/controller/testing/listers.go
index 5b3c5a3fd4d3..d650811798bf 100644
--- a/pkg/controller/testing/listers.go
+++ b/pkg/controller/testing/listers.go
@@ -14,12 +14,20 @@ limitations under the License.
package testing
import (
+ "fmt"
+
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
+ buildv1alpha1 "github.com/knative/build/pkg/apis/build/v1alpha1"
+ buildlisters "github.com/knative/build/pkg/client/listers/build/v1alpha1"
"github.com/knative/serving/pkg/apis/serving/v1alpha1"
listers "github.com/knative/serving/pkg/client/listers/serving/v1alpha1"
+ appsv1 "k8s.io/api/apps/v1"
+ corev1 "k8s.io/api/core/v1"
+ appsv1listers "k8s.io/client-go/listers/apps/v1"
+ corev1listers "k8s.io/client-go/listers/core/v1"
)
// ServiceLister is a lister.ServiceLister fake for testing.
@@ -173,3 +181,163 @@ func (r *nsRevisionLister) Get(name string) (*v1alpha1.Revision, error) {
}
return nil, errors.NewNotFound(schema.GroupResource{}, name)
}
+
+// BuildLister is a lister.BuildLister fake for testing.
+type BuildLister struct {
+ Err error
+ Items []*buildv1alpha1.Build
+}
+
+// Assert that our fake implements the interface it is faking.
+var _ buildlisters.BuildLister = (*BuildLister)(nil)
+
+func (r *BuildLister) List(selector labels.Selector) ([]*buildv1alpha1.Build, error) {
+ return r.Items, r.Err
+}
+
+func (r *BuildLister) Builds(namespace string) buildlisters.BuildNamespaceLister {
+ return &nsBuildLister{r: r, ns: namespace}
+}
+
+type nsBuildLister struct {
+ r *BuildLister
+ ns string
+}
+
+// Assert that our fake implements the interface it is faking.
+var _ buildlisters.BuildNamespaceLister = (*nsBuildLister)(nil)
+
+func (r *nsBuildLister) List(selector labels.Selector) ([]*buildv1alpha1.Build, error) {
+ return r.r.Items, r.r.Err
+}
+
+func (r *nsBuildLister) Get(name string) (*buildv1alpha1.Build, error) {
+ for _, s := range r.r.Items {
+ if s.Name == name && r.ns == s.Namespace {
+ return s, nil
+ }
+ }
+ return nil, errors.NewNotFound(schema.GroupResource{}, name)
+}
+
+// DeploymentLister is a lister.DeploymentLister fake for testing.
+type DeploymentLister struct {
+ Err error
+ Items []*appsv1.Deployment
+}
+
+// Assert that our fake implements the interface it is faking.
+var _ appsv1listers.DeploymentLister = (*DeploymentLister)(nil)
+
+func (r *DeploymentLister) List(selector labels.Selector) ([]*appsv1.Deployment, error) {
+ return r.Items, r.Err
+}
+
+func (r *DeploymentLister) Deployments(namespace string) appsv1listers.DeploymentNamespaceLister {
+ return &nsDeploymentLister{r: r, ns: namespace}
+}
+
+func (r *DeploymentLister) GetDeploymentsForReplicaSet(rs *appsv1.ReplicaSet) ([]*appsv1.Deployment, error) {
+ return nil, fmt.Errorf("unimplemented GetDeploymentsForReplicaSet(%v)", rs)
+}
+
+type nsDeploymentLister struct {
+ r *DeploymentLister
+ ns string
+}
+
+// Assert that our fake implements the interface it is faking.
+var _ appsv1listers.DeploymentNamespaceLister = (*nsDeploymentLister)(nil)
+
+func (r *nsDeploymentLister) List(selector labels.Selector) ([]*appsv1.Deployment, error) {
+ return r.r.Items, r.r.Err
+}
+
+func (r *nsDeploymentLister) Get(name string) (*appsv1.Deployment, error) {
+ for _, s := range r.r.Items {
+ if s.Name == name && r.ns == s.Namespace {
+ return s, nil
+ }
+ }
+ return nil, errors.NewNotFound(schema.GroupResource{}, name)
+}
+
+// EndpointsLister is a lister.EndpointsLister fake for testing.
+type EndpointsLister struct {
+ Err error
+ Items []*corev1.Endpoints
+}
+
+// Assert that our fake implements the interface it is faking.
+var _ corev1listers.EndpointsLister = (*EndpointsLister)(nil)
+
+func (r *EndpointsLister) List(selector labels.Selector) ([]*corev1.Endpoints, error) {
+ return r.Items, r.Err
+}
+
+func (r *EndpointsLister) Endpoints(namespace string) corev1listers.EndpointsNamespaceLister {
+ return &nsEndpointsLister{r: r, ns: namespace}
+}
+
+type nsEndpointsLister struct {
+ r *EndpointsLister
+ ns string
+}
+
+// Assert that our fake implements the interface it is faking.
+var _ corev1listers.EndpointsNamespaceLister = (*nsEndpointsLister)(nil)
+
+func (r *nsEndpointsLister) List(selector labels.Selector) ([]*corev1.Endpoints, error) {
+ return r.r.Items, r.r.Err
+}
+
+func (r *nsEndpointsLister) Get(name string) (*corev1.Endpoints, error) {
+ for _, s := range r.r.Items {
+ if s.Name == name && r.ns == s.Namespace {
+ return s, nil
+ }
+ }
+ return nil, errors.NewNotFound(schema.GroupResource{}, name)
+}
+
+// K8sServiceLister is a lister.ServiceLister fake for testing.
+type K8sServiceLister struct {
+ Err error
+ Items []*corev1.Service
+}
+
+// Assert that our fake implements the interface it is faking.
+var _ corev1listers.ServiceLister = (*K8sServiceLister)(nil)
+
+func (r *K8sServiceLister) List(selector labels.Selector) ([]*corev1.Service, error) {
+ return r.Items, r.Err
+}
+
+func (r *K8sServiceLister) Services(namespace string) corev1listers.ServiceNamespaceLister {
+ return &nsK8sServiceLister{r: r, ns: namespace}
+}
+
+func (r *K8sServiceLister) GetPodServices(p *corev1.Pod) ([]*corev1.Service, error) {
+ return nil, fmt.Errorf("unimplemented GetPodServices(%v)", p)
+}
+
+type nsK8sServiceLister struct {
+ r *K8sServiceLister
+ ns string
+}
+
+// Assert that our fake implements the interface it is faking.
+var _ corev1listers.ServiceNamespaceLister = (*nsK8sServiceLister)(nil)
+
+func (r *nsK8sServiceLister) List(selector labels.Selector) ([]*corev1.Service, error) {
+ return r.r.Items, r.r.Err
+}
+
+func (r *nsK8sServiceLister) Get(name string) (*corev1.Service, error) {
+ for _, s := range r.r.Items {
+ if s.Name == name && r.ns == s.Namespace {
+ return s, nil
+ }
+ }
+ return nil, errors.NewNotFound(schema.GroupResource{}, name)
+}