mirror of https://github.com/knative/client.git
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:
parent
8ca97c7920
commit
c60b85150c
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
75
vendor/knative.dev/serving/pkg/reconciler/route/config/zz_generated.deepcopy.go
vendored
Normal file
75
vendor/knative.dev/serving/pkg/reconciler/route/config/zz_generated.deepcopy.go
vendored
Normal 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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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())
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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())
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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",
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue