mirror of https://github.com/knative/client.git
e2e for service export (#739)
* e2e for service export * e2e for service export * e2e for service export * e2e for service export * e2e for service export
This commit is contained in:
parent
2fcf5adbbe
commit
a858a25135
|
|
@ -30,7 +30,7 @@ kn service [flags]
|
||||||
* [kn service create](kn_service_create.md) - Create a service.
|
* [kn service create](kn_service_create.md) - Create a service.
|
||||||
* [kn service delete](kn_service_delete.md) - Delete a service.
|
* [kn service delete](kn_service_delete.md) - Delete a service.
|
||||||
* [kn service describe](kn_service_describe.md) - Show details of a service
|
* [kn service describe](kn_service_describe.md) - Show details of a service
|
||||||
* [kn service export](kn_service_export.md) - export a service
|
* [kn service export](kn_service_export.md) - Export a service.
|
||||||
* [kn service list](kn_service_list.md) - List available services.
|
* [kn service list](kn_service_list.md) - List available services.
|
||||||
* [kn service update](kn_service_update.md) - Update a service.
|
* [kn service update](kn_service_update.md) - Update a service.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
## kn service export
|
## kn service export
|
||||||
|
|
||||||
export a service
|
Export a service.
|
||||||
|
|
||||||
### Synopsis
|
### Synopsis
|
||||||
|
|
||||||
export a service
|
Export a service.
|
||||||
|
|
||||||
```
|
```
|
||||||
kn service export NAME [flags]
|
kn service export NAME [flags]
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ func NewServiceExportCommand(p *commands.KnParams) *cobra.Command {
|
||||||
|
|
||||||
command := &cobra.Command{
|
command := &cobra.Command{
|
||||||
Use: "export NAME",
|
Use: "export NAME",
|
||||||
Short: "export a service",
|
Short: "Export a service.",
|
||||||
Example: `
|
Example: `
|
||||||
# Export a service in yaml format
|
# Export a service in yaml format
|
||||||
kn service export foo -n bar -o yaml
|
kn service export foo -n bar -o yaml
|
||||||
|
|
@ -125,7 +125,7 @@ func constructServicefromRevision(latestSvc *servingv1.Service, revision serving
|
||||||
TypeMeta: latestSvc.TypeMeta,
|
TypeMeta: latestSvc.TypeMeta,
|
||||||
}
|
}
|
||||||
|
|
||||||
exportedSvc.Spec.Template = servingv1.RevisionTemplateSpec{
|
exportedSvc.Spec.ConfigurationSpec.Template = servingv1.RevisionTemplateSpec{
|
||||||
Spec: revision.Spec,
|
Spec: revision.Spec,
|
||||||
ObjectMeta: latestSvc.Spec.ConfigurationSpec.Template.ObjectMeta,
|
ObjectMeta: latestSvc.Spec.ConfigurationSpec.Template.ObjectMeta,
|
||||||
}
|
}
|
||||||
|
|
@ -164,8 +164,10 @@ func exportServiceWithActiveRevisions(latestSvc *servingv1.Service, client clien
|
||||||
return nil, fmt.Errorf("no revisions found for service %s", latestSvc.ObjectMeta.Name)
|
return nil, fmt.Errorf("no revisions found for service %s", latestSvc.ObjectMeta.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
//set traffic in the latest revision
|
//set traffic in the latest revision on if there is traffic split
|
||||||
|
if len(latestSvc.Spec.RouteSpec.Traffic) > 1 {
|
||||||
exportedSvcItems[len(exportedSvcItems)-1] = setTrafficSplit(latestSvc, exportedSvcItems[len(exportedSvcItems)-1])
|
exportedSvcItems[len(exportedSvcItems)-1] = setTrafficSplit(latestSvc, exportedSvcItems[len(exportedSvcItems)-1])
|
||||||
|
}
|
||||||
|
|
||||||
typeMeta := metav1.TypeMeta{
|
typeMeta := metav1.TypeMeta{
|
||||||
APIVersion: "v1",
|
APIVersion: "v1",
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ package service
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"gotest.tools/assert"
|
"gotest.tools/assert"
|
||||||
|
|
@ -32,188 +33,97 @@ import (
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type expectedServiceOption func(*servingv1.Service)
|
||||||
|
type expectedRevisionOption func(*servingv1.Revision)
|
||||||
|
|
||||||
func TestServiceExport(t *testing.T) {
|
func TestServiceExport(t *testing.T) {
|
||||||
var svcs []*servingv1.Service
|
|
||||||
typeMeta := metav1.TypeMeta{
|
|
||||||
Kind: "service",
|
|
||||||
APIVersion: "serving.knative.dev/v1",
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 1 - plain svc
|
svcs := []*servingv1.Service{
|
||||||
plainService := getService("foo")
|
getServiceWithOptions(getService("foo"), withContainer()),
|
||||||
svcs = append(svcs, plainService)
|
getServiceWithOptions(getService("foo"), withContainer(), withEnv([]v1.EnvVar{{Name: "a", Value: "mouse"}})),
|
||||||
|
getServiceWithOptions(getService("foo"), withContainer(), withLabels(map[string]string{"a": "mouse", "b": "cookie", "empty": ""})),
|
||||||
// case 2 - svc with env variables
|
getServiceWithOptions(getService("foo"), withContainer(), withEnvFrom([]string{"cm-name"})),
|
||||||
envSvc := getService("foo")
|
getServiceWithOptions(getService("foo"), withContainer(), withVolumeandSecrets("volName", "secretName")),
|
||||||
envVars := []v1.EnvVar{
|
|
||||||
{Name: "a", Value: "mouse"},
|
|
||||||
{Name: "b", Value: "cookie"},
|
|
||||||
{Name: "empty", Value: ""},
|
|
||||||
}
|
}
|
||||||
template := &envSvc.Spec.Template
|
|
||||||
template.Spec.Containers[0].Env = envVars
|
|
||||||
template.Spec.Containers[0].Image = "gcr.io/foo/bar:baz"
|
|
||||||
template.Annotations = map[string]string{servinglib.UserImageAnnotationKey: "gcr.io/foo/bar:baz"}
|
|
||||||
svcs = append(svcs, envSvc)
|
|
||||||
|
|
||||||
//case 3 - svc with labels
|
|
||||||
labelService := getService("foo")
|
|
||||||
expected := map[string]string{
|
|
||||||
"a": "mouse",
|
|
||||||
"b": "cookie",
|
|
||||||
"empty": "",
|
|
||||||
}
|
|
||||||
labelService.Labels = expected
|
|
||||||
labelService.Spec.Template.Annotations = map[string]string{
|
|
||||||
servinglib.UserImageAnnotationKey: "gcr.io/foo/bar:baz",
|
|
||||||
}
|
|
||||||
template = &labelService.Spec.Template
|
|
||||||
template.ObjectMeta.Labels = expected
|
|
||||||
template.Spec.Containers[0].Image = "gcr.io/foo/bar:baz"
|
|
||||||
svcs = append(svcs, labelService)
|
|
||||||
|
|
||||||
//case 4 - config map
|
|
||||||
CMservice := getService("foo")
|
|
||||||
template = &CMservice.Spec.Template
|
|
||||||
template.Spec.Containers[0].EnvFrom = []v1.EnvFromSource{
|
|
||||||
{
|
|
||||||
ConfigMapRef: &v1.ConfigMapEnvSource{
|
|
||||||
LocalObjectReference: v1.LocalObjectReference{
|
|
||||||
Name: "config-map-name",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
template.Spec.Containers[0].Image = "gcr.io/foo/bar:baz"
|
|
||||||
template.Annotations = map[string]string{servinglib.UserImageAnnotationKey: "gcr.io/foo/bar:baz"}
|
|
||||||
svcs = append(svcs, CMservice)
|
|
||||||
|
|
||||||
//case 5 - volume mount and secrets
|
|
||||||
Volservice := getService("foo")
|
|
||||||
template = &Volservice.Spec.Template
|
|
||||||
template.Spec.Volumes = []v1.Volume{
|
|
||||||
{
|
|
||||||
Name: "volume-name",
|
|
||||||
VolumeSource: v1.VolumeSource{
|
|
||||||
Secret: &v1.SecretVolumeSource{
|
|
||||||
SecretName: "secret-name",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
template.Spec.Containers[0].VolumeMounts = []v1.VolumeMount{
|
|
||||||
{
|
|
||||||
Name: "volume-name",
|
|
||||||
MountPath: "/mount/path",
|
|
||||||
ReadOnly: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
svcs = append(svcs, Volservice)
|
|
||||||
|
|
||||||
template.Spec.Containers[0].Image = "gcr.io/foo/bar:baz"
|
|
||||||
template.Annotations = map[string]string{servinglib.UserImageAnnotationKey: "gcr.io/foo/bar:baz"}
|
|
||||||
|
|
||||||
for _, svc := range svcs {
|
for _, svc := range svcs {
|
||||||
svc.TypeMeta = typeMeta
|
|
||||||
callServiceExportTest(t, svc)
|
callServiceExportTest(t, svc)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func callServiceExportTest(t *testing.T, expectedService *servingv1.Service) {
|
func callServiceExportTest(t *testing.T, expectedService *servingv1.Service) {
|
||||||
// New mock client
|
// New mock client
|
||||||
client := knclient.NewMockKnServiceClient(t)
|
client := knclient.NewMockKnServiceClient(t)
|
||||||
|
|
||||||
// Recording:
|
// Recording:
|
||||||
r := client.Recorder()
|
r := client.Recorder()
|
||||||
|
|
||||||
r.GetService(expectedService.ObjectMeta.Name, expectedService, nil)
|
r.GetService(expectedService.ObjectMeta.Name, expectedService, nil)
|
||||||
|
|
||||||
output, err := executeServiceCommand(client, "export", expectedService.ObjectMeta.Name, "-o", "yaml")
|
output, err := executeServiceCommand(client, "export", expectedService.ObjectMeta.Name, "-o", "yaml")
|
||||||
|
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
expectedService.ObjectMeta.Namespace = ""
|
actSvc := servingv1.Service{}
|
||||||
|
err = yaml.Unmarshal([]byte(output), &actSvc)
|
||||||
expSvcYaml, err := yaml.Marshal(expectedService)
|
|
||||||
|
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
stripExpectedSvcVariables(expectedService)
|
||||||
assert.Equal(t, string(expSvcYaml), output)
|
assert.DeepEqual(t, expectedService, &actSvc)
|
||||||
|
|
||||||
// Validate that all recorded API methods have been called
|
// Validate that all recorded API methods have been called
|
||||||
r.Validate()
|
r.Validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServiceExportwithMultipleRevisions(t *testing.T) {
|
func TestServiceExportwithMultipleRevisions(t *testing.T) {
|
||||||
//case 1 = 2 revisions with traffic split
|
//case 1 - 2 revisions with traffic split
|
||||||
trafficSplitService := createServiceTwoRevsionsWithTraffic("foo", true)
|
expSvc1 := getServiceWithOptions(getService("foo"), withContainer(), withServiceRevisionName("foo-rev-1"))
|
||||||
|
stripExpectedSvcVariables(expSvc1)
|
||||||
|
expSvc2 := getServiceWithOptions(getService("foo"), withContainer(), withTrafficSplit([]string{"foo-rev-1", "foo-rev-2"}, []int{50, 50}, []string{"latest", "current"}), withServiceRevisionName("foo-rev-2"))
|
||||||
|
stripExpectedSvcVariables(expSvc2)
|
||||||
|
latestSvc := getServiceWithOptions(getService("foo"), withContainer(), withTrafficSplit([]string{"foo-rev-1", "foo-rev-2"}, []int{50, 50}, []string{"latest", "current"}))
|
||||||
|
|
||||||
multiRevs := createTestRevisionList("rev", "foo")
|
expSvcList := servingv1.ServiceList{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
callServiceExportHistoryTest(t, trafficSplitService, multiRevs)
|
APIVersion: "v1",
|
||||||
|
Kind: "List",
|
||||||
//case 2 - same revisions no traffic split
|
},
|
||||||
noTrafficSplitService := createServiceTwoRevsionsWithTraffic("foo", false)
|
Items: []servingv1.Service{*expSvc1, *expSvc2},
|
||||||
|
|
||||||
callServiceExportHistoryTest(t, noTrafficSplitService, multiRevs)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func callServiceExportHistoryTest(t *testing.T, expectedService *servingv1.Service, revs *servingv1.RevisionList) {
|
multiRevs := getRevisionList("rev", "foo")
|
||||||
|
|
||||||
|
callServiceExportHistoryTest(t, latestSvc, multiRevs, &expSvcList)
|
||||||
|
|
||||||
|
// case 2 - same revisions no traffic split
|
||||||
|
expSvc2 = getServiceWithOptions(getService("foo"), withContainer(), withServiceRevisionName("foo-rev-2"))
|
||||||
|
stripExpectedSvcVariables(expSvc2)
|
||||||
|
expSvcList = servingv1.ServiceList{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "List",
|
||||||
|
},
|
||||||
|
Items: []servingv1.Service{*expSvc2},
|
||||||
|
}
|
||||||
|
latestSvc = getServiceWithOptions(getService("foo"), withContainer(), withTrafficSplit([]string{"foo-rev-2"}, []int{100}, []string{"latest"}))
|
||||||
|
callServiceExportHistoryTest(t, latestSvc, multiRevs, &expSvcList)
|
||||||
|
}
|
||||||
|
|
||||||
|
func callServiceExportHistoryTest(t *testing.T, latestSvc *servingv1.Service, revs *servingv1.RevisionList, expSvcList *servingv1.ServiceList) {
|
||||||
// New mock client
|
// New mock client
|
||||||
client := knclient.NewMockKnServiceClient(t)
|
client := knclient.NewMockKnServiceClient(t)
|
||||||
|
|
||||||
// Recording:
|
// Recording:
|
||||||
r := client.Recorder()
|
r := client.Recorder()
|
||||||
|
|
||||||
r.GetService(expectedService.ObjectMeta.Name, expectedService, nil)
|
r.GetService(latestSvc.ObjectMeta.Name, latestSvc, nil)
|
||||||
|
|
||||||
r.ListRevisions(mock.Any(), revs, nil)
|
r.ListRevisions(mock.Any(), revs, nil)
|
||||||
|
|
||||||
output, err := executeServiceCommand(client, "export", expectedService.ObjectMeta.Name, "--with-revisions", "-o", "json")
|
output, err := executeServiceCommand(client, "export", latestSvc.ObjectMeta.Name, "--with-revisions", "-o", "json")
|
||||||
|
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
actSvcList := servingv1.ServiceList{}
|
actSvcList := servingv1.ServiceList{}
|
||||||
|
err = json.Unmarshal([]byte(output), &actSvcList)
|
||||||
json.Unmarshal([]byte(output), &actSvcList)
|
assert.NilError(t, err)
|
||||||
|
assert.DeepEqual(t, expSvcList, &actSvcList)
|
||||||
for i, actSvc := range actSvcList.Items {
|
|
||||||
var checkTraffic bool
|
|
||||||
if i == (len(actSvcList.Items) - 1) {
|
|
||||||
checkTraffic = true
|
|
||||||
}
|
|
||||||
validateServiceWithRevisionHistory(t, expectedService, revs, actSvc, checkTraffic)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate that all recorded API methods have been called
|
// Validate that all recorded API methods have been called
|
||||||
r.Validate()
|
r.Validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateServiceWithRevisionHistory(t *testing.T, expectedsvc *servingv1.Service, expectedRevList *servingv1.RevisionList, actualSvc servingv1.Service, checkTraffic bool) {
|
|
||||||
var expectedRev servingv1.Revision
|
|
||||||
var routeSpec servingv1.RouteSpec
|
|
||||||
for _, rev := range expectedRevList.Items {
|
|
||||||
if actualSvc.Spec.ConfigurationSpec.Template.ObjectMeta.Name == rev.ObjectMeta.Name {
|
|
||||||
expectedRev = rev
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
expectedsvc.Spec.ConfigurationSpec.Template.ObjectMeta.Name = expectedRev.ObjectMeta.Name
|
|
||||||
expectedsvc.Spec.Template.Spec = expectedRev.Spec
|
|
||||||
|
|
||||||
stripExpectedSvcVariables(expectedsvc)
|
|
||||||
|
|
||||||
if !checkTraffic {
|
|
||||||
routeSpec = expectedsvc.Spec.RouteSpec
|
|
||||||
expectedsvc.Spec.RouteSpec = servingv1.RouteSpec{}
|
|
||||||
}
|
|
||||||
assert.DeepEqual(t, expectedsvc, &actualSvc)
|
|
||||||
|
|
||||||
expectedsvc.Spec.RouteSpec = routeSpec
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServiceExportError(t *testing.T) {
|
func TestServiceExportError(t *testing.T) {
|
||||||
// New mock client
|
// New mock client
|
||||||
client := knclient.NewMockKnServiceClient(t)
|
client := knclient.NewMockKnServiceClient(t)
|
||||||
|
|
@ -225,94 +135,28 @@ func TestServiceExportError(t *testing.T) {
|
||||||
assert.Error(t, err, "'kn service export' requires output format")
|
assert.Error(t, err, "'kn service export' requires output format")
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTestRevisionList(revision string, service string) *servingv1.RevisionList {
|
func getRevisionList(revision string, service string) *servingv1.RevisionList {
|
||||||
labels1 := make(map[string]string)
|
rev1 := getRevisionWithOptions(
|
||||||
labels1[apiserving.ConfigurationGenerationLabelKey] = "1"
|
service,
|
||||||
labels1[apiserving.ServiceLabelKey] = service
|
withRevisionGeneration("1"),
|
||||||
|
withRevisionName(fmt.Sprintf("%s-%s-%d", service, revision, 1)),
|
||||||
|
)
|
||||||
|
|
||||||
labels2 := make(map[string]string)
|
rev2 := getRevisionWithOptions(
|
||||||
labels2[apiserving.ConfigurationGenerationLabelKey] = "2"
|
service,
|
||||||
labels2[apiserving.ServiceLabelKey] = service
|
withRevisionGeneration("2"),
|
||||||
|
withRevisionName(fmt.Sprintf("%s-%s-%d", service, revision, 2)),
|
||||||
rev1 := servingv1.Revision{
|
)
|
||||||
TypeMeta: metav1.TypeMeta{
|
|
||||||
Kind: "Revision",
|
|
||||||
APIVersion: "serving.knative.dev/v1",
|
|
||||||
},
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: fmt.Sprintf("%s-%s-%d", service, revision, 1),
|
|
||||||
Namespace: "default",
|
|
||||||
Generation: int64(1),
|
|
||||||
Labels: labels1,
|
|
||||||
},
|
|
||||||
Spec: servingv1.RevisionSpec{
|
|
||||||
PodSpec: v1.PodSpec{
|
|
||||||
Containers: []v1.Container{
|
|
||||||
{
|
|
||||||
Image: "gcr.io/test/image:v1",
|
|
||||||
Env: []v1.EnvVar{
|
|
||||||
{Name: "env1", Value: "eval1"},
|
|
||||||
{Name: "env2", Value: "eval2"},
|
|
||||||
},
|
|
||||||
EnvFrom: []v1.EnvFromSource{
|
|
||||||
{ConfigMapRef: &v1.ConfigMapEnvSource{LocalObjectReference: v1.LocalObjectReference{Name: "test1"}}},
|
|
||||||
{ConfigMapRef: &v1.ConfigMapEnvSource{LocalObjectReference: v1.LocalObjectReference{Name: "test2"}}},
|
|
||||||
},
|
|
||||||
Ports: []v1.ContainerPort{
|
|
||||||
{ContainerPort: 8080},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
rev2 := rev1
|
|
||||||
|
|
||||||
rev2.Spec.PodSpec.Containers[0].Image = "gcr.io/test/image:v2"
|
|
||||||
rev2.ObjectMeta.Labels = labels2
|
|
||||||
rev2.ObjectMeta.Generation = int64(2)
|
|
||||||
rev2.ObjectMeta.Name = fmt.Sprintf("%s-%s-%d", service, revision, 2)
|
|
||||||
|
|
||||||
typeMeta := metav1.TypeMeta{
|
|
||||||
APIVersion: "v1",
|
|
||||||
Kind: "List",
|
|
||||||
}
|
|
||||||
|
|
||||||
return &servingv1.RevisionList{
|
return &servingv1.RevisionList{
|
||||||
TypeMeta: typeMeta,
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "List",
|
||||||
|
},
|
||||||
Items: []servingv1.Revision{rev1, rev2},
|
Items: []servingv1.Revision{rev1, rev2},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createServiceTwoRevsionsWithTraffic(svc string, trafficSplit bool) *servingv1.Service {
|
|
||||||
expectedService := createTestService(svc, []string{svc + "-rev-1", svc + "-rev-2"}, goodConditions())
|
|
||||||
expectedService.Status.Traffic[0].LatestRevision = ptr.Bool(true)
|
|
||||||
expectedService.Status.Traffic[0].Tag = "latest"
|
|
||||||
expectedService.Status.Traffic[1].Tag = "current"
|
|
||||||
|
|
||||||
if trafficSplit {
|
|
||||||
trafficList := []servingv1.TrafficTarget{
|
|
||||||
{
|
|
||||||
RevisionName: "foo-rev-1",
|
|
||||||
Percent: ptr.Int64(int64(50)),
|
|
||||||
}, {
|
|
||||||
RevisionName: "foo-rev-2",
|
|
||||||
Percent: ptr.Int64(int64(50)),
|
|
||||||
}}
|
|
||||||
expectedService.Spec.RouteSpec = servingv1.RouteSpec{Traffic: trafficList}
|
|
||||||
} else {
|
|
||||||
trafficList := []servingv1.TrafficTarget{
|
|
||||||
{
|
|
||||||
RevisionName: "foo-rev-2",
|
|
||||||
Percent: ptr.Int64(int64(50)),
|
|
||||||
}}
|
|
||||||
expectedService.Spec.RouteSpec = servingv1.RouteSpec{Traffic: trafficList}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &expectedService
|
|
||||||
}
|
|
||||||
|
|
||||||
func stripExpectedSvcVariables(expectedsvc *servingv1.Service) {
|
func stripExpectedSvcVariables(expectedsvc *servingv1.Service) {
|
||||||
expectedsvc.ObjectMeta.Namespace = ""
|
expectedsvc.ObjectMeta.Namespace = ""
|
||||||
expectedsvc.Spec.Template.Spec.Containers[0].Resources = v1.ResourceRequirements{}
|
expectedsvc.Spec.Template.Spec.Containers[0].Resources = v1.ResourceRequirements{}
|
||||||
|
|
@ -320,3 +164,147 @@ func stripExpectedSvcVariables(expectedsvc *servingv1.Service) {
|
||||||
expectedsvc.ObjectMeta.Annotations = nil
|
expectedsvc.ObjectMeta.Annotations = nil
|
||||||
expectedsvc.ObjectMeta.CreationTimestamp = metav1.Time{}
|
expectedsvc.ObjectMeta.CreationTimestamp = metav1.Time{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getRevisionWithOptions(service string, options ...expectedRevisionOption) servingv1.Revision {
|
||||||
|
rev := servingv1.Revision{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
Kind: "Revision",
|
||||||
|
APIVersion: "serving.knative.dev/v1",
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Namespace: "default",
|
||||||
|
Labels: map[string]string{
|
||||||
|
apiserving.ServiceLabelKey: service,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: servingv1.RevisionSpec{
|
||||||
|
PodSpec: v1.PodSpec{
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{
|
||||||
|
Image: "gcr.io/foo/bar:baz",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, fn := range options {
|
||||||
|
fn(&rev)
|
||||||
|
}
|
||||||
|
return rev
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Spec.ConfigurationSpec.Template.ObjectMeta.Labels = labels
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withEnvFrom(cmNames []string) expectedServiceOption {
|
||||||
|
return func(svc *servingv1.Service) {
|
||||||
|
var list []v1.EnvFromSource
|
||||||
|
for _, cmName := range cmNames {
|
||||||
|
list = append(list, v1.EnvFromSource{
|
||||||
|
ConfigMapRef: &v1.ConfigMapEnvSource{
|
||||||
|
LocalObjectReference: v1.LocalObjectReference{
|
||||||
|
Name: cmName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
svc.Spec.ConfigurationSpec.Template.Spec.PodSpec.Containers[0].EnvFrom = list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withEnv(env []v1.EnvVar) expectedServiceOption {
|
||||||
|
return func(svc *servingv1.Service) {
|
||||||
|
svc.Spec.ConfigurationSpec.Template.Spec.PodSpec.Containers[0].Env = env
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withContainer() expectedServiceOption {
|
||||||
|
return func(svc *servingv1.Service) {
|
||||||
|
svc.Spec.ConfigurationSpec.Template.Spec.Containers[0].Image = "gcr.io/foo/bar:baz"
|
||||||
|
svc.Spec.ConfigurationSpec.Template.Annotations = map[string]string{servinglib.UserImageAnnotationKey: "gcr.io/foo/bar:baz"}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withVolumeandSecrets(volName string, secretName string) expectedServiceOption {
|
||||||
|
return func(svc *servingv1.Service) {
|
||||||
|
template := &svc.Spec.Template
|
||||||
|
template.Spec.Volumes = []v1.Volume{
|
||||||
|
{
|
||||||
|
Name: volName,
|
||||||
|
VolumeSource: v1.VolumeSource{
|
||||||
|
Secret: &v1.SecretVolumeSource{
|
||||||
|
SecretName: secretName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
template.Spec.Containers[0].VolumeMounts = []v1.VolumeMount{
|
||||||
|
{
|
||||||
|
Name: volName,
|
||||||
|
MountPath: "/mount/path",
|
||||||
|
ReadOnly: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withRevisionGeneration(gen string) expectedRevisionOption {
|
||||||
|
return func(rev *servingv1.Revision) {
|
||||||
|
i, _ := strconv.Atoi(gen)
|
||||||
|
rev.ObjectMeta.Generation = int64(i)
|
||||||
|
rev.ObjectMeta.Labels[apiserving.ConfigurationGenerationLabelKey] = gen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withRevisionName(name string) expectedRevisionOption {
|
||||||
|
return func(rev *servingv1.Revision) {
|
||||||
|
rev.ObjectMeta.Name = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withServiceRevisionName(name string) expectedServiceOption {
|
||||||
|
return func(svc *servingv1.Service) {
|
||||||
|
svc.Spec.ConfigurationSpec.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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,264 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// +build e2e
|
||||||
|
// +build !eventing
|
||||||
|
|
||||||
|
package e2e
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gotest.tools/assert"
|
||||||
|
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
|
"knative.dev/pkg/ptr"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
|
servingv1 "knative.dev/serving/pkg/apis/serving/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type expectedServiceOption func(*servingv1.Service)
|
||||||
|
|
||||||
|
func TestServiceExportImportApply(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
test, err := NewE2eTest()
|
||||||
|
assert.NilError(t, err)
|
||||||
|
defer func() {
|
||||||
|
assert.NilError(t, test.Teardown())
|
||||||
|
}()
|
||||||
|
|
||||||
|
r := NewKnRunResultCollector(t)
|
||||||
|
defer r.DumpIfFailed()
|
||||||
|
|
||||||
|
t.Log("create service with byo revision")
|
||||||
|
test.serviceCreateWithOptions(t, r, "hello", "--revision-name", "rev1")
|
||||||
|
|
||||||
|
t.Log("export service and compare")
|
||||||
|
test.serviceExport(t, r, "hello", getSvc(withName("hello"), withRevisionName("hello-rev1"), withAnnotations()), "-o", "json")
|
||||||
|
|
||||||
|
t.Log("update service - add env variable")
|
||||||
|
test.serviceUpdateWithOptions(t, r, "hello", "--env", "key1=val1", "--revision-name", "rev2", "--no-lock-to-digest")
|
||||||
|
test.serviceExport(t, r, "hello", getSvc(withName("hello"), withRevisionName("hello-rev2"), withEnv("key1", "val1")), "-o", "json")
|
||||||
|
test.serviceExportWithRevisions(t, r, "hello", getSvcListWithOneRevision(), "--with-revisions", "-o", "yaml")
|
||||||
|
|
||||||
|
t.Log("update service with tag and split traffic")
|
||||||
|
test.serviceUpdateWithOptions(t, r, "hello", "--tag", "hello-rev1=candidate", "--traffic", "candidate=2%,@latest=98%")
|
||||||
|
test.serviceExportWithRevisions(t, r, "hello", getSvcListWithTags(), "--with-revisions", "-o", "yaml")
|
||||||
|
|
||||||
|
t.Log("update service - untag, add env variable and traffic split")
|
||||||
|
test.serviceUpdateWithOptions(t, r, "hello", "--untag", "candidate")
|
||||||
|
test.serviceUpdateWithOptions(t, r, "hello", "--env", "key2=val2", "--revision-name", "rev3", "--traffic", "hello-rev1=30,hello-rev2=30,hello-rev3=40")
|
||||||
|
test.serviceExportWithRevisions(t, r, "hello", getSvcListWOTags(), "--with-revisions", "-o", "yaml")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (test *e2eTest) serviceExport(t *testing.T, r *KnRunResultCollector, serviceName string, expService servingv1.Service, options ...string) {
|
||||||
|
command := []string{"service", "export", serviceName}
|
||||||
|
command = append(command, options...)
|
||||||
|
out := test.kn.Run(command...)
|
||||||
|
validateExportedService(t, out.Stdout, expService)
|
||||||
|
r.AssertNoError(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateExportedService(t *testing.T, out string, expService servingv1.Service) {
|
||||||
|
actSvcJSON := servingv1.Service{}
|
||||||
|
err := json.Unmarshal([]byte(out), &actSvcJSON)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.DeepEqual(t, &expService, &actSvcJSON)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (test *e2eTest) serviceExportWithRevisions(t *testing.T, r *KnRunResultCollector, serviceName string, expServiceList servingv1.ServiceList, options ...string) {
|
||||||
|
command := []string{"service", "export", serviceName}
|
||||||
|
command = append(command, options...)
|
||||||
|
out := test.kn.Run(command...)
|
||||||
|
validateExportedServiceList(t, out.Stdout, expServiceList)
|
||||||
|
r.AssertNoError(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateExportedServiceList(t *testing.T, out string, expServiceList servingv1.ServiceList) {
|
||||||
|
actYaml := servingv1.ServiceList{}
|
||||||
|
err := yaml.Unmarshal([]byte(out), &actYaml)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.DeepEqual(t, &expServiceList, &actYaml)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSvc(options ...expectedServiceOption) servingv1.Service {
|
||||||
|
svc := servingv1.Service{
|
||||||
|
Spec: servingv1.ServiceSpec{
|
||||||
|
ConfigurationSpec: servingv1.ConfigurationSpec{
|
||||||
|
Template: servingv1.RevisionTemplateSpec{
|
||||||
|
Spec: servingv1.RevisionSpec{
|
||||||
|
ContainerConcurrency: ptr.Int64(int64(0)),
|
||||||
|
TimeoutSeconds: ptr.Int64(int64(300)),
|
||||||
|
PodSpec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{
|
||||||
|
{
|
||||||
|
Name: "user-container",
|
||||||
|
Image: KnDefaultTestImage,
|
||||||
|
Resources: corev1.ResourceRequirements{},
|
||||||
|
ReadinessProbe: &corev1.Probe{
|
||||||
|
SuccessThreshold: int32(1),
|
||||||
|
Handler: corev1.Handler{
|
||||||
|
TCPSocket: &corev1.TCPSocketAction{
|
||||||
|
Port: intstr.FromInt(0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
Kind: "Service",
|
||||||
|
APIVersion: "serving.knative.dev/v1",
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, fn := range options {
|
||||||
|
fn(&svc)
|
||||||
|
}
|
||||||
|
return svc
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSvcListWOTags() servingv1.ServiceList {
|
||||||
|
return servingv1.ServiceList{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "List",
|
||||||
|
},
|
||||||
|
Items: []servingv1.Service{
|
||||||
|
getSvc(
|
||||||
|
withName("hello"),
|
||||||
|
withRevisionName("hello-rev1"),
|
||||||
|
),
|
||||||
|
getSvc(
|
||||||
|
withName("hello"),
|
||||||
|
withRevisionName("hello-rev2"),
|
||||||
|
withEnv("key1", "val1"),
|
||||||
|
),
|
||||||
|
getSvc(
|
||||||
|
withName("hello"),
|
||||||
|
withRevisionName("hello-rev3"),
|
||||||
|
withEnv("key1", "val1"), withEnv("key2", "val2"),
|
||||||
|
withTrafficSplit([]string{"hello-rev1", "hello-rev2", "hello-rev3"}, []int{30, 30, 40}, []string{"", "", ""}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSvcListWithTags() servingv1.ServiceList {
|
||||||
|
return servingv1.ServiceList{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "List",
|
||||||
|
},
|
||||||
|
Items: []servingv1.Service{
|
||||||
|
getSvc(
|
||||||
|
withName("hello"),
|
||||||
|
withRevisionName("hello-rev1"),
|
||||||
|
),
|
||||||
|
getSvc(
|
||||||
|
withName("hello"),
|
||||||
|
withRevisionName("hello-rev2"),
|
||||||
|
withEnv("key1", "val1"),
|
||||||
|
withTrafficSplit([]string{"latest", "hello-rev1"}, []int{98, 2}, []string{"", "candidate"}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSvcListWithOneRevision() servingv1.ServiceList {
|
||||||
|
return servingv1.ServiceList{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "List",
|
||||||
|
},
|
||||||
|
Items: []servingv1.Service{
|
||||||
|
getSvc(
|
||||||
|
withName("hello"),
|
||||||
|
withRevisionName("hello-rev2"),
|
||||||
|
withEnv("key1", "val1"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withRevisionName(revName string) expectedServiceOption {
|
||||||
|
return func(svc *servingv1.Service) {
|
||||||
|
svc.Spec.ConfigurationSpec.Template.ObjectMeta.Name = revName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withAnnotations() expectedServiceOption {
|
||||||
|
return func(svc *servingv1.Service) {
|
||||||
|
svc.Spec.ConfigurationSpec.Template.ObjectMeta.Annotations = map[string]string{
|
||||||
|
"client.knative.dev/user-image": "gcr.io/knative-samples/helloworld-go",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withName(name string) expectedServiceOption {
|
||||||
|
return func(svc *servingv1.Service) {
|
||||||
|
svc.ObjectMeta.Name = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withEnv(key string, val string) expectedServiceOption {
|
||||||
|
return func(svc *servingv1.Service) {
|
||||||
|
env := []corev1.EnvVar{
|
||||||
|
{
|
||||||
|
Name: key,
|
||||||
|
Value: val,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
currentEnv := svc.Spec.ConfigurationSpec.Template.Spec.PodSpec.Containers[0].Env
|
||||||
|
if len(currentEnv) > 0 {
|
||||||
|
svc.Spec.ConfigurationSpec.Template.Spec.PodSpec.Containers[0].Env = append(currentEnv, env...)
|
||||||
|
} else {
|
||||||
|
svc.Spec.ConfigurationSpec.Template.Spec.PodSpec.Containers[0].Env = env
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue