diff --git a/docs/cmd/kn_service_create.md b/docs/cmd/kn_service_create.md index e7e61b841..f94c55f8f 100644 --- a/docs/cmd/kn_service_create.md +++ b/docs/cmd/kn_service_create.md @@ -36,12 +36,16 @@ kn service create NAME --image IMAGE [flags] ### Options ``` + --concurrency-limit int Hard Limit of concurrent requests to be processed by a single replica. + --concurrency-target int Recommendation for when to scale up based on the concurrent number of incoming request. Defaults to --concurrency-limit when given. -e, --env stringArray Environment variable to set. NAME=value; you may provide this flag any number of times to set multiple environment variables. --force Create service forcefully, replaces existing service if any. -h, --help help for create --image string Image to run. --limits-cpu string The limits on the requested CPU (e.g., 1000m). --limits-memory string The limits on the requested CPU (e.g., 1024Mi). + --max-scale int Maximal number of replicas. + --min-scale int Minimal number of replicas. -n, --namespace string List the requested object(s) in given namespace. --requests-cpu string The requested CPU (e.g., 250m). --requests-memory string The requested CPU (e.g., 64Mi). diff --git a/docs/cmd/kn_service_update.md b/docs/cmd/kn_service_update.md index 13ece39da..c390b5f7e 100644 --- a/docs/cmd/kn_service_update.md +++ b/docs/cmd/kn_service_update.md @@ -24,11 +24,15 @@ kn service update NAME [flags] ### Options ``` + --concurrency-limit int Hard Limit of concurrent requests to be processed by a single replica. + --concurrency-target int Recommendation for when to scale up based on the concurrent number of incoming request. Defaults to --concurrency-limit when given. -e, --env stringArray Environment variable to set. NAME=value; you may provide this flag any number of times to set multiple environment variables. -h, --help help for update --image string Image to run. --limits-cpu string The limits on the requested CPU (e.g., 1000m). --limits-memory string The limits on the requested CPU (e.g., 1024Mi). + --max-scale int Maximal number of replicas. + --min-scale int Minimal number of replicas. -n, --namespace string List the requested object(s) in given namespace. --requests-cpu string The requested CPU (e.g., 250m). --requests-memory string The requested CPU (e.g., 64Mi). diff --git a/pkg/kn/commands/service/configuration_edit_flags.go b/pkg/kn/commands/service/configuration_edit_flags.go index e65b354c9..53badf8c4 100644 --- a/pkg/kn/commands/service/configuration_edit_flags.go +++ b/pkg/kn/commands/service/configuration_edit_flags.go @@ -30,6 +30,10 @@ type ConfigurationEditFlags struct { Env []string RequestsFlags, LimitsFlags ResourceFlags ForceCreate bool + MinScale int + MaxScale int + ConcurrencyTarget int + ConcurrencyLimit int } type ResourceFlags struct { @@ -46,6 +50,10 @@ func (p *ConfigurationEditFlags) AddUpdateFlags(command *cobra.Command) { command.Flags().StringVar(&p.RequestsFlags.Memory, "requests-memory", "", "The requested CPU (e.g., 64Mi).") command.Flags().StringVar(&p.LimitsFlags.CPU, "limits-cpu", "", "The limits on the requested CPU (e.g., 1000m).") command.Flags().StringVar(&p.LimitsFlags.Memory, "limits-memory", "", "The limits on the requested CPU (e.g., 1024Mi).") + command.Flags().IntVar(&p.MinScale, "min-scale", 0, "Minimal number of replicas.") + command.Flags().IntVar(&p.MaxScale, "max-scale", 0, "Maximal number of replicas.") + command.Flags().IntVar(&p.ConcurrencyTarget, "concurrency-target", 0, "Recommendation for when to scale up based on the concurrent number of incoming request. Defaults to --concurrency-limit when given.") + command.Flags().IntVar(&p.ConcurrencyLimit, "concurrency-limit", 0, "Hard Limit of concurrent requests to be processed by a single replica.") } func (p *ConfigurationEditFlags) AddCreateFlags(command *cobra.Command) { @@ -93,6 +101,9 @@ func (p *ConfigurationEditFlags) Apply(service *servingv1alpha1.Service, cmd *co if err != nil { return err } + + servinglib.UpdateConcurrencyConfiguration(template, p.MinScale, p.MaxScale, p.ConcurrencyTarget, p.ConcurrencyLimit) + return nil } diff --git a/pkg/kn/commands/service/service_create_test.go b/pkg/kn/commands/service/service_create_test.go index 447675719..682b22ffa 100644 --- a/pkg/kn/commands/service/service_create_test.go +++ b/pkg/kn/commands/service/service_create_test.go @@ -235,6 +235,42 @@ func TestServiceCreateRequestsLimitsMemory(t *testing.T) { } } +func TestServiceCreateMaxMinScale(t *testing.T) { + action, created, _, err := fakeServiceCreate([]string{ + "service", "create", "foo", "--image", "gcr.io/foo/bar:baz", + "--min-scale", "1", "--max-scale", "5", "--concurrency-target", "10", "--concurrency-limit", "100"}) + + if err != nil { + t.Fatal(err) + } else if !action.Matches("create", "services") { + t.Fatalf("Bad action %v", action) + } + + template, err := servinglib.GetRevisionTemplate(created) + if err != nil { + t.Fatal(err) + } + + actualAnnos := template.Annotations + expectedAnnos := []string{ + "autoscaling.knative.dev/minScale", "1", + "autoscaling.knative.dev/maxScale", "5", + "autoscaling.knative.dev/target", "10", + } + + for i := 0; i < len(expectedAnnos); i += 2 { + anno := expectedAnnos[i] + if actualAnnos[anno] != expectedAnnos[i+1] { + t.Fatalf("Unexpected annotation value for %s : %s (actual) != %s (expected)", + anno, actualAnnos[anno], expectedAnnos[i+1]) + } + } + + if template.Spec.ContainerConcurrency != 100 { + t.Fatalf("container concurrency not set to given value 1000") + } +} + func TestServiceCreateRequestsLimitsCPUMemory(t *testing.T) { action, created, _, err := fakeServiceCreate([]string{ "service", "create", "foo", "--image", "gcr.io/foo/bar:baz", diff --git a/pkg/kn/commands/service/service_update_test.go b/pkg/kn/commands/service/service_update_test.go index f21e67e7a..fc1cd87d5 100644 --- a/pkg/kn/commands/service/service_update_test.go +++ b/pkg/kn/commands/service/service_update_test.go @@ -64,27 +64,7 @@ func fakeServiceUpdate(original *v1alpha1.Service, args []string) ( } func TestServiceUpdateImage(t *testing.T) { - orig := &v1alpha1.Service{ - TypeMeta: metav1.TypeMeta{ - Kind: "Service", - APIVersion: "knative.dev/v1alpha1", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "foo", - Namespace: "default", - }, - Spec: v1alpha1.ServiceSpec{ - DeprecatedRunLatest: &v1alpha1.RunLatestType{ - Configuration: v1alpha1.ConfigurationSpec{ - DeprecatedRevisionTemplate: &v1alpha1.RevisionTemplateSpec{ - Spec: v1alpha1.RevisionSpec{ - DeprecatedContainer: &corev1.Container{}, - }, - }, - }, - }, - }, - } + orig := newEmptyService() template, err := servinglib.GetRevisionTemplate(orig) if err != nil { @@ -110,6 +90,45 @@ func TestServiceUpdateImage(t *testing.T) { } } +func TestServiceUpdateMaxMinScale(t *testing.T) { + original := newEmptyService() + + action, updated, _, err := fakeServiceUpdate(original, []string{ + "service", "update", "foo", + "--min-scale", "1", "--max-scale", "5", "--concurrency-target", "10", "--concurrency-limit", "100"}) + + if err != nil { + t.Fatal(err) + } else if !action.Matches("update", "services") { + t.Fatalf("Bad action %v", action) + } + + template, err := servinglib.GetRevisionTemplate(updated) + if err != nil { + t.Fatal(err) + } + + actualAnnos := template.Annotations + expectedAnnos := []string{ + "autoscaling.knative.dev/minScale", "1", + "autoscaling.knative.dev/maxScale", "5", + "autoscaling.knative.dev/target", "10", + } + + for i := 0; i < len(expectedAnnos); i += 2 { + anno := expectedAnnos[i] + if actualAnnos[anno] != expectedAnnos[i+1] { + t.Fatalf("Unexpected annotation value for %s : %s (actual) != %s (expected)", + anno, actualAnnos[anno], expectedAnnos[i+1]) + } + } + + if template.Spec.ContainerConcurrency != 100 { + t.Fatalf("container concurrency not set to given value 1000") + } + +} + func TestServiceUpdateEnv(t *testing.T) { orig := &v1alpha1.Service{ TypeMeta: metav1.TypeMeta{ @@ -279,6 +298,30 @@ func TestServiceUpdateRequestsLimitsCPU_and_Memory(t *testing.T) { } } +func newEmptyService() *v1alpha1.Service { + return &v1alpha1.Service{ + TypeMeta: metav1.TypeMeta{ + Kind: "Service", + APIVersion: "knative.dev/v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "default", + }, + Spec: v1alpha1.ServiceSpec{ + DeprecatedRunLatest: &v1alpha1.RunLatestType{ + Configuration: v1alpha1.ConfigurationSpec{ + DeprecatedRevisionTemplate: &v1alpha1.RevisionTemplateSpec{ + Spec: v1alpha1.RevisionSpec{ + DeprecatedContainer: &corev1.Container{}, + }, + }, + }, + }, + }, + } +} + func createMockServiceWithResources(t *testing.T, requestCPU, requestMemory, limitsCPU, limitsMemory string) *v1alpha1.Service { service := &v1alpha1.Service{ TypeMeta: metav1.TypeMeta{ diff --git a/pkg/serving/config_changes.go b/pkg/serving/config_changes.go index f9ee71bb3..6baba1635 100644 --- a/pkg/serving/config_changes.go +++ b/pkg/serving/config_changes.go @@ -16,8 +16,10 @@ package serving import ( "fmt" + "strconv" servingv1alpha1 "github.com/knative/serving/pkg/apis/serving/v1alpha1" + servingv1beta1 "github.com/knative/serving/pkg/apis/serving/v1beta1" corev1 "k8s.io/api/core/v1" ) @@ -33,6 +35,33 @@ func UpdateEnvVars(template *servingv1alpha1.RevisionTemplateSpec, vars map[stri return nil } +// Update min and max scale annotation if larger than 0 +func UpdateConcurrencyConfiguration(template *servingv1alpha1.RevisionTemplateSpec, minScale int, maxScale int, target int, limit int) { + if minScale != 0 { + UpdateAnnotation(template, "autoscaling.knative.dev/minScale", strconv.Itoa(minScale)) + } + if maxScale != 0 { + UpdateAnnotation(template, "autoscaling.knative.dev/maxScale", strconv.Itoa(maxScale)) + } + if target != 0 { + UpdateAnnotation(template, "autoscaling.knative.dev/target", strconv.Itoa(target)) + } + + if limit != 0 { + template.Spec.ContainerConcurrency = servingv1beta1.RevisionContainerConcurrencyType(limit) + } +} + +// Updater (or add) an annotation to the given service +func UpdateAnnotation(template *servingv1alpha1.RevisionTemplateSpec, annotation string, value string) { + annoMap := template.Annotations + if annoMap == nil { + annoMap = make(map[string]string) + template.Annotations = annoMap + } + annoMap[annotation] = value +} + // Utility function to translate between the API list form of env vars, and the // more convenient map form. func EnvToMap(vars []corev1.EnvVar) (map[string]string, error) { diff --git a/pkg/serving/config_changes_test.go b/pkg/serving/config_changes_test.go index 35c8d4c13..0e87235a0 100644 --- a/pkg/serving/config_changes_test.go +++ b/pkg/serving/config_changes_test.go @@ -23,6 +23,24 @@ import ( corev1 "k8s.io/api/core/v1" ) +func TestUpdateAutoscalingAnnotations(t *testing.T) { + template := &servingv1alpha1.RevisionTemplateSpec{} + UpdateConcurrencyConfiguration(template, 10, 100, 1000, 1000) + annos := template.Annotations + if annos["autoscaling.knative.dev/minScale"] != "10" { + t.Error("minScale failed") + } + if annos["autoscaling.knative.dev/maxScale"] != "100" { + t.Error("maxScale failed") + } + if annos["autoscaling.knative.dev/target"] != "1000" { + t.Error("target failed") + } + if template.Spec.ContainerConcurrency != 1000 { + t.Error("limit failed") + } +} + func TestUpdateEnvVarsNew(t *testing.T) { template, container := getV1alpha1RevisionTemplateWithOldFields() testUpdateEnvVarsNew(t, template, container) diff --git a/vendor/modules.txt b/vendor/modules.txt index 02aa63c7f..8ffceafca 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -77,13 +77,13 @@ github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1 github.com/knative/serving/pkg/client/clientset/versioned/typed/serving/v1alpha1/fake github.com/knative/serving/pkg/apis/serving github.com/knative/serving/pkg/apis/serving/v1alpha1 +github.com/knative/serving/pkg/apis/serving/v1beta1 github.com/knative/serving/pkg/client/clientset/versioned/scheme github.com/knative/serving/pkg/apis/autoscaling github.com/knative/serving/pkg/apis/networking github.com/knative/serving/pkg/apis/networking/v1alpha1 -github.com/knative/serving/pkg/apis/serving/v1beta1 -github.com/knative/serving/pkg/apis/autoscaling/v1alpha1 github.com/knative/serving/pkg/apis/config +github.com/knative/serving/pkg/apis/autoscaling/v1alpha1 # github.com/knative/test-infra v0.0.0-20190531180034-a3c073a2fea1 github.com/knative/test-infra/scripts # github.com/magiconair/properties v1.8.0