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
This commit is contained in:
Murugappan Chetty 2020-11-02 07:21:35 -08:00 committed by GitHub
parent 8ca97c7920
commit c60b85150c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 2529 additions and 765 deletions

View File

@ -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)
}
}

View File

@ -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,
},
}
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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())
}

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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())
}

View File

@ -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

View File

@ -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()
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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",
}},
},
},
},
}
)

7
vendor/modules.txt vendored
View File

@ -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