From c60b85150c8be9440fb43511d2bd43930cea540d Mon Sep 17 00:00:00 2001 From: Murugappan Chetty Date: Mon, 2 Nov 2020 07:21:35 -0800 Subject: [PATCH] Export test refactor (#1069) * vendor changes for importing serving test * refactor export test * vendor changes * lint changes * add comments and address review comments * review comments for #1069 * rebase * remove vendor/license * remove vendor/license --- lib/test/service.go | 190 ++++++- pkg/kn/commands/service/export_test.go | 515 +++++------------- test/e2e/service_export_test.go | 509 +++++------------ vendor/knative.dev/serving/pkg/gc/config.go | 174 ++++++ vendor/knative.dev/serving/pkg/gc/doc.go | 20 + .../serving/pkg/gc/zz_generated.deepcopy.go | 37 ++ .../pkg/reconciler/route/config/doc.go | 21 + .../pkg/reconciler/route/config/domain.go | 118 ++++ .../pkg/reconciler/route/config/store.go | 121 ++++ .../route/config/zz_generated.deepcopy.go | 75 +++ .../pkg/reconciler/route/domains/doc.go | 18 + .../pkg/reconciler/route/domains/domains.go | 134 +++++ .../reconciler/route/resources/labels/doc.go | 18 + .../route/resources/labels/labels.go | 51 ++ .../reconciler/route/resources/names/doc.go | 18 + .../reconciler/route/resources/names/names.go | 44 ++ .../reconciler/service/resources/names/doc.go | 18 + .../service/resources/names/names.go | 29 + .../serving/pkg/testing/v1/configuration.go | 133 +++++ .../serving/pkg/testing/v1/revision.go | 269 +++++++++ .../serving/pkg/testing/v1/route.go | 294 ++++++++++ .../serving/pkg/testing/v1/service.go | 481 ++++++++++++++++ vendor/modules.txt | 7 + 23 files changed, 2529 insertions(+), 765 deletions(-) create mode 100644 vendor/knative.dev/serving/pkg/gc/config.go create mode 100644 vendor/knative.dev/serving/pkg/gc/doc.go create mode 100644 vendor/knative.dev/serving/pkg/gc/zz_generated.deepcopy.go create mode 100644 vendor/knative.dev/serving/pkg/reconciler/route/config/doc.go create mode 100644 vendor/knative.dev/serving/pkg/reconciler/route/config/domain.go create mode 100644 vendor/knative.dev/serving/pkg/reconciler/route/config/store.go create mode 100644 vendor/knative.dev/serving/pkg/reconciler/route/config/zz_generated.deepcopy.go create mode 100644 vendor/knative.dev/serving/pkg/reconciler/route/domains/doc.go create mode 100644 vendor/knative.dev/serving/pkg/reconciler/route/domains/domains.go create mode 100644 vendor/knative.dev/serving/pkg/reconciler/route/resources/labels/doc.go create mode 100644 vendor/knative.dev/serving/pkg/reconciler/route/resources/labels/labels.go create mode 100644 vendor/knative.dev/serving/pkg/reconciler/route/resources/names/doc.go create mode 100644 vendor/knative.dev/serving/pkg/reconciler/route/resources/names/names.go create mode 100644 vendor/knative.dev/serving/pkg/reconciler/service/resources/names/doc.go create mode 100644 vendor/knative.dev/serving/pkg/reconciler/service/resources/names/names.go create mode 100644 vendor/knative.dev/serving/pkg/testing/v1/configuration.go create mode 100644 vendor/knative.dev/serving/pkg/testing/v1/revision.go create mode 100644 vendor/knative.dev/serving/pkg/testing/v1/route.go create mode 100644 vendor/knative.dev/serving/pkg/testing/v1/service.go diff --git a/lib/test/service.go b/lib/test/service.go index 55d05baa7..44d6f6d9c 100644 --- a/lib/test/service.go +++ b/lib/test/service.go @@ -15,18 +15,44 @@ package test import ( + "context" "encoding/json" "strings" "gotest.tools/assert" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + clientv1alpha1 "knative.dev/client/pkg/apis/client/v1alpha1" + "knative.dev/pkg/kmeta" + "knative.dev/pkg/ptr" pkgtest "knative.dev/pkg/test" + "knative.dev/serving/pkg/apis/config" servingv1 "knative.dev/serving/pkg/apis/serving/v1" + servingtest "knative.dev/serving/pkg/testing/v1" "knative.dev/client/pkg/util" ) +// ExpectedServiceListOption enables further configuration of a ServiceList. +type ExpectedServiceListOption func(*servingv1.ServiceList) + +// ExpectedRevisionListOption enables further configuration of a RevisionList. +type ExpectedRevisionListOption func(*servingv1.RevisionList) + +// ExpectedKNExportOption enables further configuration of a Export. +type ExpectedKNExportOption func(*clientv1alpha1.Export) + +var revisionSpec = servingv1.RevisionSpec{ + PodSpec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Image: pkgtest.ImagePath("helloworld"), + }}, + EnableServiceLinks: ptr.Bool(false), + }, + TimeoutSeconds: ptr.Int64(config.DefaultRevisionTimeoutSeconds), +} + // ServiceCreate verifies given service creation in sync mode and also verifies output func ServiceCreate(r *KnRunResultCollector, serviceName string) { out := r.KnTest().Kn().Run("service", "create", serviceName, "--image", pkgtest.ImagePath("helloworld")) @@ -96,6 +122,7 @@ func ServiceDescribeWithJSONPath(r *KnRunResultCollector, serviceName, jsonpath return out.Stdout } +// ValidateServiceResources validates cpu and mem resources func ValidateServiceResources(r *KnRunResultCollector, serviceName string, requestsMemory, requestsCPU, limitsMemory, limitsCPU string) { var err error rlist := corev1.ResourceList{} @@ -123,8 +150,8 @@ func ValidateServiceResources(r *KnRunResultCollector, serviceName string, reque assert.DeepEqual(r.T(), serviceLimitsResourceList, llist) } -//GetServiceFromKNServiceDescribe runs the kn service describe command -//decodes it into a ksvc and returns it. +// GetServiceFromKNServiceDescribe runs the kn service describe command +// decodes it into a ksvc and returns it. func GetServiceFromKNServiceDescribe(r *KnRunResultCollector, serviceName string) servingv1.Service { out := r.KnTest().Kn().Run("service", "describe", serviceName, "-ojson") data := json.NewDecoder(strings.NewReader(out.Stdout)) @@ -134,3 +161,162 @@ func GetServiceFromKNServiceDescribe(r *KnRunResultCollector, serviceName string assert.NilError(r.T(), err) return service } + +// BuildServiceListWithOptions returns ServiceList with options provided +func BuildServiceListWithOptions(options ...ExpectedServiceListOption) *servingv1.ServiceList { + list := &servingv1.ServiceList{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "List", + }, + } + + for _, fn := range options { + fn(list) + } + + return list +} + +// WithService appends the given service to ServiceList +func WithService(svc *servingv1.Service) ExpectedServiceListOption { + return func(list *servingv1.ServiceList) { + list.Items = append(list.Items, *svc) + } +} + +// BuildRevisionListWithOptions returns RevisionList with options provided +func BuildRevisionListWithOptions(options ...ExpectedRevisionListOption) *servingv1.RevisionList { + list := &servingv1.RevisionList{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "List", + }, + } + + for _, fn := range options { + fn(list) + } + + return list +} + +// BuildKNExportWithOptions returns Export object with the options provided +func BuildKNExportWithOptions(options ...ExpectedKNExportOption) *clientv1alpha1.Export { + knExport := &clientv1alpha1.Export{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "client.knative.dev/v1alpha1", + Kind: "Export", + }, + } + + for _, fn := range options { + fn(knExport) + } + + return knExport +} + +// BuildConfigurationSpec builds servingv1.ConfigurationSpec with the options provided +func BuildConfigurationSpec(co ...servingtest.ConfigOption) *servingv1.ConfigurationSpec { + c := &servingv1.Configuration{ + Spec: servingv1.ConfigurationSpec{ + Template: servingv1.RevisionTemplateSpec{ + Spec: *revisionSpec.DeepCopy(), + }, + }, + } + for _, opt := range co { + opt(c) + } + c.SetDefaults(context.Background()) + return &c.Spec +} + +// BuildServiceWithOptions returns ksvc with options provided +func BuildServiceWithOptions(name string, so ...servingtest.ServiceOption) *servingv1.Service { + svc := servingtest.ServiceWithoutNamespace(name, so...) + svc.TypeMeta = metav1.TypeMeta{ + Kind: "Service", + APIVersion: "serving.knative.dev/v1", + } + svc.Spec.Template.Spec.Containers[0].Resources = corev1.ResourceRequirements{} + return svc +} + +// WithTrafficSpec adds route to ksvc +func WithTrafficSpec(revisions []string, percentages []int, tags []string) servingtest.ServiceOption { + return func(svc *servingv1.Service) { + var trafficTargets []servingv1.TrafficTarget + for i, rev := range revisions { + trafficTargets = append(trafficTargets, servingv1.TrafficTarget{ + Percent: ptr.Int64(int64(percentages[i])), + }) + if tags[i] != "" { + trafficTargets[i].Tag = tags[i] + } + if rev == "latest" { + trafficTargets[i].LatestRevision = ptr.Bool(true) + } else { + trafficTargets[i].RevisionName = rev + trafficTargets[i].LatestRevision = ptr.Bool(false) + } + } + svc.Spec.RouteSpec = servingv1.RouteSpec{ + Traffic: trafficTargets, + } + } +} + +// BuildRevision returns Revision object with the options provided +func BuildRevision(name string, options ...servingtest.RevisionOption) *servingv1.Revision { + rev := servingtest.Revision("", name, options...) + rev.TypeMeta = metav1.TypeMeta{ + Kind: "Revision", + APIVersion: "serving.knative.dev/v1", + } + rev.Spec.PodSpec.Containers[0].Name = config.DefaultUserContainerName + rev.Spec.PodSpec.EnableServiceLinks = ptr.Bool(false) + rev.ObjectMeta.SelfLink = "" + rev.ObjectMeta.Namespace = "" + rev.ObjectMeta.UID = "" + rev.ObjectMeta.Generation = int64(0) + rev.Spec.PodSpec.Containers[0].Resources = corev1.ResourceRequirements{} + return rev +} + +// WithRevision appends Revision object to RevisionList +func WithRevision(rev servingv1.Revision) ExpectedRevisionListOption { + return func(list *servingv1.RevisionList) { + list.Items = append(list.Items, rev) + } +} + +// WithKNRevision appends Revision object RevisionList to Kn Export +func WithKNRevision(rev servingv1.Revision) ExpectedKNExportOption { + return func(export *clientv1alpha1.Export) { + export.Spec.Revisions = append(export.Spec.Revisions, rev) + } +} + +// WithRevisionEnv adds env variable to Revision object +func WithRevisionEnv(evs ...corev1.EnvVar) servingtest.RevisionOption { + return func(s *servingv1.Revision) { + s.Spec.PodSpec.Containers[0].Env = evs + } +} + +// WithRevisionImage adds revision image to Revision object +func WithRevisionImage(image string) servingtest.RevisionOption { + return func(s *servingv1.Revision) { + s.Spec.PodSpec.Containers[0].Image = image + } +} + +// WithRevisionAnnotations adds annotation to revision spec in ksvc +func WithRevisionAnnotations(annotations map[string]string) servingtest.ServiceOption { + return func(service *servingv1.Service) { + service.Spec.Template.ObjectMeta.Annotations = kmeta.UnionMaps( + service.Spec.Template.ObjectMeta.Annotations, annotations) + } +} diff --git a/pkg/kn/commands/service/export_test.go b/pkg/kn/commands/service/export_test.go index ac825a843..f60be43f5 100644 --- a/pkg/kn/commands/service/export_test.go +++ b/pkg/kn/commands/service/export_test.go @@ -15,28 +15,33 @@ package service import ( + "context" "encoding/json" "testing" "gotest.tools/assert" v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + libtest "knative.dev/client/lib/test" clientv1alpha1 "knative.dev/client/pkg/apis/client/v1alpha1" knclient "knative.dev/client/pkg/serving/v1" "knative.dev/client/pkg/util/mock" "knative.dev/pkg/ptr" apiserving "knative.dev/serving/pkg/apis/serving" servingv1 "knative.dev/serving/pkg/apis/serving/v1" + servingtest "knative.dev/serving/pkg/testing/v1" "sigs.k8s.io/yaml" ) -type expectedServiceOption func(*servingv1.Service) -type expectedRevisionOption func(*servingv1.Revision) -type expectedServiceListOption func(*servingv1.ServiceList) -type expectedRevisionListOption func(*servingv1.RevisionList) -type expectedKNExportOption func(*clientv1alpha1.Export) -type podSpecOption func(*v1.PodSpec) +var revisionSpec = servingv1.RevisionSpec{ + PodSpec: v1.PodSpec{ + Containers: []v1.Container{{ + Image: "busybox", + }}, + EnableServiceLinks: ptr.Bool(false), + }, + TimeoutSeconds: ptr.Int64(300), +} type testCase struct { name string @@ -62,11 +67,11 @@ func TestServiceExportError(t *testing.T) { func TestServiceExport(t *testing.T) { for _, tc := range []testCase{ - {latestSvc: getServiceWithOptions(getService("foo"), withServicePodSpecOption(withContainer()))}, - {latestSvc: getServiceWithOptions(getService("foo"), withServicePodSpecOption(withContainer(), withEnv([]v1.EnvVar{{Name: "a", Value: "mouse"}})))}, - {latestSvc: getServiceWithOptions(getService("foo"), withConfigurationLabels(map[string]string{"a": "mouse"}), withConfigurationAnnotations(map[string]string{"a": "mouse"}), withServicePodSpecOption(withContainer()))}, - {latestSvc: getServiceWithOptions(getService("foo"), withLabels(map[string]string{"a": "mouse"}), withAnnotations(map[string]string{"a": "mouse"}), withServicePodSpecOption(withContainer()))}, - {latestSvc: getServiceWithOptions(getService("foo"), withServicePodSpecOption(withContainer(), withVolumeandSecrets("secretName")))}, + {latestSvc: libtest.BuildServiceWithOptions("foo", servingtest.WithConfigSpec(buildConfiguration()))}, + {latestSvc: libtest.BuildServiceWithOptions("foo", servingtest.WithConfigSpec(buildConfiguration()), servingtest.WithEnv(v1.EnvVar{Name: "a", Value: "mouse"}))}, + {latestSvc: libtest.BuildServiceWithOptions("foo", servingtest.WithConfigSpec(buildConfiguration()), libtest.WithRevisionAnnotations(map[string]string{"client.knative.dev/user-image": "busybox:v2"}))}, + {latestSvc: libtest.BuildServiceWithOptions("foo", servingtest.WithConfigSpec(buildConfiguration()), servingtest.WithServiceLabel("a", "mouse"), servingtest.WithServiceAnnotation("a", "mouse"))}, + {latestSvc: libtest.BuildServiceWithOptions("foo", servingtest.WithConfigSpec(buildConfiguration()), servingtest.WithVolume("secretName", "/mountpath", volumeSource("secretName")))}, } { exportServiceTest(t, &tc) } @@ -80,181 +85,130 @@ func exportServiceTest(t *testing.T, tc *testCase) { err = yaml.Unmarshal([]byte(output), &actSvc) assert.NilError(t, err) - stripUnwantedFields(tc.latestSvc) assert.DeepEqual(t, tc.latestSvc, &actSvc) } func TestServiceExportwithMultipleRevisions(t *testing.T) { for _, tc := range []testCase{{ name: "test 2 revisions with traffic split", - latestSvc: getServiceWithOptions( - getService("foo"), - withAnnotations(map[string]string{"serving.knative.dev/creator": "ut", "serving.knative.dev/lastModifier": "ut"}), - withConfigurationAnnotations(map[string]string{"client.knative.dev/user-image": "gcr.io/foo/bar:baz2"}), - withTrafficSplit([]string{"foo-rev-1", ""}, []int{50, 50}, []bool{false, true}), - withServicePodSpecOption(withContainer()), + latestSvc: libtest.BuildServiceWithOptions( + "foo", servingtest.WithConfigSpec(buildConfiguration()), + libtest.WithRevisionAnnotations(map[string]string{"client.knative.dev/user-image": "busybox:v2"}), + libtest.WithTrafficSpec([]string{"foo-rev-1", "latest"}, []int{50, 50}, []string{"", ""}), ), - expectedSvcList: getServiceListWithOptions( - withServices( - getService("foo"), - withUnwantedFieldsStripped(), - withConfigurationAnnotations(map[string]string{"client.knative.dev/user-image": "gcr.io/foo/bar:baz1"}), - withServicePodSpecOption(withContainer()), - withServiceRevisionName("foo-rev-1"), - ), - withServices( - getService("foo"), - withUnwantedFieldsStripped(), - withConfigurationAnnotations(map[string]string{"client.knative.dev/user-image": "gcr.io/foo/bar:baz2"}), - withServicePodSpecOption(withContainer()), - withTrafficSplit([]string{"foo-rev-1", ""}, []int{50, 50}, []bool{false, true}), - ), + expectedSvcList: libtest.BuildServiceListWithOptions( + libtest.WithService(libtest.BuildServiceWithOptions("foo", servingtest.WithConfigSpec(buildConfiguration()), + libtest.WithRevisionAnnotations(map[string]string{"client.knative.dev/user-image": "busybox:v1"}), + servingtest.WithBYORevisionName("foo-rev-1"), + )), + libtest.WithService(libtest.BuildServiceWithOptions("foo", servingtest.WithConfigSpec(buildConfiguration()), + libtest.WithRevisionAnnotations(map[string]string{"client.knative.dev/user-image": "busybox:v2"}), + libtest.WithTrafficSpec([]string{"foo-rev-1", "latest"}, []int{50, 50}, []string{"", ""}), + )), ), - revisionList: getRevisionListWithOptions( - withRevisions( - withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}), - withRevisionGeneration("1"), - withRevisionAnnotations(map[string]string{"serving.knative.dev/lastPinned": "1111132"}), - withRevisionAnnotations(map[string]string{"client.knative.dev/user-image": "gcr.io/foo/bar:baz1"}), - withRevisionName("foo-rev-1"), - withRevisionPodSpecOption(withContainer()), - ), - withRevisions( - withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}), - withRevisionGeneration("2"), - withRevisionName("foo-rev-2"), - withRevisionAnnotations(map[string]string{"client.knative.dev/user-image": "gcr.io/foo/bar:baz2"}), - withRevisionPodSpecOption(withContainer()), - ), + revisionList: libtest.BuildRevisionListWithOptions( + libtest.WithRevision(*(libtest.BuildRevision("foo-rev-1", + servingtest.WithRevisionLabel(apiserving.ServiceLabelKey, "foo"), + servingtest.WithRevisionLabel(apiserving.ConfigurationGenerationLabelKey, "1"), + servingtest.WithRevisionAnn("client.knative.dev/user-image", "busybox:v1"), + servingtest.WithRevisionAnn("serving.knative.dev/lastPinned", "1111132"), + ))), + libtest.WithRevision(*(libtest.BuildRevision("foo-rev-2", + servingtest.WithRevisionLabel(apiserving.ServiceLabelKey, "foo"), + servingtest.WithRevisionLabel(apiserving.ConfigurationGenerationLabelKey, "1"), + servingtest.WithRevisionAnn("client.knative.dev/user-image", "busybox:v2"), + ))), ), - expectedKNExport: getKNExportWithOptions( - withKNRevisions( - withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}), - withRevisionName("foo-rev-1"), - withRevisionGeneration("1"), - withRevisionAnnotations(map[string]string{"client.knative.dev/user-image": "gcr.io/foo/bar:baz1"}), - withRevisionPodSpecOption(withContainer()), - ), + expectedKNExport: libtest.BuildKNExportWithOptions( + libtest.WithKNRevision(*(libtest.BuildRevision("foo-rev-1", + servingtest.WithRevisionLabel(apiserving.ServiceLabelKey, "foo"), + servingtest.WithRevisionLabel(apiserving.ConfigurationGenerationLabelKey, "1"), + servingtest.WithRevisionAnn("client.knative.dev/user-image", "busybox:v1"), + ))), ), }, { name: "test 2 revisions no traffic split", - latestSvc: getServiceWithOptions( - getService("foo"), - withTrafficSplit([]string{""}, []int{100}, []bool{true}), - withServicePodSpecOption(withContainer()), + latestSvc: libtest.BuildServiceWithOptions( + "foo", servingtest.WithConfigSpec(buildConfiguration()), + libtest.WithTrafficSpec([]string{"latest"}, []int{100}, []string{""}), ), - expectedSvcList: getServiceListWithOptions( - withServices( - getService("foo"), - withUnwantedFieldsStripped(), - withServicePodSpecOption(withContainer()), - withTrafficSplit([]string{""}, []int{100}, []bool{true}), - ), + expectedSvcList: libtest.BuildServiceListWithOptions( + libtest.WithService(libtest.BuildServiceWithOptions( + "foo", servingtest.WithConfigSpec(buildConfiguration()), + libtest.WithTrafficSpec([]string{"latest"}, []int{100}, []string{""}), + )), ), - revisionList: getRevisionListWithOptions( - withRevisions( - withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}), - withRevisionGeneration("1"), - withRevisionName("foo-rev-1"), - withRevisionPodSpecOption(withContainer()), - ), - withRevisions( - withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}), - withRevisionGeneration("2"), - withRevisionName("foo-rev-2"), - withRevisionPodSpecOption(withContainer()), - ), + revisionList: libtest.BuildRevisionListWithOptions( + libtest.WithRevision(*(libtest.BuildRevision("foo-rev-1", + servingtest.WithRevisionLabel(apiserving.ConfigurationGenerationLabelKey, "1"), + servingtest.WithRevisionLabel(apiserving.ServiceLabelKey, "foo"), + ))), + libtest.WithRevision(*(libtest.BuildRevision("foo-rev-2", + servingtest.WithRevisionLabel(apiserving.ConfigurationGenerationLabelKey, "2"), + servingtest.WithRevisionLabel(apiserving.ServiceLabelKey, "foo"), + ))), ), - expectedKNExport: getKNExportWithOptions(), + expectedKNExport: libtest.BuildKNExportWithOptions(), }, { name: "test 3 active revisions with traffic split with no latest revision", - latestSvc: getServiceWithOptions( - getService("foo"), - withTrafficSplit([]string{"foo-rev-1", "foo-rev-2", "foo-rev-3"}, []int{25, 50, 25}, []bool{false, false, false}), - withServiceRevisionName("foo-rev-3"), - withServicePodSpecOption( - withContainer(), - withEnv([]v1.EnvVar{{Name: "a", Value: "mouse"}}), - ), + latestSvc: libtest.BuildServiceWithOptions( + "foo", servingtest.WithConfigSpec(buildConfiguration()), + servingtest.WithEnv(v1.EnvVar{Name: "a", Value: "mouse"}), + servingtest.WithBYORevisionName("foo-rev-3"), + libtest.WithTrafficSpec([]string{"foo-rev-1", "foo-rev-2", "foo-rev-3"}, []int{25, 50, 25}, []string{"", "", ""}), ), - expectedSvcList: getServiceListWithOptions( - withServices( - getService("foo"), - withUnwantedFieldsStripped(), - withServicePodSpecOption( - withContainer(), - withEnv([]v1.EnvVar{{Name: "a", Value: "cat"}}), - ), - withServiceRevisionName("foo-rev-1"), - ), - withServices( - getService("foo"), - withUnwantedFieldsStripped(), - withServicePodSpecOption( - withContainer(), - withEnv([]v1.EnvVar{{Name: "a", Value: "dog"}}), - ), - withServiceRevisionName("foo-rev-2"), - ), - withServices( - getService("foo"), - withUnwantedFieldsStripped(), - withServicePodSpecOption( - withContainer(), - withEnv([]v1.EnvVar{{Name: "a", Value: "mouse"}}), - ), - withServiceRevisionName("foo-rev-3"), - withTrafficSplit([]string{"foo-rev-1", "foo-rev-2", "foo-rev-3"}, []int{25, 50, 25}, []bool{false, false, false}), - ), - ), - revisionList: getRevisionListWithOptions( - withRevisions( - withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}), - withRevisionGeneration("1"), - withRevisionName("foo-rev-1"), - withRevisionPodSpecOption( - withContainer(), - withEnv([]v1.EnvVar{{Name: "a", Value: "cat"}}), + expectedSvcList: libtest.BuildServiceListWithOptions( + libtest.WithService( + libtest.BuildServiceWithOptions( + "foo", servingtest.WithConfigSpec(buildConfiguration()), + servingtest.WithEnv(v1.EnvVar{Name: "a", Value: "cat"}), + servingtest.WithBYORevisionName("foo-rev-1"), ), ), - withRevisions( - withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}), - withRevisionGeneration("2"), - withRevisionName("foo-rev-2"), - withRevisionPodSpecOption( - withContainer(), - withEnv([]v1.EnvVar{{Name: "a", Value: "dog"}}), + libtest.WithService( + libtest.BuildServiceWithOptions( + "foo", servingtest.WithConfigSpec(buildConfiguration()), + servingtest.WithEnv(v1.EnvVar{Name: "a", Value: "dog"}), + servingtest.WithBYORevisionName("foo-rev-2"), ), ), - withRevisions( - withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}), - withRevisionGeneration("3"), - withRevisionName("foo-rev-3"), - withRevisionPodSpecOption( - withContainer(), - withEnv([]v1.EnvVar{{Name: "a", Value: "mouse"}}), + libtest.WithService( + libtest.BuildServiceWithOptions( + "foo", servingtest.WithConfigSpec(buildConfiguration()), + servingtest.WithEnv(v1.EnvVar{Name: "a", Value: "mouse"}), + servingtest.WithBYORevisionName("foo-rev-3"), + libtest.WithTrafficSpec([]string{"foo-rev-1", "foo-rev-2", "foo-rev-3"}, []int{25, 50, 25}, []string{"", "", ""}), ), ), ), - expectedKNExport: getKNExportWithOptions( - withKNRevisions( - withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}), - withRevisionName("foo-rev-1"), - withRevisionGeneration("1"), - withRevisionPodSpecOption( - withContainer(), - withEnv([]v1.EnvVar{{Name: "a", Value: "cat"}}), - ), - ), - withKNRevisions( - withRevisionLabels(map[string]string{apiserving.ServiceLabelKey: "foo"}), - withRevisionName("foo-rev-2"), - withRevisionGeneration("2"), - withRevisionPodSpecOption( - withContainer(), - withEnv([]v1.EnvVar{{Name: "a", Value: "dog"}}), - ), - ), + revisionList: libtest.BuildRevisionListWithOptions( + libtest.WithRevision(*(libtest.BuildRevision("foo-rev-1", + servingtest.WithRevisionLabel(apiserving.ConfigurationGenerationLabelKey, "1"), + servingtest.WithRevisionLabel(apiserving.ServiceLabelKey, "foo"), + libtest.WithRevisionEnv(v1.EnvVar{Name: "a", Value: "cat"}), + ))), + libtest.WithRevision(*(libtest.BuildRevision("foo-rev-2", + servingtest.WithRevisionLabel(apiserving.ConfigurationGenerationLabelKey, "2"), + servingtest.WithRevisionLabel(apiserving.ServiceLabelKey, "foo"), + libtest.WithRevisionEnv(v1.EnvVar{Name: "a", Value: "dog"}), + ))), + libtest.WithRevision(*(libtest.BuildRevision("foo-rev-3", + servingtest.WithRevisionLabel(apiserving.ConfigurationGenerationLabelKey, "3"), + servingtest.WithRevisionLabel(apiserving.ServiceLabelKey, "foo"), + libtest.WithRevisionEnv(v1.EnvVar{Name: "a", Value: "mouse"}), + ))), + ), + expectedKNExport: libtest.BuildKNExportWithOptions( + libtest.WithKNRevision(*(libtest.BuildRevision("foo-rev-1", + servingtest.WithRevisionLabel(apiserving.ConfigurationGenerationLabelKey, "1"), + servingtest.WithRevisionLabel(apiserving.ServiceLabelKey, "foo"), + libtest.WithRevisionEnv(v1.EnvVar{Name: "a", Value: "cat"}), + ))), + libtest.WithKNRevision(*(libtest.BuildRevision("foo-rev-2", + servingtest.WithRevisionLabel(apiserving.ConfigurationGenerationLabelKey, "2"), + servingtest.WithRevisionLabel(apiserving.ServiceLabelKey, "foo"), + libtest.WithRevisionEnv(v1.EnvVar{Name: "a", Value: "dog"}), + ))), ), }} { t.Run(tc.name, func(t *testing.T) { @@ -278,7 +232,6 @@ func exportWithRevisionsTest(t *testing.T, tc *testCase) { output, err := executeServiceExportCommand(t, tc, "export", tc.latestSvc.ObjectMeta.Name, "--with-revisions", "--mode", "export", "-o", "json") assert.NilError(t, err) - stripUnwantedFields(tc.latestSvc) tc.expectedKNExport.Spec.Service = *tc.latestSvc actKNExport := &clientv1alpha1.Export{} @@ -298,226 +251,22 @@ func executeServiceExportCommand(t *testing.T, tc *testCase, options ...string) return executeServiceCommand(client, options...) } -func stripUnwantedFields(svc *servingv1.Service) { - svc.ObjectMeta.Namespace = "" - svc.Spec.Template.Spec.Containers[0].Resources = v1.ResourceRequirements{} - svc.Status = servingv1.ServiceStatus{} - svc.ObjectMeta.CreationTimestamp = metav1.Time{} - for _, annotation := range IGNORED_SERVICE_ANNOTATIONS { - delete(svc.ObjectMeta.Annotations, annotation) - } - if len(svc.ObjectMeta.Annotations) == 0 { - svc.ObjectMeta.Annotations = nil - } -} - -func getServiceListWithOptions(options ...expectedServiceListOption) *servingv1.ServiceList { - list := &servingv1.ServiceList{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "List", - }, - } - - for _, fn := range options { - fn(list) - } - - return list -} - -func withServices(svc *servingv1.Service, options ...expectedServiceOption) expectedServiceListOption { - return func(list *servingv1.ServiceList) { - list.Items = append(list.Items, *(getServiceWithOptions(svc, options...))) - } -} - -func getRevisionListWithOptions(options ...expectedRevisionListOption) *servingv1.RevisionList { - list := &servingv1.RevisionList{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "List", - }, - } - - for _, fn := range options { - fn(list) - } - - return list -} - -func withRevisions(options ...expectedRevisionOption) expectedRevisionListOption { - return func(list *servingv1.RevisionList) { - list.Items = append(list.Items, getRevisionWithOptions(options...)) - } -} - -func getKNExportWithOptions(options ...expectedKNExportOption) *clientv1alpha1.Export { - knExport := &clientv1alpha1.Export{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "client.knative.dev/v1alpha1", - Kind: "Export", - }, - } - - for _, fn := range options { - fn(knExport) - } - - return knExport -} - -func withKNRevisions(options ...expectedRevisionOption) expectedKNExportOption { - return func(export *clientv1alpha1.Export) { - export.Spec.Revisions = append(export.Spec.Revisions, getRevisionWithOptions(options...)) - } -} - -func getServiceWithOptions(svc *servingv1.Service, options ...expectedServiceOption) *servingv1.Service { - svc.TypeMeta = metav1.TypeMeta{ - Kind: "service", - APIVersion: "serving.knative.dev/v1", - } - - for _, fn := range options { - fn(svc) - } - - return svc -} -func withLabels(labels map[string]string) expectedServiceOption { - return func(svc *servingv1.Service) { - svc.ObjectMeta.Labels = labels - } -} -func withConfigurationLabels(labels map[string]string) expectedServiceOption { - return func(svc *servingv1.Service) { - svc.Spec.Template.ObjectMeta.Labels = labels - } -} -func withAnnotations(Annotations map[string]string) expectedServiceOption { - return func(svc *servingv1.Service) { - svc.ObjectMeta.Annotations = Annotations - } -} -func withConfigurationAnnotations(Annotations map[string]string) expectedServiceOption { - return func(svc *servingv1.Service) { - svc.Spec.Template.ObjectMeta.Annotations = Annotations - } -} -func withServiceRevisionName(name string) expectedServiceOption { - return func(svc *servingv1.Service) { - svc.Spec.Template.ObjectMeta.Name = name - } -} -func withUnwantedFieldsStripped() expectedServiceOption { - return func(svc *servingv1.Service) { - svc.ObjectMeta.Namespace = "" - svc.Spec.Template.Spec.Containers[0].Resources = v1.ResourceRequirements{} - svc.Status = servingv1.ServiceStatus{} - svc.ObjectMeta.CreationTimestamp = metav1.Time{} - } -} -func withTrafficSplit(revisions []string, percentages []int, latest []bool) expectedServiceOption { - return func(svc *servingv1.Service) { - var trafficTargets []servingv1.TrafficTarget - for i, rev := range revisions { - trafficTargets = append(trafficTargets, servingv1.TrafficTarget{ - Percent: ptr.Int64(int64(percentages[i])), - }) - if latest[i] { - trafficTargets[i].LatestRevision = ptr.Bool(true) - } else { - trafficTargets[i].RevisionName = rev - trafficTargets[i].LatestRevision = ptr.Bool(false) - } - } - svc.Spec.RouteSpec = servingv1.RouteSpec{ - Traffic: trafficTargets, - } - } -} -func withServicePodSpecOption(options ...podSpecOption) expectedServiceOption { - return func(svc *servingv1.Service) { - svc.Spec.Template.Spec.PodSpec = getPodSpecWithOptions(options...) - } -} - -func getRevisionWithOptions(options ...expectedRevisionOption) servingv1.Revision { - rev := servingv1.Revision{ - TypeMeta: metav1.TypeMeta{ - Kind: "Revision", - APIVersion: "serving.knative.dev/v1", - }, - } - for _, fn := range options { - fn(&rev) - } - return rev -} -func withRevisionGeneration(gen string) expectedRevisionOption { - return func(rev *servingv1.Revision) { - rev.ObjectMeta.Labels[apiserving.ConfigurationGenerationLabelKey] = gen - } -} -func withRevisionName(name string) expectedRevisionOption { - return func(rev *servingv1.Revision) { - rev.ObjectMeta.Name = name - } -} -func withRevisionLabels(labels map[string]string) expectedRevisionOption { - return func(rev *servingv1.Revision) { - rev.ObjectMeta.Labels = labels - } -} -func withRevisionAnnotations(Annotations map[string]string) expectedRevisionOption { - return func(rev *servingv1.Revision) { - rev.ObjectMeta.Annotations = Annotations - } -} -func withRevisionPodSpecOption(options ...podSpecOption) expectedRevisionOption { - return func(rev *servingv1.Revision) { - rev.Spec.PodSpec = getPodSpecWithOptions(options...) - } -} - -func getPodSpecWithOptions(options ...podSpecOption) v1.PodSpec { - spec := v1.PodSpec{} - for _, fn := range options { - fn(&spec) - } - return spec -} - -func withEnv(env []v1.EnvVar) podSpecOption { - return func(spec *v1.PodSpec) { - spec.Containers[0].Env = env - } -} -func withContainer() podSpecOption { - return func(spec *v1.PodSpec) { - spec.Containers = append(spec.Containers, v1.Container{Image: "gcr.io/foo/bar:baz"}) - } -} -func withVolumeandSecrets(secretName string) podSpecOption { - return func(spec *v1.PodSpec) { - spec.Volumes = []v1.Volume{ - { - Name: secretName, - VolumeSource: v1.VolumeSource{ - Secret: &v1.SecretVolumeSource{ - SecretName: secretName, - }, - }, +func buildConfiguration() *servingv1.ConfigurationSpec { + c := &servingv1.Configuration{ + Spec: servingv1.ConfigurationSpec{ + Template: servingv1.RevisionTemplateSpec{ + Spec: *revisionSpec.DeepCopy(), }, - } - spec.Containers[0].VolumeMounts = []v1.VolumeMount{ - { - Name: secretName, - MountPath: "/mount/path", - ReadOnly: true, - }, - } + }, + } + c.SetDefaults(context.Background()) + return &c.Spec +} + +func volumeSource(secretName string) v1.VolumeSource { + return v1.VolumeSource{ + Secret: &v1.SecretVolumeSource{ + SecretName: secretName, + }, } } diff --git a/test/e2e/service_export_test.go b/test/e2e/service_export_test.go index 71ea161a3..d15a9bd9d 100644 --- a/test/e2e/service_export_test.go +++ b/test/e2e/service_export_test.go @@ -25,25 +25,17 @@ import ( "gotest.tools/assert" - "k8s.io/apimachinery/pkg/util/intstr" - "knative.dev/pkg/ptr" "sigs.k8s.io/yaml" "knative.dev/client/lib/test" corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" clientv1alpha1 "knative.dev/client/pkg/apis/client/v1alpha1" pkgtest "knative.dev/pkg/test" servingv1 "knative.dev/serving/pkg/apis/serving/v1" + servingtest "knative.dev/serving/pkg/testing/v1" ) -type expectedServiceOption func(*servingv1.Service) -type expectedRevisionOption func(*servingv1.Revision) -type expectedServiceListOption func(*servingv1.ServiceList) -type expectedKNExportOption func(*clientv1alpha1.Export) -type podSpecOption func(*corev1.PodSpec) - func TestServiceExport(t *testing.T) { //FIXME: enable once 0.19 is available // see: https://github.com/knative/serving/pull/9685 @@ -64,105 +56,75 @@ func TestServiceExport(t *testing.T) { serviceCreateWithOptions(r, "hello", "--revision-name", "rev1") t.Log("export service-revision1 and compare") - serviceExport(r, "hello", getServiceWithOptions( - withServiceName("hello"), - withServiceRevisionName("hello-rev1"), - withConfigurationAnnotations(map[string]string{ - "client.knative.dev/user-image": pkgtest.ImagePath("helloworld"), - }), - withServicePodSpecOption(withContainer()), + serviceExport(r, "hello", test.BuildServiceWithOptions("hello", + servingtest.WithConfigSpec(test.BuildConfigurationSpec()), + servingtest.WithBYORevisionName("hello-rev1"), + test.WithRevisionAnnotations(map[string]string{"client.knative.dev/user-image": pkgtest.ImagePath("helloworld")}), ), "-o", "json") t.Log("update service - add env variable") test.ServiceUpdate(r, "hello", "--env", "a=mouse", "--revision-name", "rev2", "--no-lock-to-digest") - serviceExport(r, "hello", getServiceWithOptions( - withServiceName("hello"), - withServiceRevisionName("hello-rev2"), - withServicePodSpecOption( - withContainer(), - withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}}), - ), + serviceExport(r, "hello", test.BuildServiceWithOptions("hello", + servingtest.WithConfigSpec(test.BuildConfigurationSpec()), + servingtest.WithBYORevisionName("hello-rev2"), + servingtest.WithEnv(corev1.EnvVar{Name: "a", Value: "mouse"}), ), "-o", "json") t.Log("export service-revision2 with kubernetes-resources") - serviceExportWithServiceList(r, "hello", getServiceListWithOptions( - withServices( - withServiceName("hello"), - withServiceRevisionName("hello-rev2"), - withTrafficSplit([]string{"latest"}, []int{100}, []string{""}), - withServicePodSpecOption( - withContainer(), - withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}}), - ), - ), + serviceExportWithServiceList(r, "hello", test.BuildServiceListWithOptions( + test.WithService(test.BuildServiceWithOptions("hello", + servingtest.WithConfigSpec(test.BuildConfigurationSpec()), + servingtest.WithBYORevisionName("hello-rev2"), + test.WithTrafficSpec([]string{"latest"}, []int{100}, []string{""}), + servingtest.WithEnv(corev1.EnvVar{Name: "a", Value: "mouse"}), + )), ), "--with-revisions", "--mode", "replay", "-o", "yaml") t.Log("export service-revision2 with revisions-only") - serviceExportWithRevisionList(r, "hello", getServiceWithOptions( - withServiceName("hello"), - withServiceRevisionName("hello-rev2"), - withTrafficSplit([]string{"latest"}, []int{100}, []string{""}), - withServicePodSpecOption( - withContainer(), - withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}}), - ), - ), getKNExportWithOptions(), "--with-revisions", "--mode", "export", "-o", "yaml") + serviceExportWithRevisionList(r, "hello", test.BuildServiceWithOptions("hello", + servingtest.WithConfigSpec(test.BuildConfigurationSpec()), + servingtest.WithBYORevisionName("hello-rev2"), + test.WithTrafficSpec([]string{"latest"}, []int{100}, []string{""}), + servingtest.WithEnv(corev1.EnvVar{Name: "a", Value: "mouse"}), + ), test.BuildKNExportWithOptions(), "--with-revisions", "--mode", "export", "-o", "yaml") t.Log("update service with tag and split traffic") test.ServiceUpdate(r, "hello", "--tag", "hello-rev1=candidate", "--traffic", "candidate=2%,@latest=98%") t.Log("export service-revision2 after tagging kubernetes-resources") - serviceExportWithServiceList(r, "hello", getServiceListWithOptions( - withServices( - withServiceName("hello"), - withServiceRevisionName("hello-rev1"), - withConfigurationAnnotations(map[string]string{ + serviceExportWithServiceList(r, "hello", test.BuildServiceListWithOptions( + test.WithService(test.BuildServiceWithOptions("hello", + servingtest.WithConfigSpec(test.BuildConfigurationSpec()), + servingtest.WithBYORevisionName("hello-rev1"), + test.WithRevisionAnnotations(map[string]string{ "client.knative.dev/user-image": pkgtest.ImagePath("helloworld"), "serving.knative.dev/routes": "hello", }), - withServicePodSpecOption( - withContainer(), - ), - ), - withServices( - withServiceName("hello"), - withServiceRevisionName("hello-rev2"), - withTrafficSplit([]string{"latest", "hello-rev1"}, []int{98, 2}, []string{"", "candidate"}), - withServicePodSpecOption( - withContainer(), - withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}}), - ), - ), + )), + test.WithService(test.BuildServiceWithOptions("hello", + servingtest.WithConfigSpec(test.BuildConfigurationSpec()), + servingtest.WithBYORevisionName("hello-rev2"), + test.WithTrafficSpec([]string{"latest", "hello-rev1"}, []int{98, 2}, []string{"", "candidate"}), + servingtest.WithEnv(corev1.EnvVar{Name: "a", Value: "mouse"}), + )), ), "--with-revisions", "--mode", "replay", "-o", "yaml") t.Log("export service-revision2 after tagging with revisions-only") - serviceExportWithRevisionList(r, "hello", getServiceWithOptions( - withServiceName("hello"), - withServiceRevisionName("hello-rev2"), - withTrafficSplit([]string{"latest", "hello-rev1"}, []int{98, 2}, []string{"", "candidate"}), - withServicePodSpecOption( - withContainer(), - withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}}), - ), - ), getKNExportWithOptions( - withRevisions( - withRevisionName("hello-rev1"), - withRevisionAnnotations( - map[string]string{ - "client.knative.dev/user-image": pkgtest.ImagePath("helloworld"), - "serving.knative.dev/routes": "hello", - }), - withRevisionLabels( - map[string]string{ - "serving.knative.dev/configuration": "hello", - "serving.knative.dev/configurationGeneration": "1", - "serving.knative.dev/routingState": "active", - "serving.knative.dev/service": "hello", - }), - withRevisionPodSpecOption( - withContainer(), - ), - ), + serviceExportWithRevisionList(r, "hello", test.BuildServiceWithOptions("hello", + servingtest.WithConfigSpec(test.BuildConfigurationSpec()), + servingtest.WithBYORevisionName("hello-rev2"), + test.WithTrafficSpec([]string{"latest", "hello-rev1"}, []int{98, 2}, []string{"", "candidate"}), + servingtest.WithEnv(corev1.EnvVar{Name: "a", Value: "mouse"}), + ), test.BuildKNExportWithOptions( + test.WithKNRevision(*(test.BuildRevision("hello-rev1", + servingtest.WithRevisionAnn("client.knative.dev/user-image", pkgtest.ImagePath("helloworld")), + servingtest.WithRevisionAnn("serving.knative.dev/routes", "hello"), + servingtest.WithRevisionLabel("serving.knative.dev/configuration", "hello"), + servingtest.WithRevisionLabel("serving.knative.dev/configurationGeneration", "1"), + servingtest.WithRevisionLabel("serving.knative.dev/routingState", "active"), + servingtest.WithRevisionLabel("serving.knative.dev/service", "hello"), + test.WithRevisionImage(pkgtest.ImagePath("helloworld")), + ))), ), "--with-revisions", "--mode", "export", "-o", "yaml") t.Log("update service - untag, add env variable, traffic split and system revision name") @@ -170,148 +132,105 @@ func TestServiceExport(t *testing.T) { test.ServiceUpdate(r, "hello", "--env", "b=cat", "--revision-name", "hello-rev3", "--traffic", "hello-rev1=30,hello-rev2=30,hello-rev3=40") t.Log("export service-revision3 with kubernetes-resources") - serviceExportWithServiceList(r, "hello", getServiceListWithOptions( - withServices( - withServiceName("hello"), - withConfigurationAnnotations(map[string]string{ + serviceExportWithServiceList(r, "hello", test.BuildServiceListWithOptions( + test.WithService(test.BuildServiceWithOptions("hello", + servingtest.WithConfigSpec(test.BuildConfigurationSpec()), + test.WithRevisionAnnotations(map[string]string{ "client.knative.dev/user-image": pkgtest.ImagePath("helloworld"), "serving.knative.dev/routes": "hello", }), - withServiceRevisionName("hello-rev1"), - withServicePodSpecOption( - withContainer(), - ), + servingtest.WithBYORevisionName("hello-rev1"), ), - withServices( - withServiceName("hello"), - withServiceRevisionName("hello-rev2"), - withConfigurationAnnotations(map[string]string{ + ), + test.WithService(test.BuildServiceWithOptions("hello", + servingtest.WithConfigSpec(test.BuildConfigurationSpec()), + servingtest.WithBYORevisionName("hello-rev2"), + test.WithRevisionAnnotations(map[string]string{ "serving.knative.dev/routes": "hello", }), - withServicePodSpecOption( - withContainer(), - withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}}), - ), + servingtest.WithEnv(corev1.EnvVar{Name: "a", Value: "mouse"}), ), - withServices( - withServiceName("hello"), - withServiceRevisionName("hello-rev3"), - withTrafficSplit([]string{"hello-rev1", "hello-rev2", "hello-rev3"}, []int{30, 30, 40}, []string{"", "", ""}), - withServicePodSpecOption( - withContainer(), - withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}, {Name: "b", Value: "cat"}}), - ), ), - ), "--with-revisions", "--mode", "replay", "-o", "yaml") + test.WithService(test.BuildServiceWithOptions("hello", + servingtest.WithConfigSpec(test.BuildConfigurationSpec()), + servingtest.WithBYORevisionName("hello-rev3"), + test.WithTrafficSpec([]string{"hello-rev1", "hello-rev2", "hello-rev3"}, []int{30, 30, 40}, []string{"", "", ""}), + servingtest.WithEnv(corev1.EnvVar{Name: "a", Value: "mouse"}, corev1.EnvVar{Name: "b", Value: "cat"}), + ), + )), "--with-revisions", "--mode", "replay", "-o", "yaml") t.Log("export service-revision3 with revisions-only") - serviceExportWithRevisionList(r, "hello", getServiceWithOptions( - withServiceName("hello"), - withServiceRevisionName("hello-rev3"), - withTrafficSplit([]string{"hello-rev1", "hello-rev2", "hello-rev3"}, []int{30, 30, 40}, []string{"", "", ""}), - withServicePodSpecOption( - withContainer(), - withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}, {Name: "b", Value: "cat"}}), - ), - ), getKNExportWithOptions( - withRevisions( - withRevisionName("hello-rev1"), - withRevisionAnnotations( - map[string]string{ - "client.knative.dev/user-image": pkgtest.ImagePath("helloworld"), - "serving.knative.dev/routes": "hello", - }), - withRevisionLabels( - map[string]string{ - "serving.knative.dev/configuration": "hello", - "serving.knative.dev/configurationGeneration": "1", - "serving.knative.dev/routingState": "active", - "serving.knative.dev/service": "hello", - }), - withRevisionPodSpecOption( - withContainer(), - ), - ), - withRevisions( - withRevisionName("hello-rev2"), - withRevisionAnnotations( - map[string]string{ - "serving.knative.dev/routes": "hello", - }), - withRevisionLabels( - map[string]string{ - "serving.knative.dev/configuration": "hello", - "serving.knative.dev/configurationGeneration": "2", - "serving.knative.dev/routingState": "active", - "serving.knative.dev/service": "hello", - }), - withRevisionPodSpecOption( - withContainer(), - withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}}), - ), - ), + serviceExportWithRevisionList(r, "hello", test.BuildServiceWithOptions("hello", + servingtest.WithConfigSpec(test.BuildConfigurationSpec()), + test.WithTrafficSpec([]string{"hello-rev1", "hello-rev2", "hello-rev3"}, []int{30, 30, 40}, []string{"", "", ""}), + servingtest.WithBYORevisionName("hello-rev3"), + servingtest.WithEnv(corev1.EnvVar{Name: "a", Value: "mouse"}, corev1.EnvVar{Name: "b", Value: "cat"}), + ), test.BuildKNExportWithOptions( + test.WithKNRevision(*(test.BuildRevision("hello-rev1", + servingtest.WithRevisionAnn("client.knative.dev/user-image", pkgtest.ImagePath("helloworld")), + servingtest.WithRevisionAnn("serving.knative.dev/routes", "hello"), + servingtest.WithRevisionLabel("serving.knative.dev/configuration", "hello"), + servingtest.WithRevisionLabel("serving.knative.dev/configurationGeneration", "1"), + servingtest.WithRevisionLabel("serving.knative.dev/routingState", "active"), + servingtest.WithRevisionLabel("serving.knative.dev/service", "hello"), + test.WithRevisionImage(pkgtest.ImagePath("helloworld")), + ))), + test.WithKNRevision(*(test.BuildRevision("hello-rev2", + servingtest.WithRevisionAnn("serving.knative.dev/routes", "hello"), + servingtest.WithRevisionLabel("serving.knative.dev/configuration", "hello"), + servingtest.WithRevisionLabel("serving.knative.dev/configurationGeneration", "2"), + servingtest.WithRevisionLabel("serving.knative.dev/routingState", "active"), + servingtest.WithRevisionLabel("serving.knative.dev/service", "hello"), + test.WithRevisionImage(pkgtest.ImagePath("helloworld")), + test.WithRevisionEnv(corev1.EnvVar{Name: "a", Value: "mouse"}), + ))), ), "--with-revisions", "--mode", "export", "-o", "yaml") t.Log("send all traffic to revision 2") test.ServiceUpdate(r, "hello", "--traffic", "hello-rev2=100") t.Log("export kubernetes-resources - all traffic to revision 2") - serviceExportWithServiceList(r, "hello", getServiceListWithOptions( - withServices( - withServiceName("hello"), - withServiceRevisionName("hello-rev2"), - withConfigurationAnnotations(map[string]string{ + serviceExportWithServiceList(r, "hello", test.BuildServiceListWithOptions( + test.WithService(test.BuildServiceWithOptions("hello", + servingtest.WithConfigSpec(test.BuildConfigurationSpec()), + servingtest.WithBYORevisionName("hello-rev2"), + test.WithRevisionAnnotations(map[string]string{ "serving.knative.dev/routes": "hello", }), - withServicePodSpecOption( - withContainer(), - withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}}), - ), + servingtest.WithEnv(corev1.EnvVar{Name: "a", Value: "mouse"}), + ), + ), + test.WithService(test.BuildServiceWithOptions("hello", + servingtest.WithConfigSpec(test.BuildConfigurationSpec()), + test.WithTrafficSpec([]string{"hello-rev2"}, []int{100}, []string{""}), + servingtest.WithBYORevisionName("hello-rev3"), + servingtest.WithEnv(corev1.EnvVar{Name: "a", Value: "mouse"}, corev1.EnvVar{Name: "b", Value: "cat"}), ), - withServices( - withServiceName("hello"), - withServiceRevisionName("hello-rev3"), - withTrafficSplit([]string{"hello-rev2"}, []int{100}, []string{""}), - withServicePodSpecOption( - withContainer(), - withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}, {Name: "b", Value: "cat"}}), - ), ), ), "--with-revisions", "--mode", "replay", "-o", "yaml") t.Log("export revisions-only - all traffic to revision 2") - serviceExportWithRevisionList(r, "hello", getServiceWithOptions( - withServiceName("hello"), - withServiceRevisionName("hello-rev3"), - withTrafficSplit([]string{"hello-rev2"}, []int{100}, []string{""}), - withServicePodSpecOption( - withContainer(), - withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}, {Name: "b", Value: "cat"}}), - ), - ), getKNExportWithOptions( - withRevisions( - withRevisionName("hello-rev2"), - withRevisionAnnotations(map[string]string{ - "serving.knative.dev/routes": "hello", - }), - withRevisionLabels( - map[string]string{ - "serving.knative.dev/configuration": "hello", - "serving.knative.dev/configurationGeneration": "2", - "serving.knative.dev/routingState": "active", - "serving.knative.dev/service": "hello", - }), - withRevisionPodSpecOption( - withContainer(), - withEnv([]corev1.EnvVar{{Name: "a", Value: "mouse"}}), - ), - ), + serviceExportWithRevisionList(r, "hello", test.BuildServiceWithOptions("hello", + servingtest.WithConfigSpec(test.BuildConfigurationSpec()), + servingtest.WithBYORevisionName("hello-rev3"), + test.WithTrafficSpec([]string{"hello-rev2"}, []int{100}, []string{""}), + servingtest.WithEnv(corev1.EnvVar{Name: "a", Value: "mouse"}, corev1.EnvVar{Name: "b", Value: "cat"}), + ), test.BuildKNExportWithOptions( + test.WithKNRevision(*(test.BuildRevision("hello-rev2", + servingtest.WithRevisionAnn("serving.knative.dev/routes", "hello"), + servingtest.WithRevisionLabel("serving.knative.dev/configuration", "hello"), + servingtest.WithRevisionLabel("serving.knative.dev/configurationGeneration", "2"), + servingtest.WithRevisionLabel("serving.knative.dev/routingState", "active"), + servingtest.WithRevisionLabel("serving.knative.dev/service", "hello"), + test.WithRevisionImage(pkgtest.ImagePath("helloworld")), + test.WithRevisionEnv(corev1.EnvVar{Name: "a", Value: "mouse"}), + ))), ), "--with-revisions", "--mode", "export", "-o", "yaml") } // Private methods -func serviceExport(r *test.KnRunResultCollector, serviceName string, expService servingv1.Service, options ...string) { +func serviceExport(r *test.KnRunResultCollector, serviceName string, expService *servingv1.Service, options ...string) { command := []string{"service", "export", serviceName} command = append(command, options...) out := r.KnTest().Kn().Run(command...) @@ -319,7 +238,7 @@ func serviceExport(r *test.KnRunResultCollector, serviceName string, expService r.AssertNoError(out) } -func serviceExportWithServiceList(r *test.KnRunResultCollector, serviceName string, expServiceList servingv1.ServiceList, options ...string) { +func serviceExportWithServiceList(r *test.KnRunResultCollector, serviceName string, expServiceList *servingv1.ServiceList, options ...string) { command := []string{"service", "export", serviceName} command = append(command, options...) out := r.KnTest().Kn().Run(command...) @@ -327,7 +246,7 @@ func serviceExportWithServiceList(r *test.KnRunResultCollector, serviceName stri r.AssertNoError(out) } -func serviceExportWithRevisionList(r *test.KnRunResultCollector, serviceName string, expService servingv1.Service, knExport clientv1alpha1.Export, options ...string) { +func serviceExportWithRevisionList(r *test.KnRunResultCollector, serviceName string, expService *servingv1.Service, knExport *clientv1alpha1.Export, options ...string) { command := []string{"service", "export", serviceName} command = append(command, options...) out := r.KnTest().Kn().Run(command...) @@ -337,195 +256,25 @@ func serviceExportWithRevisionList(r *test.KnRunResultCollector, serviceName str // Private functions -func validateExportedService(t *testing.T, it *test.KnTest, out string, expService servingv1.Service) { +func validateExportedService(t *testing.T, it *test.KnTest, out string, expService *servingv1.Service) { actSvc := servingv1.Service{} err := json.Unmarshal([]byte(out), &actSvc) assert.NilError(t, err) - assert.DeepEqual(t, &expService, &actSvc) + assert.DeepEqual(t, expService, &actSvc) } -func validateExportedServiceList(t *testing.T, it *test.KnTest, out string, expServiceList servingv1.ServiceList) { +func validateExportedServiceList(t *testing.T, it *test.KnTest, out string, expServiceList *servingv1.ServiceList) { actSvcList := servingv1.ServiceList{} err := yaml.Unmarshal([]byte(out), &actSvcList) assert.NilError(t, err) - assert.DeepEqual(t, &expServiceList, &actSvcList) + assert.DeepEqual(t, expServiceList, &actSvcList) } -func validateExportedServiceandRevisionList(t *testing.T, it *test.KnTest, out string, expService servingv1.Service, knExport clientv1alpha1.Export) { +func validateExportedServiceandRevisionList(t *testing.T, it *test.KnTest, out string, expService *servingv1.Service, knExport *clientv1alpha1.Export) { actSvc := clientv1alpha1.Export{} err := yaml.Unmarshal([]byte(out), &actSvc) assert.NilError(t, err) - knExport.Spec.Service = expService - assert.DeepEqual(t, &knExport, &actSvc) -} - -func getServiceListWithOptions(options ...expectedServiceListOption) servingv1.ServiceList { - list := servingv1.ServiceList{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "List", - }, - } - - for _, fn := range options { - fn(&list) - } - return list -} - -func withServices(options ...expectedServiceOption) expectedServiceListOption { - return func(list *servingv1.ServiceList) { - list.Items = append(list.Items, getServiceWithOptions(options...)) - } -} - -func getKNExportWithOptions(options ...expectedKNExportOption) clientv1alpha1.Export { - knExport := clientv1alpha1.Export{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "client.knative.dev/v1alpha1", - Kind: "Export", - }, - } - - for _, fn := range options { - fn(&knExport) - } - - return knExport -} - -func withRevisions(options ...expectedRevisionOption) expectedKNExportOption { - return func(export *clientv1alpha1.Export) { - export.Spec.Revisions = append(export.Spec.Revisions, getRevisionWithOptions(options...)) - } -} - -func getServiceWithOptions(options ...expectedServiceOption) servingv1.Service { - svc := servingv1.Service{ - TypeMeta: metav1.TypeMeta{ - Kind: "Service", - APIVersion: "serving.knative.dev/v1", - }, - } - - for _, fn := range options { - fn(&svc) - } - svc.Spec.Template.Spec.ContainerConcurrency = ptr.Int64(int64(0)) - svc.Spec.Template.Spec.TimeoutSeconds = ptr.Int64(int64(300)) - - return svc -} -func withServiceName(name string) expectedServiceOption { - return func(svc *servingv1.Service) { - svc.ObjectMeta.Name = name - } -} -func withConfigurationAnnotations(annotations map[string]string) expectedServiceOption { - return func(svc *servingv1.Service) { - svc.Spec.Template.ObjectMeta.Annotations = annotations - } -} -func withServiceRevisionName(name string) expectedServiceOption { - return func(svc *servingv1.Service) { - svc.Spec.Template.ObjectMeta.Name = name - } -} -func withTrafficSplit(revisions []string, percentages []int, tags []string) expectedServiceOption { - return func(svc *servingv1.Service) { - var trafficTargets []servingv1.TrafficTarget - for i, rev := range revisions { - trafficTargets = append(trafficTargets, servingv1.TrafficTarget{ - Percent: ptr.Int64(int64(percentages[i])), - }) - if tags[i] != "" { - trafficTargets[i].Tag = tags[i] - } - if rev == "latest" { - trafficTargets[i].LatestRevision = ptr.Bool(true) - } else { - trafficTargets[i].RevisionName = rev - trafficTargets[i].LatestRevision = ptr.Bool(false) - } - } - svc.Spec.RouteSpec = servingv1.RouteSpec{ - Traffic: trafficTargets, - } - } -} -func withServicePodSpecOption(options ...podSpecOption) expectedServiceOption { - return func(svc *servingv1.Service) { - svc.Spec.Template.Spec.PodSpec = getPodSpecWithOptions(options...) - } -} -func getRevisionWithOptions(options ...expectedRevisionOption) servingv1.Revision { - rev := servingv1.Revision{ - TypeMeta: metav1.TypeMeta{ - Kind: "Revision", - APIVersion: "serving.knative.dev/v1", - }, - } - for _, fn := range options { - fn(&rev) - } - rev.Spec.ContainerConcurrency = ptr.Int64(int64(0)) - rev.Spec.TimeoutSeconds = ptr.Int64(int64(300)) - return rev -} -func withRevisionName(name string) expectedRevisionOption { - return func(rev *servingv1.Revision) { - rev.ObjectMeta.Name = name - } -} -func withRevisionLabels(labels map[string]string) expectedRevisionOption { - return func(rev *servingv1.Revision) { - rev.ObjectMeta.Labels = labels - } -} -func withRevisionAnnotations(Annotations map[string]string) expectedRevisionOption { - return func(rev *servingv1.Revision) { - rev.ObjectMeta.Annotations = Annotations - } -} -func withRevisionPodSpecOption(options ...podSpecOption) expectedRevisionOption { - return func(rev *servingv1.Revision) { - rev.Spec.PodSpec = getPodSpecWithOptions(options...) - } -} - -func getPodSpecWithOptions(options ...podSpecOption) corev1.PodSpec { - spec := corev1.PodSpec{} - for _, fn := range options { - fn(&spec) - } - // Service links are disabled by default now see https://github.com/knative/serving/pull/9685 - spec.EnableServiceLinks = ptr.Bool(false) - return spec -} - -func withEnv(env []corev1.EnvVar) podSpecOption { - return func(spec *corev1.PodSpec) { - spec.Containers[0].Env = env - } -} - -func withContainer() podSpecOption { - return func(spec *corev1.PodSpec) { - spec.Containers = []corev1.Container{ - { - Name: "user-container", - Image: pkgtest.ImagePath("helloworld"), - Resources: corev1.ResourceRequirements{}, - ReadinessProbe: &corev1.Probe{ - SuccessThreshold: int32(1), - Handler: corev1.Handler{ - TCPSocket: &corev1.TCPSocketAction{ - Port: intstr.FromInt(0), - }, - }, - }, - }, - } - } + knExport.Spec.Service = *expService + assert.DeepEqual(t, knExport, &actSvc) } diff --git a/vendor/knative.dev/serving/pkg/gc/config.go b/vendor/knative.dev/serving/pkg/gc/config.go new file mode 100644 index 000000000..97fe2b9ae --- /dev/null +++ b/vendor/knative.dev/serving/pkg/gc/config.go @@ -0,0 +1,174 @@ +/* +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 gc + +import ( + "context" + "fmt" + "strconv" + "strings" + "time" + + corev1 "k8s.io/api/core/v1" + cm "knative.dev/pkg/configmap" + "knative.dev/pkg/controller" + "knative.dev/pkg/logging" +) + +const ( + // ConfigName is the name of the config map for garbage collection. + ConfigName = "config-gc" + + // Disabled is the value (-1) used by various config map values to indicate + // the setting is disabled. + Disabled = -1 + + disabled = "disabled" +) + +// Config defines the tunable parameters for Garbage Collection. +type Config struct { + // Delay duration after a revision create before considering it for GC + StaleRevisionCreateDelay time.Duration + // Timeout since a revision lastPinned before it should be GC'd + // This must be longer than the controller resync period + StaleRevisionTimeout time.Duration + // Minimum number of generations of revisions to keep before considering for GC. + StaleRevisionMinimumGenerations int64 + // Minimum staleness duration before updating lastPinned + StaleRevisionLastpinnedDebounce time.Duration + + // Duration from creation when a Revision should be considered active + // and exempt from GC. Note that GCMaxStaleRevision may override this if set. + // Set Disabled (-1) to disable/ignore duration and always consider active. + RetainSinceCreateTime time.Duration + // Duration from last active when a Revision should be considered active + // and exempt from GC.Note that GCMaxStaleRevision may override this if set. + // Set Disabled (-1) to disable/ignore duration and always consider active. + RetainSinceLastActiveTime time.Duration + // Minimum number of non-active revisions to keep before considering for GC. + MinNonActiveRevisions int64 + // Maximum number of non-active revisions to keep before considering for GC. + // regardless of creation or staleness time-bounds. + // Set Disabled (-1) to disable/ignore max. + MaxNonActiveRevisions int64 +} + +func defaultConfig() *Config { + return &Config{ + // V1 GC Settings + StaleRevisionCreateDelay: 48 * time.Hour, + StaleRevisionTimeout: 15 * time.Hour, + StaleRevisionLastpinnedDebounce: 5 * time.Hour, + StaleRevisionMinimumGenerations: 20, + + // V2 GC Settings + RetainSinceCreateTime: 48 * time.Hour, + RetainSinceLastActiveTime: 15 * time.Hour, + MinNonActiveRevisions: 20, + MaxNonActiveRevisions: 1000, + } +} + +// NewConfigFromConfigMapFunc creates a Config from the supplied ConfigMap func. +func NewConfigFromConfigMapFunc(ctx context.Context) func(configMap *corev1.ConfigMap) (*Config, error) { + logger := logging.FromContext(ctx) + minRevisionTimeout := controller.GetResyncPeriod(ctx) + return func(configMap *corev1.ConfigMap) (*Config, error) { + c := defaultConfig() + + var retainCreate, retainActive, max string + if err := cm.Parse(configMap.Data, + cm.AsDuration("stale-revision-create-delay", &c.StaleRevisionCreateDelay), + cm.AsDuration("stale-revision-timeout", &c.StaleRevisionTimeout), + cm.AsDuration("stale-revision-lastpinned-debounce", &c.StaleRevisionLastpinnedDebounce), + cm.AsInt64("stale-revision-minimum-generations", &c.StaleRevisionMinimumGenerations), + + // v2 settings + cm.AsString("retain-since-create-time", &retainCreate), + cm.AsString("retain-since-last-active-time", &retainActive), + cm.AsInt64("min-non-active-revisions", &c.MinNonActiveRevisions), + cm.AsString("max-non-active-revisions", &max), + ); err != nil { + return nil, fmt.Errorf("failed to parse data: %w", err) + } + + if c.StaleRevisionMinimumGenerations < 0 { + return nil, fmt.Errorf( + "stale-revision-minimum-generations must be non-negative, was: %d", + c.StaleRevisionMinimumGenerations) + } + if c.StaleRevisionTimeout-c.StaleRevisionLastpinnedDebounce < minRevisionTimeout { + logger.Warnf("Got revision timeout of %v, minimum supported value is %v", + c.StaleRevisionTimeout, minRevisionTimeout+c.StaleRevisionLastpinnedDebounce) + c.StaleRevisionTimeout = minRevisionTimeout + c.StaleRevisionLastpinnedDebounce + } + + // validate V2 settings + if err := parseDisabledOrDuration(retainCreate, &c.RetainSinceCreateTime); err != nil { + return nil, fmt.Errorf("failed to parse retain-since-create-time: %w", err) + } + if err := parseDisabledOrDuration(retainActive, &c.RetainSinceLastActiveTime); err != nil { + return nil, fmt.Errorf("failed to parse retain-since-last-active-time: %w", err) + } + if err := parseDisabledOrInt64(max, &c.MaxNonActiveRevisions); err != nil { + return nil, fmt.Errorf("failed to parse max-stale-revisions: %w", err) + } + if c.MinNonActiveRevisions < 0 { + return nil, fmt.Errorf("min-non-active-revisions must be non-negative, was: %d", c.MinNonActiveRevisions) + } + if c.MaxNonActiveRevisions >= 0 && c.MinNonActiveRevisions > c.MaxNonActiveRevisions { + return nil, fmt.Errorf("min-non-active-revisions(%d) must be <= max-stale-revisions(%d)", c.MinNonActiveRevisions, c.MaxNonActiveRevisions) + } + return c, nil + } +} + +func parseDisabledOrInt64(val string, toSet *int64) error { + switch { + case val == "": + // keep default value + case strings.EqualFold(val, disabled): + *toSet = Disabled + default: + parsed, err := strconv.ParseUint(val, 10, 64) + if err != nil { + return err + } + *toSet = int64(parsed) + } + return nil +} + +func parseDisabledOrDuration(val string, toSet *time.Duration) error { + switch { + case val == "": + // keep default value + case strings.EqualFold(val, disabled): + *toSet = time.Duration(Disabled) + default: + parsed, err := time.ParseDuration(val) + if err != nil { + return err + } + if parsed < 0 { + return fmt.Errorf("must be non-negative") + } + *toSet = parsed + } + return nil +} diff --git a/vendor/knative.dev/serving/pkg/gc/doc.go b/vendor/knative.dev/serving/pkg/gc/doc.go new file mode 100644 index 000000000..33d94938e --- /dev/null +++ b/vendor/knative.dev/serving/pkg/gc/doc.go @@ -0,0 +1,20 @@ +/* +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. +*/ + +// +k8s:deepcopy-gen=package + +// Package gc manages garbage collection of old resources. +package gc diff --git a/vendor/knative.dev/serving/pkg/gc/zz_generated.deepcopy.go b/vendor/knative.dev/serving/pkg/gc/zz_generated.deepcopy.go new file mode 100644 index 000000000..9a3151e12 --- /dev/null +++ b/vendor/knative.dev/serving/pkg/gc/zz_generated.deepcopy.go @@ -0,0 +1,37 @@ +// +build !ignore_autogenerated + +/* +Copyright 2020 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package gc + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Config) DeepCopyInto(out *Config) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Config. +func (in *Config) DeepCopy() *Config { + if in == nil { + return nil + } + out := new(Config) + in.DeepCopyInto(out) + return out +} diff --git a/vendor/knative.dev/serving/pkg/reconciler/route/config/doc.go b/vendor/knative.dev/serving/pkg/reconciler/route/config/doc.go new file mode 100644 index 000000000..25512a648 --- /dev/null +++ b/vendor/knative.dev/serving/pkg/reconciler/route/config/doc.go @@ -0,0 +1,21 @@ +/* +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. +*/ + +// +k8s:deepcopy-gen=package + +// Package config holds the typed objects that define the schemas for +// assorted ConfigMap objects on which the Route controller depends. +package config diff --git a/vendor/knative.dev/serving/pkg/reconciler/route/config/domain.go b/vendor/knative.dev/serving/pkg/reconciler/route/config/domain.go new file mode 100644 index 000000000..1ffb0298d --- /dev/null +++ b/vendor/knative.dev/serving/pkg/reconciler/route/config/domain.go @@ -0,0 +1,118 @@ +/* +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 config + +import ( + "strings" + + "github.com/ghodss/yaml" + corev1 "k8s.io/api/core/v1" + + netpkg "knative.dev/networking/pkg" + "knative.dev/pkg/configmap" + "knative.dev/pkg/network" + "knative.dev/serving/pkg/apis/serving" +) + +const ( + // DomainConfigName is the config map name for the domain configuration. + DomainConfigName = "config-domain" + // DefaultDomain holds the domain that Route's live under by default + // when no label selector-based options apply. + DefaultDomain = "example.com" +) + +// LabelSelector represents map of {key,value} pairs. A single {key,value} in the +// map is equivalent to a requirement key == value. The requirements are ANDed. +type LabelSelector struct { + Selector map[string]string `json:"selector,omitempty"` +} + +func (s *LabelSelector) specificity() int { + return len(s.Selector) +} + +// Matches returns whether the given labels meet the requirement of the selector. +func (s *LabelSelector) Matches(labels map[string]string) bool { + for label, expectedValue := range s.Selector { + value, ok := labels[label] + if !ok || expectedValue != value { + return false + } + } + return true +} + +// Domain maps domains to routes by matching the domain's +// label selectors to the route's labels. +type Domain struct { + // Domains map from domain to label selector. If a route has + // labels matching a particular selector, it will use the + // corresponding domain. If multiple selectors match, we choose + // the most specific selector. + Domains map[string]*LabelSelector +} + +// NewDomainFromConfigMap creates a Domain from the supplied ConfigMap +func NewDomainFromConfigMap(configMap *corev1.ConfigMap) (*Domain, error) { + c := Domain{Domains: map[string]*LabelSelector{}} + hasDefault := false + for k, v := range configMap.Data { + if k == configmap.ExampleKey { + continue + } + labelSelector := &LabelSelector{} + err := yaml.Unmarshal([]byte(v), labelSelector) + if err != nil { + return nil, err + } + c.Domains[k] = labelSelector + if len(labelSelector.Selector) == 0 { + hasDefault = true + } + } + if !hasDefault { + c.Domains[DefaultDomain] = &LabelSelector{} + } + return &c, nil +} + +// LookupDomainForLabels returns a domain given a set of labels. +// Since we reject configuration without a default domain, this should +// always return a value. +func (c *Domain) LookupDomainForLabels(labels map[string]string) string { + domain := "" + specificity := -1 + // If we see VisibilityLabelKey sets with VisibilityClusterLocal, that + // will take precedence and the route will get a Cluster's Domain Name. + if labels[netpkg.VisibilityLabelKey] == serving.VisibilityClusterLocal || + labels[serving.VisibilityLabelKeyObsolete] == serving.VisibilityClusterLocal { + return "svc." + network.GetClusterDomainName() + } + for k, selector := range c.Domains { + // Ignore if selector doesn't match, or decrease the specificity. + if !selector.Matches(labels) || selector.specificity() < specificity { + continue + } + if selector.specificity() > specificity || strings.Compare(k, domain) < 0 { + domain = k + specificity = selector.specificity() + } + } + + return domain +} diff --git a/vendor/knative.dev/serving/pkg/reconciler/route/config/store.go b/vendor/knative.dev/serving/pkg/reconciler/route/config/store.go new file mode 100644 index 000000000..82d5f8621 --- /dev/null +++ b/vendor/knative.dev/serving/pkg/reconciler/route/config/store.go @@ -0,0 +1,121 @@ +/* +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 config + +import ( + "context" + + network "knative.dev/networking/pkg" + "knative.dev/pkg/configmap" + "knative.dev/pkg/logging" + cfgmap "knative.dev/serving/pkg/apis/config" + "knative.dev/serving/pkg/gc" +) + +type cfgKey struct{} + +// Config is the configuration for the route reconciler. +// +k8s:deepcopy-gen=false +type Config struct { + Domain *Domain + GC *gc.Config + Network *network.Config + Features *cfgmap.Features +} + +// FromContext obtains a Config injected into the passed context. +func FromContext(ctx context.Context) *Config { + return ctx.Value(cfgKey{}).(*Config) +} + +// FromContextOrDefaults is like FromContext, but when no Config is attached it +// returns a Config populated with the defaults for each of the Config fields. +func FromContextOrDefaults(ctx context.Context) *Config { + cfg := FromContext(ctx) + if cfg == nil { + cfg = &Config{} + } + + if cfg.Features == nil { + cfg.Features, _ = cfgmap.NewFeaturesConfigFromMap(map[string]string{}) + } + + return cfg +} + +// ToContext stores the configuration Config in the passed context. +func ToContext(ctx context.Context, c *Config) context.Context { + return context.WithValue(ctx, cfgKey{}, c) +} + +// Store is based on configmap.UntypedStore and is used to store and watch for +// updates to configuration related to routes (currently only config-domain). +// +// +k8s:deepcopy-gen=false +type Store struct { + *configmap.UntypedStore +} + +// NewStore creates a configmap.UntypedStore based config store. +// +// logger must be non-nil implementation of configmap.Logger (commonly used +// loggers conform) +// +// onAfterStore is a variadic list of callbacks to run +// after the ConfigMap has been processed and stored. +// +// See also: configmap.NewUntypedStore(). +func NewStore(ctx context.Context, onAfterStore ...func(name string, value interface{})) *Store { + logger := logging.FromContext(ctx) + + store := &Store{ + UntypedStore: configmap.NewUntypedStore( + "route", + logger, + configmap.Constructors{ + DomainConfigName: NewDomainFromConfigMap, + gc.ConfigName: gc.NewConfigFromConfigMapFunc(ctx), + network.ConfigName: network.NewConfigFromConfigMap, + cfgmap.FeaturesConfigName: cfgmap.NewFeaturesConfigFromConfigMap, + }, + onAfterStore..., + ), + } + + return store +} + +// ToContext stores the configuration Store in the passed context. +func (s *Store) ToContext(ctx context.Context) context.Context { + return ToContext(ctx, s.Load()) +} + +// Load creates a Config for this store. +func (s *Store) Load() *Config { + config := &Config{ + Domain: s.UntypedLoad(DomainConfigName).(*Domain).DeepCopy(), + GC: s.UntypedLoad(gc.ConfigName).(*gc.Config).DeepCopy(), + Network: s.UntypedLoad(network.ConfigName).(*network.Config).DeepCopy(), + Features: nil, + } + + if featureConfig := s.UntypedLoad(cfgmap.FeaturesConfigName); featureConfig != nil { + config.Features = featureConfig.(*cfgmap.Features).DeepCopy() + } + + return config +} diff --git a/vendor/knative.dev/serving/pkg/reconciler/route/config/zz_generated.deepcopy.go b/vendor/knative.dev/serving/pkg/reconciler/route/config/zz_generated.deepcopy.go new file mode 100644 index 000000000..86b30c499 --- /dev/null +++ b/vendor/knative.dev/serving/pkg/reconciler/route/config/zz_generated.deepcopy.go @@ -0,0 +1,75 @@ +// +build !ignore_autogenerated + +/* +Copyright 2020 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package config + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Domain) DeepCopyInto(out *Domain) { + *out = *in + if in.Domains != nil { + in, out := &in.Domains, &out.Domains + *out = make(map[string]*LabelSelector, len(*in)) + for key, val := range *in { + var outVal *LabelSelector + if val == nil { + (*out)[key] = nil + } else { + in, out := &val, &outVal + *out = new(LabelSelector) + (*in).DeepCopyInto(*out) + } + (*out)[key] = outVal + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Domain. +func (in *Domain) DeepCopy() *Domain { + if in == nil { + return nil + } + out := new(Domain) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LabelSelector) DeepCopyInto(out *LabelSelector) { + *out = *in + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LabelSelector. +func (in *LabelSelector) DeepCopy() *LabelSelector { + if in == nil { + return nil + } + out := new(LabelSelector) + in.DeepCopyInto(out) + return out +} diff --git a/vendor/knative.dev/serving/pkg/reconciler/route/domains/doc.go b/vendor/knative.dev/serving/pkg/reconciler/route/domains/doc.go new file mode 100644 index 000000000..a61e0a9a6 --- /dev/null +++ b/vendor/knative.dev/serving/pkg/reconciler/route/domains/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2019 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 domains holds simple functions for generating domains. +package domains diff --git a/vendor/knative.dev/serving/pkg/reconciler/route/domains/domains.go b/vendor/knative.dev/serving/pkg/reconciler/route/domains/domains.go new file mode 100644 index 000000000..a1cc7fa10 --- /dev/null +++ b/vendor/knative.dev/serving/pkg/reconciler/route/domains/domains.go @@ -0,0 +1,134 @@ +/* +Copyright 2019 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 domains + +import ( + "bytes" + "context" + "fmt" + "strings" + "text/template" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + network "knative.dev/networking/pkg" + netv1alpha1 "knative.dev/networking/pkg/apis/networking/v1alpha1" + "knative.dev/pkg/apis" + pkgnet "knative.dev/pkg/network" + "knative.dev/serving/pkg/apis/serving" + v1 "knative.dev/serving/pkg/apis/serving/v1" + "knative.dev/serving/pkg/reconciler/route/config" + "knative.dev/serving/pkg/reconciler/route/resources/labels" +) + +// HTTPScheme is the string representation of http. +const HTTPScheme string = "http" + +// GetAllDomainsAndTags returns all of the domains and tags(including subdomains) associated with a Route +func GetAllDomainsAndTags(ctx context.Context, r *v1.Route, names []string, visibility map[string]netv1alpha1.IngressVisibility) (map[string]string, error) { + domainTagMap := make(map[string]string) + + for _, name := range names { + meta := r.ObjectMeta.DeepCopy() + + hostname, err := HostnameFromTemplate(ctx, meta.Name, name) + if err != nil { + return nil, err + } + + labels.SetVisibility(meta, visibility[name] == netv1alpha1.IngressVisibilityClusterLocal) + + subDomain, err := DomainNameFromTemplate(ctx, *meta, hostname) + if err != nil { + return nil, err + } + domainTagMap[subDomain] = name + } + return domainTagMap, nil +} + +// DomainNameFromTemplate generates domain name base on the template specified in the `config-network` ConfigMap. +// name is the "subdomain" which will be referred as the "name" in the template +func DomainNameFromTemplate(ctx context.Context, r metav1.ObjectMeta, name string) (string, error) { + domainConfig := config.FromContext(ctx).Domain + rLabels := r.Labels + domain := domainConfig.LookupDomainForLabels(rLabels) + annotations := r.Annotations + // These are the available properties they can choose from. + // We could add more over time - e.g. RevisionName if we thought that + // might be of interest to people. + data := network.DomainTemplateValues{ + Name: name, + Namespace: r.Namespace, + Domain: domain, + Annotations: annotations, + Labels: rLabels, + } + + networkConfig := config.FromContext(ctx).Network + buf := bytes.Buffer{} + + var templ *template.Template + // If the route is "cluster local" then don't use the user-defined + // domain template, use the default one + if rLabels[network.VisibilityLabelKey] == serving.VisibilityClusterLocal || + rLabels[serving.VisibilityLabelKeyObsolete] == serving.VisibilityClusterLocal { + templ = template.Must(template.New("domain-template").Parse( + network.DefaultDomainTemplate)) + } else { + templ = networkConfig.GetDomainTemplate() + } + + if err := templ.Execute(&buf, data); err != nil { + return "", fmt.Errorf("error executing the DomainTemplate: %w", err) + } + return buf.String(), nil +} + +// HostnameFromTemplate generates domain name base on the template specified in the `config-network` ConfigMap. +// name is the "subdomain" which will be referred as the "name" in the template +func HostnameFromTemplate(ctx context.Context, name, tag string) (string, error) { + if tag == "" { + return name, nil + } + // These are the available properties they can choose from. + // We could add more over time - e.g. RevisionName if we thought that + // might be of interest to people. + data := network.TagTemplateValues{ + Name: name, + Tag: tag, + } + + networkConfig := config.FromContext(ctx).Network + buf := bytes.Buffer{} + if err := networkConfig.GetTagTemplate().Execute(&buf, data); err != nil { + return "", fmt.Errorf("error executing the TagTemplate: %w", err) + } + return buf.String(), nil +} + +// URL generates the a string representation of a URL. +func URL(scheme, fqdn string) *apis.URL { + return &apis.URL{ + Scheme: scheme, + Host: fqdn, + } +} + +// IsClusterLocal checks if a domain is only visible with cluster. +func IsClusterLocal(domain string) bool { + return strings.HasSuffix(domain, pkgnet.GetClusterDomainName()) +} diff --git a/vendor/knative.dev/serving/pkg/reconciler/route/resources/labels/doc.go b/vendor/knative.dev/serving/pkg/reconciler/route/resources/labels/doc.go new file mode 100644 index 000000000..49e42e5bb --- /dev/null +++ b/vendor/knative.dev/serving/pkg/reconciler/route/resources/labels/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2019 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 labels holds simple functions for working with ObjectMeta labels. +package labels diff --git a/vendor/knative.dev/serving/pkg/reconciler/route/resources/labels/labels.go b/vendor/knative.dev/serving/pkg/reconciler/route/resources/labels/labels.go new file mode 100644 index 000000000..865360f4e --- /dev/null +++ b/vendor/knative.dev/serving/pkg/reconciler/route/resources/labels/labels.go @@ -0,0 +1,51 @@ +/* +Copyright 2019 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 labels + +import ( + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + network "knative.dev/networking/pkg" + "knative.dev/serving/pkg/apis/serving" +) + +// IsObjectLocalVisibility returns whether an ObjectMeta is of cluster-local visibility +func IsObjectLocalVisibility(meta *v1.ObjectMeta) bool { + return meta.Labels[network.VisibilityLabelKey] != "" || meta.Labels[serving.VisibilityLabelKeyObsolete] != "" +} + +// SetVisibility sets the visibility on an ObjectMeta +func SetVisibility(meta *v1.ObjectMeta, isClusterLocal bool) { + if isClusterLocal { + SetLabel(meta, network.VisibilityLabelKey, serving.VisibilityClusterLocal) + } else { + DeleteLabel(meta, network.VisibilityLabelKey) + } +} + +// SetLabel sets/update the label of the an ObjectMeta +func SetLabel(meta *v1.ObjectMeta, key, value string) { + if meta.Labels == nil { + meta.Labels = make(map[string]string, 1) + } + + meta.Labels[key] = value +} + +// DeleteLabel removes a label from the ObjectMeta +func DeleteLabel(meta *v1.ObjectMeta, key string) { + delete(meta.Labels, key) +} diff --git a/vendor/knative.dev/serving/pkg/reconciler/route/resources/names/doc.go b/vendor/knative.dev/serving/pkg/reconciler/route/resources/names/doc.go new file mode 100644 index 000000000..42a13f3c0 --- /dev/null +++ b/vendor/knative.dev/serving/pkg/reconciler/route/resources/names/doc.go @@ -0,0 +1,18 @@ +/* +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 names holds simple functions for synthesizing resource names. +package names diff --git a/vendor/knative.dev/serving/pkg/reconciler/route/resources/names/names.go b/vendor/knative.dev/serving/pkg/reconciler/route/resources/names/names.go new file mode 100644 index 000000000..32e4abe4c --- /dev/null +++ b/vendor/knative.dev/serving/pkg/reconciler/route/resources/names/names.go @@ -0,0 +1,44 @@ +/* +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 names + +import ( + "knative.dev/pkg/kmeta" + "knative.dev/pkg/network" +) + +// K8sService returns the name of the K8sService for a given route. +func K8sService(route kmeta.Accessor) string { + return route.GetName() +} + +// K8sServiceFullname returns the full name of the K8sService for a given route. +func K8sServiceFullname(route kmeta.Accessor) string { + return network.GetServiceHostname(K8sService(route), route.GetNamespace()) +} + +// Ingress returns the name for the Ingress +// child resource for the given Route. +func Ingress(route kmeta.Accessor) string { + return kmeta.ChildName(route.GetName(), "") +} + +// Certificate returns the name for the Certificate +// child resource for the given Route. +func Certificate(route kmeta.Accessor) string { + return "route-" + string(route.GetUID()) +} diff --git a/vendor/knative.dev/serving/pkg/reconciler/service/resources/names/doc.go b/vendor/knative.dev/serving/pkg/reconciler/service/resources/names/doc.go new file mode 100644 index 000000000..42a13f3c0 --- /dev/null +++ b/vendor/knative.dev/serving/pkg/reconciler/service/resources/names/doc.go @@ -0,0 +1,18 @@ +/* +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 names holds simple functions for synthesizing resource names. +package names diff --git a/vendor/knative.dev/serving/pkg/reconciler/service/resources/names/names.go b/vendor/knative.dev/serving/pkg/reconciler/service/resources/names/names.go new file mode 100644 index 000000000..7f3eeccbf --- /dev/null +++ b/vendor/knative.dev/serving/pkg/reconciler/service/resources/names/names.go @@ -0,0 +1,29 @@ +/* +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 names + +import "knative.dev/pkg/kmeta" + +// Configuration returns a configuration name based on a given service name. +func Configuration(service kmeta.Accessor) string { + return service.GetName() +} + +// Route returns a route name based on a given service name. +func Route(service kmeta.Accessor) string { + return service.GetName() +} diff --git a/vendor/knative.dev/serving/pkg/testing/v1/configuration.go b/vendor/knative.dev/serving/pkg/testing/v1/configuration.go new file mode 100644 index 000000000..0c7d88b21 --- /dev/null +++ b/vendor/knative.dev/serving/pkg/testing/v1/configuration.go @@ -0,0 +1,133 @@ +/* +Copyright 2019 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 v1 + +import ( + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/pkg/kmeta" + "knative.dev/pkg/ptr" + v1 "knative.dev/serving/pkg/apis/serving/v1" +) + +// ConfigOption enables further configuration of a Configuration. +type ConfigOption func(*v1.Configuration) + +// WithConfigReadinessProbe sets the provided probe to be the readiness +// probe on the configuration. +func WithConfigReadinessProbe(p *corev1.Probe) ConfigOption { + return func(cfg *v1.Configuration) { + cfg.Spec.Template.Spec.Containers[0].ReadinessProbe = p + } +} + +// WithConfigImage sets the container image to be the provided string. +func WithConfigImage(img string) ConfigOption { + return func(cfg *v1.Configuration) { + cfg.Spec.Template.Spec.Containers[0].Image = img + } +} + +// WithConfigDeletionTimestamp sets the DeletionTimestamp on the Config. +func WithConfigDeletionTimestamp(r *v1.Configuration) { + t := metav1.NewTime(time.Unix(1e9, 0)) + r.ObjectMeta.SetDeletionTimestamp(&t) +} + +// WithConfigContainerConcurrency sets the given Configuration's concurrency. +func WithConfigContainerConcurrency(cc int64) ConfigOption { + return func(cfg *v1.Configuration) { + cfg.Spec.GetTemplate().Spec.ContainerConcurrency = &cc + } +} + +// WithConfigGeneration sets the generation of the Configuration. +func WithConfigGeneration(gen int64) ConfigOption { + return func(cfg *v1.Configuration) { + cfg.Generation = gen + } +} + +// WithConfigObservedGen sets the observed generation of the Configuration. +func WithConfigObservedGen(cfg *v1.Configuration) { + cfg.Status.ObservedGeneration = cfg.Generation +} + +// WithLatestCreated initializes the .status.latestCreatedRevisionName to be the name +// of the latest revision that the Configuration would have created. +func WithLatestCreated(name string) ConfigOption { + return func(cfg *v1.Configuration) { + cfg.Status.SetLatestCreatedRevisionName(name) + } +} + +// WithLatestReady initializes the .status.latestReadyRevisionName to be the name +// of the latest revision that the Configuration would have created. +func WithLatestReady(name string) ConfigOption { + return func(cfg *v1.Configuration) { + cfg.Status.SetLatestReadyRevisionName(name) + } +} + +// MarkRevisionCreationFailed calls .Status.MarkRevisionCreationFailed. +func MarkRevisionCreationFailed(msg string) ConfigOption { + return func(cfg *v1.Configuration) { + cfg.Status.MarkRevisionCreationFailed(msg) + } +} + +// MarkLatestCreatedFailed calls .Status.MarkLatestCreatedFailed. +func MarkLatestCreatedFailed(msg string) ConfigOption { + return func(cfg *v1.Configuration) { + cfg.Status.MarkLatestCreatedFailed(cfg.Status.LatestCreatedRevisionName, msg) + } +} + +// WithConfigLabel attaches a particular label to the configuration. +func WithConfigLabel(key, value string) ConfigOption { + return func(config *v1.Configuration) { + config.Labels = kmeta.UnionMaps(config.Labels, map[string]string{key: value}) + } +} + +// WithConfigAnn attaches a particular label to the configuration. +func WithConfigAnn(key, value string) ConfigOption { + return func(config *v1.Configuration) { + config.Annotations = kmeta.UnionMaps(config.Annotations, map[string]string{key: value}) + } +} + +// WithConfigOwnersRemoved clears the owner references of this Configuration. +func WithConfigOwnersRemoved(cfg *v1.Configuration) { + cfg.OwnerReferences = nil +} + +// WithConfigEnv configures the Service to use the provided environment variables. +func WithConfigEnv(evs ...corev1.EnvVar) ConfigOption { + return func(c *v1.Configuration) { + c.Spec.Template.Spec.Containers[0].Env = evs + } +} + +// WithConfigRevisionTimeoutSeconds sets revision timeout. +func WithConfigRevisionTimeoutSeconds(revisionTimeoutSeconds int64) ConfigOption { + return func(cfg *v1.Configuration) { + cfg.Spec.Template.Spec.TimeoutSeconds = ptr.Int64(revisionTimeoutSeconds) + } +} diff --git a/vendor/knative.dev/serving/pkg/testing/v1/revision.go b/vendor/knative.dev/serving/pkg/testing/v1/revision.go new file mode 100644 index 000000000..7bd4f1248 --- /dev/null +++ b/vendor/knative.dev/serving/pkg/testing/v1/revision.go @@ -0,0 +1,269 @@ +/* +Copyright 2020 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 v1 + +import ( + "context" + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/clock" + "knative.dev/pkg/kmeta" + "knative.dev/serving/pkg/apis/serving" + v1 "knative.dev/serving/pkg/apis/serving/v1" +) + +// RevisionOption enables further configuration of a Revision. +type RevisionOption func(*v1.Revision) + +// WithRevisionDeletionTimestamp will set the DeletionTimestamp on the Revision. +func WithRevisionDeletionTimestamp(r *v1.Revision) { + t := metav1.NewTime(time.Unix(1e9, 0)) + r.ObjectMeta.SetDeletionTimestamp(&t) +} + +// WithInitRevConditions calls .Status.InitializeConditions() on a Revision. +func WithInitRevConditions(r *v1.Revision) { + r.Status.InitializeConditions() +} + +// WithRevName sets the name of the revision +func WithRevName(name string) RevisionOption { + return func(rev *v1.Revision) { + rev.Name = name + } +} + +// WithServiceName propagates the given service name to the revision status. +func WithServiceName(sn string) RevisionOption { + return func(rev *v1.Revision) { + rev.Status.ServiceName = sn + } +} + +// MarkResourceNotOwned calls the function of the same name on the Revision's status. +func MarkResourceNotOwned(kind, name string) RevisionOption { + return func(rev *v1.Revision) { + rev.Status.MarkResourcesAvailableFalse( + v1.ReasonNotOwned, + v1.ResourceNotOwnedMessage(kind, name), + ) + } +} + +// WithRevContainerConcurrency sets the given Revision's concurrency. +func WithRevContainerConcurrency(cc int64) RevisionOption { + return func(rev *v1.Revision) { + rev.Spec.ContainerConcurrency = &cc + } +} + +// WithLogURL sets the .Status.LogURL to the expected value. +func WithLogURL(r *v1.Revision) { + r.Status.LogURL = "http://logger.io/test-uid" +} + +// WithCreationTimestamp sets the Revision's timestamp to the provided time. +// TODO(mattmoor): Ideally this could be a more generic Option and use meta.Accessor, +// but unfortunately Go's type system cannot support that. +func WithCreationTimestamp(t time.Time) RevisionOption { + return func(rev *v1.Revision) { + rev.ObjectMeta.CreationTimestamp = metav1.Time{Time: t} + } +} + +// WithLastPinned updates the "last pinned" annotation to the provided timestamp. +func WithLastPinned(t time.Time) RevisionOption { + return func(rev *v1.Revision) { + rev.SetLastPinned(t) + } +} + +// WithRevisionPreserveAnnotation updates the annotation with preserve key. +func WithRevisionPreserveAnnotation() RevisionOption { + return func(rev *v1.Revision) { + rev.Annotations = kmeta.UnionMaps(rev.Annotations, + map[string]string{ + serving.RevisionPreservedAnnotationKey: "true", + }) + } +} + +// WithRoutingStateModified updates the annotation to the provided timestamp. +func WithRoutingStateModified(t time.Time) RevisionOption { + return func(rev *v1.Revision) { + rev.Annotations = kmeta.UnionMaps(rev.Annotations, + map[string]string{ + serving.RoutingStateModifiedAnnotationKey: t.UTC().Format(time.RFC3339), + }) + } +} + +// WithRoutingState updates the annotation to the provided timestamp. +func WithRoutingState(s v1.RoutingState) RevisionOption { + return func(rev *v1.Revision) { + rev.SetRoutingState(s, clock.RealClock{}) + } +} + +// WithRevStatus is a generic escape hatch for creating hard-to-craft +// status orientations. +func WithRevStatus(st v1.RevisionStatus) RevisionOption { + return func(rev *v1.Revision) { + rev.Status = st + } +} + +// WithImagePullSecrets updates the revision spec ImagePullSecrets to +// the provided secrets +func WithImagePullSecrets(secretName string) RevisionOption { + return func(rev *v1.Revision) { + rev.Spec.ImagePullSecrets = []corev1.LocalObjectReference{{ + Name: secretName, + }} + } +} + +// MarkActive calls .Status.MarkActive on the Revision. +func MarkActive(r *v1.Revision) { + r.Status.MarkActiveTrue() +} + +// WithK8sServiceName applies sn to the revision status field. +func WithK8sServiceName(sn string) RevisionOption { + return func(r *v1.Revision) { + r.Status.ServiceName = sn + } +} + +// MarkInactive calls .Status.MarkInactive on the Revision. +func MarkInactive(reason, message string) RevisionOption { + return func(r *v1.Revision) { + r.Status.MarkActiveFalse(reason, message) + } +} + +// MarkActivating calls .Status.MarkActivating on the Revision. +func MarkActivating(reason, message string) RevisionOption { + return func(r *v1.Revision) { + r.Status.MarkActiveUnknown(reason, message) + } +} + +// MarkDeploying calls .Status.MarkDeploying on the Revision. +func MarkDeploying(reason string) RevisionOption { + return func(r *v1.Revision) { + r.Status.MarkResourcesAvailableUnknown(reason, "") + r.Status.MarkContainerHealthyUnknown(reason, "") + } +} + +// MarkProgressDeadlineExceeded calls the method of the same name on the Revision +// with the message we expect the Revision Reconciler to pass. +func MarkProgressDeadlineExceeded(message string) RevisionOption { + return func(r *v1.Revision) { + r.Status.MarkResourcesAvailableFalse( + v1.ReasonProgressDeadlineExceeded, + message, + ) + } +} + +// MarkContainerMissing calls .Status.MarkContainerMissing on the Revision. +func MarkContainerMissing(rev *v1.Revision) { + rev.Status.MarkContainerHealthyFalse(v1.ReasonContainerMissing, "It's the end of the world as we know it") +} + +// MarkContainerExiting calls .Status.MarkContainerExiting on the Revision. +func MarkContainerExiting(exitCode int32, message string) RevisionOption { + return func(r *v1.Revision) { + r.Status.MarkContainerHealthyFalse(v1.ExitCodeReason(exitCode), message) + } +} + +// MarkResourcesUnavailable calls .Status.MarkResourcesUnavailable on the Revision. +func MarkResourcesUnavailable(reason, message string) RevisionOption { + return func(r *v1.Revision) { + r.Status.MarkResourcesAvailableFalse(reason, message) + } +} + +// MarkRevisionReady calls the necessary helpers to make the Revision Ready=True. +func MarkRevisionReady(r *v1.Revision) { + WithInitRevConditions(r) + MarkActive(r) + r.Status.MarkResourcesAvailableTrue() + r.Status.MarkContainerHealthyTrue() + r.Status.ObservedGeneration = r.Generation +} + +// WithRevisionLabel attaches a particular label to the revision. +func WithRevisionLabel(key, value string) RevisionOption { + return func(rev *v1.Revision) { + rev.Labels = kmeta.UnionMaps(rev.Labels, map[string]string{key: value}) + } +} + +// WithRevisionAnn attaches a particular label to the revision. +func WithRevisionAnn(key, value string) RevisionOption { + return func(rev *v1.Revision) { + rev.Annotations = kmeta.UnionMaps(rev.Annotations, map[string]string{key: value}) + } +} + +// WithContainerStatuses sets the .Status.ContainerStatuses to the Revision. +func WithContainerStatuses(containerStatus []v1.ContainerStatus) RevisionOption { + return func(r *v1.Revision) { + r.Status.ContainerStatuses = containerStatus + } +} + +// WithRevisionObservedGeneration sets the observed generation on the +// revision status. +func WithRevisionObservedGeneration(gen int64) RevisionOption { + return func(r *v1.Revision) { + r.Status.ObservedGeneration = gen + } +} + +// Revision creates a revision object with given ns/name and options. +func Revision(namespace, name string, ro ...RevisionOption) *v1.Revision { + r := &v1.Revision{ + ObjectMeta: metav1.ObjectMeta{ + SelfLink: "/apis/serving/v1/namespaces/test/revisions/" + name, + Name: name, + Namespace: namespace, + UID: "test-uid", + Generation: 1, + }, + Spec: v1.RevisionSpec{ + PodSpec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: name, + Image: "busybox", + }}, + }, + }, + } + r.SetDefaults(context.Background()) + + for _, opt := range ro { + opt(r) + } + return r +} diff --git a/vendor/knative.dev/serving/pkg/testing/v1/route.go b/vendor/knative.dev/serving/pkg/testing/v1/route.go new file mode 100644 index 000000000..a4c7b1a31 --- /dev/null +++ b/vendor/knative.dev/serving/pkg/testing/v1/route.go @@ -0,0 +1,294 @@ +/* +Copyright 2019 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 v1 + +import ( + "context" + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "knative.dev/networking/pkg/apis/networking" + netv1alpha1 "knative.dev/networking/pkg/apis/networking/v1alpha1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/network" + "knative.dev/pkg/ptr" + v1 "knative.dev/serving/pkg/apis/serving/v1" + routenames "knative.dev/serving/pkg/reconciler/route/resources/names" +) + +// RouteOption enables further configuration of a Route. +type RouteOption func(*v1.Route) + +// WithSpecTraffic sets the Route's traffic block to the specified traffic targets. +func WithSpecTraffic(traffic ...v1.TrafficTarget) RouteOption { + return func(r *v1.Route) { + r.Spec.Traffic = traffic + } +} + +// WithRouteUID sets the Route's UID +func WithRouteUID(uid types.UID) RouteOption { + return func(r *v1.Route) { + r.UID = uid + } +} + +// WithRouteGeneration sets the route's generation +func WithRouteGeneration(generation int64) RouteOption { + return func(r *v1.Route) { + r.Generation = generation + } +} + +// WithRouteObservedGeneration sets the route's observed generation to it's generation +func WithRouteObservedGeneration(r *v1.Route) { + r.Status.ObservedGeneration = r.Generation +} + +// WithRouteFinalizer adds the Route finalizer to the Route. +func WithRouteFinalizer(r *v1.Route) { + r.ObjectMeta.Finalizers = append(r.ObjectMeta.Finalizers, "routes.serving.knative.dev") +} + +// WithRouteDeletionTimestamp adds the Route finalizer to the Route. +func WithRouteDeletionTimestamp(t *metav1.Time) RouteOption { + return func(r *v1.Route) { + r.ObjectMeta.DeletionTimestamp = t + } +} + +// WithConfigTarget sets the Route's traffic block to point at a particular Configuration. +func WithConfigTarget(config string) RouteOption { + return WithSpecTraffic(v1.TrafficTarget{ + ConfigurationName: config, + Percent: ptr.Int64(100), + }) +} + +// WithRevTarget sets the Route's traffic block to point at a particular Revision. +func WithRevTarget(revision string) RouteOption { + return WithSpecTraffic(v1.TrafficTarget{ + RevisionName: revision, + Percent: ptr.Int64(100), + }) +} + +// WithStatusTraffic sets the Route's status traffic block to the specified traffic targets. +func WithStatusTraffic(traffic ...v1.TrafficTarget) RouteOption { + ctx := apis.WithinStatus(context.Background()) + + for _, t := range traffic { + if err := t.Validate(ctx); err != nil { + panic(err) + } + } + + return func(r *v1.Route) { + r.Status.Traffic = traffic + } +} + +// WithRouteOwnersRemoved clears the owner references of this Route. +func WithRouteOwnersRemoved(r *v1.Route) { + r.OwnerReferences = nil +} + +// MarkServiceNotOwned calls the function of the same name on the Service's status. +func MarkServiceNotOwned(r *v1.Route) { + r.Status.MarkServiceNotOwned(routenames.K8sService(r)) +} + +// WithURL sets the .Status.Domain field to the prototypical domain. +func WithURL(r *v1.Route) { + r.Status.URL = &apis.URL{ + Scheme: "http", + Host: fmt.Sprintf("%s.%s.example.com", r.Name, r.Namespace), + } +} + +// WithHTTPSDomain sets the .Status.URL field to a https-domain based on the name and namespace. +func WithHTTPSDomain(r *v1.Route) { + r.Status.URL = &apis.URL{ + Scheme: "https", + Host: fmt.Sprintf("%s.%s.example.com", r.Name, r.Namespace), + } +} + +// WithAddress sets the .Status.Address field to the prototypical internal hostname. +func WithAddress(r *v1.Route) { + r.Status.Address = &duckv1.Addressable{ + URL: &apis.URL{ + Scheme: "http", + Host: network.GetServiceHostname(r.Name, r.Namespace), + }, + } +} + +// WithAnotherDomain sets the .Status.Domain field to an atypical domain. +func WithAnotherDomain(r *v1.Route) { + r.Status.URL = &apis.URL{ + Scheme: "http", + Host: fmt.Sprintf("%s.%s.another-example.com", r.Name, r.Namespace), + } +} + +// WithLocalDomain sets the .Status.Domain field to use ClusterDomain suffix. +func WithLocalDomain(r *v1.Route) { + r.Status.URL = &apis.URL{ + Scheme: "http", + Host: network.GetServiceHostname(r.Name, r.Namespace), + } +} + +// WithInitRouteConditions initializes the Service's conditions. +func WithInitRouteConditions(rt *v1.Route) { + rt.Status.InitializeConditions() +} + +// WithRouteConditionsAutoTLSDisabled calls MarkTLSNotEnabled with AutoTLSNotEnabledMessage +// after initialized the Service's conditions. +func WithRouteConditionsAutoTLSDisabled(rt *v1.Route) { + rt.Status.InitializeConditions() + rt.Status.MarkTLSNotEnabled(v1.AutoTLSNotEnabledMessage) +} + +// WithRouteConditionsTLSNotEnabledForClusterLocalMessage calls +// MarkTLSNotEnabled with TLSNotEnabledForClusterLocalMessage after initialized +// the Service's conditions. +func WithRouteConditionsTLSNotEnabledForClusterLocalMessage(rt *v1.Route) { + rt.Status.InitializeConditions() + rt.Status.MarkTLSNotEnabled(v1.TLSNotEnabledForClusterLocalMessage) +} + +// WithRouteConditionsHTTPDowngrade calls MarkHTTPDowngrade after initialized the Service's conditions. +func WithRouteConditionsHTTPDowngrade(rt *v1.Route) { + rt.Status.InitializeConditions() + rt.Status.MarkHTTPDowngrade(routenames.Certificate(rt)) +} + +// MarkTrafficAssigned calls the method of the same name on .Status +func MarkTrafficAssigned(r *v1.Route) { + r.Status.MarkTrafficAssigned() +} + +// MarkCertificateNotReady calls the method of the same name on .Status +func MarkCertificateNotReady(r *v1.Route) { + r.Status.MarkCertificateNotReady(routenames.Certificate(r)) +} + +// MarkCertificateNotOwned calls the method of the same name on .Status +func MarkCertificateNotOwned(r *v1.Route) { + r.Status.MarkCertificateNotOwned(routenames.Certificate(r)) +} + +// MarkCertificateReady calls the method of the same name on .Status +func MarkCertificateReady(r *v1.Route) { + r.Status.MarkCertificateReady(routenames.Certificate(r)) +} + +// WithReadyCertificateName marks the certificate specified by name as ready. +func WithReadyCertificateName(name string) func(*v1.Route) { + return func(r *v1.Route) { + r.Status.MarkCertificateReady(name) + } +} + +// MarkIngressReady propagates a Ready=True Ingress status to the Route. +func MarkIngressReady(r *v1.Route) { + r.Status.PropagateIngressStatus(netv1alpha1.IngressStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + }) +} + +// MarkIngressNotConfigured calls the method of the same name on .Status +func MarkIngressNotConfigured(r *v1.Route) { + r.Status.MarkIngressNotConfigured() +} + +// WithPropagatedStatus propagates the given IngressStatus into the routes status. +func WithPropagatedStatus(status netv1alpha1.IngressStatus) RouteOption { + return func(r *v1.Route) { + r.Status.PropagateIngressStatus(status) + } +} + +// MarkMissingTrafficTarget calls the method of the same name on .Status +func MarkMissingTrafficTarget(kind, revision string) RouteOption { + return func(r *v1.Route) { + r.Status.MarkMissingTrafficTarget(kind, revision) + } +} + +// MarkConfigurationNotReady calls the method of the same name on .Status +func MarkConfigurationNotReady(name string) RouteOption { + return func(r *v1.Route) { + r.Status.MarkConfigurationNotReady(name) + } +} + +// MarkConfigurationFailed calls the method of the same name on .Status +func MarkConfigurationFailed(name string) RouteOption { + return func(r *v1.Route) { + r.Status.MarkConfigurationFailed(name) + } +} + +// WithRouteLabel sets the specified label on the Route. +func WithRouteLabel(labels map[string]string) RouteOption { + return func(r *v1.Route) { + r.Labels = labels + } +} + +// WithIngressClass sets the ingress class annotation on the Route. +func WithIngressClass(ingressClass string) RouteOption { + return func(r *v1.Route) { + if r.Annotations == nil { + r.Annotations = make(map[string]string, 1) + } + r.Annotations[networking.IngressClassAnnotationKey] = ingressClass + } +} + +// WithRouteAnnotation sets the specified annotation on the Route. +func WithRouteAnnotation(annotations map[string]string) RouteOption { + return func(r *v1.Route) { + r.Annotations = annotations + } +} + +// Route creates a route with RouteOptions +func Route(namespace, name string, ro ...RouteOption) *v1.Route { + r := &v1.Route{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: name, + }, + } + for _, opt := range ro { + opt(r) + } + r.SetDefaults(context.Background()) + return r +} diff --git a/vendor/knative.dev/serving/pkg/testing/v1/service.go b/vendor/knative.dev/serving/pkg/testing/v1/service.go new file mode 100644 index 000000000..734d122f7 --- /dev/null +++ b/vendor/knative.dev/serving/pkg/testing/v1/service.go @@ -0,0 +1,481 @@ +/* +Copyright 2019 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 v1 + +import ( + "context" + "fmt" + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" + "knative.dev/pkg/kmeta" + "knative.dev/pkg/network" + "knative.dev/pkg/ptr" + v1 "knative.dev/serving/pkg/apis/serving/v1" + "knative.dev/serving/pkg/reconciler/route/domains" + servicenames "knative.dev/serving/pkg/reconciler/service/resources/names" +) + +// ServiceOption enables further configuration of a Service. +type ServiceOption func(*v1.Service) + +// Service creates a service with ServiceOptions +func Service(name, namespace string, so ...ServiceOption) *v1.Service { + s := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + } + for _, opt := range so { + opt(s) + } + return s +} + +// DefaultService creates a service with ServiceOptions and with default values set +func DefaultService(name, namespace string, so ...ServiceOption) *v1.Service { + return Service(name, namespace, append(so, WithServiceDefaults)...) +} + +// ServiceWithoutNamespace creates a service with ServiceOptions but without a specific namespace +func ServiceWithoutNamespace(name string, so ...ServiceOption) *v1.Service { + s := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } + for _, opt := range so { + opt(s) + } + return s +} + +// WithInitSvcConditions initializes the Service's conditions. +func WithInitSvcConditions(s *v1.Service) { + s.Status.InitializeConditions() +} + +// WithServiceObservedGenFailure marks the top level condition as unknown when the reconciler +// does not set any condition during reconciliation of a new generation. +func WithServiceObservedGenFailure(s *v1.Service) { + condSet := s.GetConditionSet() + condSet.Manage(&s.Status).MarkUnknown(condSet.GetTopLevelConditionType(), + "NewObservedGenFailure", "unsuccessfully observed a new generation") +} + +// WithConfigSpec configures the Service to use the given config spec +func WithConfigSpec(config *v1.ConfigurationSpec) ServiceOption { + return func(svc *v1.Service) { + svc.Spec.ConfigurationSpec = *config + } +} + +// WithRouteSpec configures the Service to use the given route spec +func WithRouteSpec(route v1.RouteSpec) ServiceOption { + return func(svc *v1.Service) { + svc.Spec.RouteSpec = route + } +} + +// WithNamedPort sets the name on the Service's port to the provided name +func WithNamedPort(name string) ServiceOption { + return func(svc *v1.Service) { + c := &svc.Spec.Template.Spec.Containers[0] + if len(c.Ports) == 1 { + c.Ports[0].Name = name + } else { + c.Ports = []corev1.ContainerPort{{ + Name: name, + }} + } + } +} + +// WithNumberedPort sets the Service's port number to what's provided. +func WithNumberedPort(number int32) ServiceOption { + return func(svc *v1.Service) { + c := &svc.Spec.Template.Spec.Containers[0] + if len(c.Ports) == 1 { + c.Ports[0].ContainerPort = number + } else { + c.Ports = []corev1.ContainerPort{{ + ContainerPort: number, + }} + } + } +} + +// WithServiceDefaults will set the default values on the service. +func WithServiceDefaults(svc *v1.Service) { + svc.SetDefaults(context.Background()) +} + +// WithResourceRequirements attaches resource requirements to the service +func WithResourceRequirements(resourceRequirements corev1.ResourceRequirements) ServiceOption { + return func(svc *v1.Service) { + svc.Spec.Template.Spec.Containers[0].Resources = resourceRequirements + } +} + +// WithServiceAnnotation adds the given annotation to the service. +func WithServiceAnnotation(k, v string) ServiceOption { + return func(svc *v1.Service) { + svc.Annotations = kmeta.UnionMaps(svc.Annotations, map[string]string{ + k: v, + }) + } +} + +// WithServiceAnnotationRemoved adds the given annotation to the service. +func WithServiceAnnotationRemoved(k string) ServiceOption { + return func(svc *v1.Service) { + svc.Annotations = kmeta.FilterMap(svc.Annotations, func(s string) bool { + return k == s + }) + } +} + +// WithServiceImage sets the container image to be the provided string. +func WithServiceImage(img string) ServiceOption { + return func(svc *v1.Service) { + svc.Spec.Template.Spec.Containers[0].Image = img + } +} + +// WithServiceTemplateMeta sets the container image to be the provided string. +func WithServiceTemplateMeta(m metav1.ObjectMeta) ServiceOption { + return func(svc *v1.Service) { + svc.Spec.Template.ObjectMeta = m + } +} + +// WithRevisionTimeoutSeconds sets revision timeout +func WithRevisionTimeoutSeconds(revisionTimeoutSeconds int64) ServiceOption { + return func(service *v1.Service) { + service.Spec.Template.Spec.TimeoutSeconds = ptr.Int64(revisionTimeoutSeconds) + } +} + +// WithServiceAccountName sets revision service account name +func WithServiceAccountName(serviceAccountName string) ServiceOption { + return func(service *v1.Service) { + service.Spec.Template.Spec.ServiceAccountName = serviceAccountName + } +} + +// WithContainerConcurrency sets the given Service's concurrency. +func WithContainerConcurrency(cc int64) ServiceOption { + return func(svc *v1.Service) { + svc.Spec.Template.Spec.ContainerConcurrency = &cc + } +} + +// WithVolume adds a volume to the service +func WithVolume(name, mountPath string, volumeSource corev1.VolumeSource) ServiceOption { + return func(svc *v1.Service) { + rt := &svc.Spec.ConfigurationSpec.Template.Spec + + rt.Containers[0].VolumeMounts = append(rt.Containers[0].VolumeMounts, + corev1.VolumeMount{ + Name: name, + MountPath: mountPath, + }) + + rt.Volumes = append(rt.Volumes, corev1.Volume{ + Name: name, + VolumeSource: volumeSource, + }) + } +} + +// WithEnv configures the Service to use the provided environment variables. +func WithEnv(evs ...corev1.EnvVar) ServiceOption { + return func(s *v1.Service) { + s.Spec.Template.Spec.Containers[0].Env = evs + } +} + +// WithEnvFrom configures the Service to use the provided environment variables. +func WithEnvFrom(evs ...corev1.EnvFromSource) ServiceOption { + return func(s *v1.Service) { + s.Spec.Template.Spec.Containers[0].EnvFrom = evs + } +} + +// WithSecurityContext configures the Service to use the provided security context. +func WithSecurityContext(sc *corev1.SecurityContext) ServiceOption { + return func(s *v1.Service) { + s.Spec.Template.Spec.Containers[0].SecurityContext = sc + } +} + +// WithWorkingDir configures the Service to use the provided working directory. +func WithWorkingDir(wd string) ServiceOption { + return func(s *v1.Service) { + s.Spec.Template.Spec.Containers[0].WorkingDir = wd + } +} + +// WithServiceAnnotations adds the supplied annotations to the Service +func WithServiceAnnotations(annotations map[string]string) ServiceOption { + return func(service *v1.Service) { + service.Annotations = kmeta.UnionMaps(service.Annotations, annotations) + } +} + +// WithConfigAnnotations assigns config annotations to a service +func WithConfigAnnotations(annotations map[string]string) ServiceOption { + return func(service *v1.Service) { + service.Spec.Template.Annotations = kmeta.UnionMaps( + service.Spec.Template.Annotations, annotations) + } +} + +// WithBYORevisionName sets the given name to the config spec +func WithBYORevisionName(name string) ServiceOption { + return func(s *v1.Service) { + s.Spec.Template.Name = name + } +} + +// WithServiceDeletionTimestamp will set the DeletionTimestamp on the Service. +func WithServiceDeletionTimestamp(r *v1.Service) { + t := metav1.NewTime(time.Unix(1e9, 0)) + r.ObjectMeta.SetDeletionTimestamp(&t) +} + +// WithNamedRevision configures the Service to use BYO Revision in the +// template spec and reference that same revision name in the route spec. +func WithNamedRevision(s *v1.Service) { + s.Spec = v1.ServiceSpec{ + ConfigurationSpec: v1.ConfigurationSpec{ + Template: v1.RevisionTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Name: s.Name + "-byo", + }, + Spec: v1.RevisionSpec{ + PodSpec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Image: "busybox", + }}, + }, + TimeoutSeconds: ptr.Int64(60), + }, + }, + }, + RouteSpec: v1.RouteSpec{ + Traffic: []v1.TrafficTarget{{ + RevisionName: s.Name + "-byo", + Percent: ptr.Int64(100), + }}, + }, + } +} + +// WithOutOfDateConfig reflects the Configuration's readiness in the Service +// resource. +func WithOutOfDateConfig(s *v1.Service) { + s.Status.MarkConfigurationNotReconciled() +} + +// WithServiceGeneration sets the service's generation +func WithServiceGeneration(generation int64) ServiceOption { + return func(svc *v1.Service) { + svc.Status.ObservedGeneration = generation + } +} + +// WithServiceLabel attaches a particular label to the service. +func WithServiceLabel(key, value string) ServiceOption { + return func(service *v1.Service) { + if service.Labels == nil { + service.Labels = make(map[string]string, 1) + } + service.Labels[key] = value + } +} + +// WithServiceObservedGeneration sets the service's observed generation to it's generation +func WithServiceObservedGeneration(svc *v1.Service) { + svc.Status.ObservedGeneration = svc.Generation +} + +// MarkRevisionNameTaken calls the function of the same name on the Service's status +func MarkRevisionNameTaken(service *v1.Service) { + service.Status.MarkRevisionNameTaken(service.Spec.GetTemplate().GetName()) +} + +// WithRunLatestRollout configures the Service to use a "runLatest" rollout. +func WithRunLatestRollout(s *v1.Service) { + s.Spec = v1.ServiceSpec{ + ConfigurationSpec: *configSpec.DeepCopy(), + } +} + +// WithReadyRoute reflects the Route's readiness in the Service resource. +func WithReadyRoute(s *v1.Service) { + s.Status.PropagateRouteStatus(&v1.RouteStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + }) +} + +// WithReadyConfig reflects the Configuration's readiness in the Service +// resource. This must coincide with the setting of Latest{Created,Ready} +// to the provided revision name. +func WithReadyConfig(name string) ServiceOption { + return func(s *v1.Service) { + s.Status.PropagateConfigurationStatus(&v1.ConfigurationStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "True", + }}, + }, + ConfigurationStatusFields: v1.ConfigurationStatusFields{ + LatestCreatedRevisionName: name, + LatestReadyRevisionName: name, + }, + }) + } +} + +// WithSvcStatusDomain propagates the domain name to the status of the Service. +func WithSvcStatusDomain(s *v1.Service) { + n, ns := s.GetName(), s.GetNamespace() + s.Status.URL = &apis.URL{ + Scheme: "http", + Host: fmt.Sprintf("%s.%s.example.com", n, ns), + } +} + +// WithSvcStatusAddress updates the service's status with the address. +func WithSvcStatusAddress(s *v1.Service) { + s.Status.Address = &duckv1.Addressable{ + URL: &apis.URL{ + Scheme: "http", + Host: network.GetServiceHostname(s.Name, s.Namespace), + }, + } +} + +// WithSvcStatusTraffic sets the Service's status traffic block to the specified traffic targets. +func WithSvcStatusTraffic(targets ...v1.TrafficTarget) ServiceOption { + return func(r *v1.Service) { + // Automatically inject URL into TrafficTarget status + for _, tt := range targets { + tt.URL = domains.URL(domains.HTTPScheme, tt.Tag+".example.com") + } + r.Status.Traffic = targets + } +} + +// WithServiceLatestReadyRevision sets the latest ready revision on the Service's status. +func WithServiceLatestReadyRevision(lrr string) ServiceOption { + return func(s *v1.Service) { + s.Status.LatestReadyRevisionName = lrr + } +} + +// WithReadinessProbe sets the provided probe to be the readiness +// probe on the service. +func WithReadinessProbe(p *corev1.Probe) ServiceOption { + return func(s *v1.Service) { + s.Spec.Template.Spec.Containers[0].ReadinessProbe = p + } +} + +// MarkConfigurationNotReconciled calls the function of the same name on the Service's status. +func MarkConfigurationNotReconciled(service *v1.Service) { + service.Status.MarkConfigurationNotReconciled() +} + +// WithServiceStatusRouteNotReady sets the `RoutesReady` condition on the service to `Unknown`. +func WithServiceStatusRouteNotReady(s *v1.Service) { + s.Status.MarkRouteNotYetReady() +} + +// MarkConfigurationNotOwned calls the function of the same name on the Service's status. +func MarkConfigurationNotOwned(service *v1.Service) { + service.Status.MarkConfigurationNotOwned(servicenames.Configuration(service)) +} + +// MarkRouteNotOwned calls the function of the same name on the Service's status. +func MarkRouteNotOwned(service *v1.Service) { + service.Status.MarkRouteNotOwned(servicenames.Route(service)) +} + +// WithFailedRoute reflects a Route's failure in the Service resource. +func WithFailedRoute(reason, message string) ServiceOption { + return func(s *v1.Service) { + s.Status.PropagateRouteStatus(&v1.RouteStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "False", + Reason: reason, + Message: message, + }}, + }, + }) + } +} + +// WithFailedConfig reflects the Configuration's failure in the Service +// resource. The failing revision's name is reflected in LatestCreated. +func WithFailedConfig(name, reason, message string) ServiceOption { + return func(s *v1.Service) { + s.Status.PropagateConfigurationStatus(&v1.ConfigurationStatus{ + Status: duckv1.Status{ + Conditions: duckv1.Conditions{{ + Type: "Ready", + Status: "False", + Reason: reason, + Message: fmt.Sprintf("Revision %q failed with message: %s.", + name, message), + }}, + }, + ConfigurationStatusFields: v1.ConfigurationStatusFields{ + LatestCreatedRevisionName: name, + }, + }) + } +} + +var ( + // configSpec is the spec used for the different styles of Service rollout. + configSpec = v1.ConfigurationSpec{ + Template: v1.RevisionTemplateSpec{ + Spec: v1.RevisionSpec{ + TimeoutSeconds: ptr.Int64(60), + PodSpec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Image: "busybox", + }}, + }, + }, + }, + } +) diff --git a/vendor/modules.txt b/vendor/modules.txt index ca2416af8..196a13d17 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -906,7 +906,14 @@ knative.dev/serving/pkg/autoscaler/config/autoscalerconfig knative.dev/serving/pkg/client/clientset/versioned/scheme knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1 knative.dev/serving/pkg/client/clientset/versioned/typed/serving/v1/fake +knative.dev/serving/pkg/gc knative.dev/serving/pkg/networking +knative.dev/serving/pkg/reconciler/route/config +knative.dev/serving/pkg/reconciler/route/domains +knative.dev/serving/pkg/reconciler/route/resources/labels +knative.dev/serving/pkg/reconciler/route/resources/names +knative.dev/serving/pkg/reconciler/service/resources/names +knative.dev/serving/pkg/testing/v1 knative.dev/serving/test/test_images/grpc-ping knative.dev/serving/test/test_images/grpc-ping/proto # sigs.k8s.io/kustomize v2.0.3+incompatible