diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 298a26f3be..9af3bc19da 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -18,6 +18,10 @@ |=== | | Description | PR +| 🎁 +| Add target-utilization option for `service create` and `service update` (`--target-utilization`) +| https://github.com/knative/client/pull/788[#788] + | 🐣 | Refactor `e2e` common code into `lib/test` | https://github.com/knative/client/pull/765[#765] diff --git a/docs/cmd/kn_service_create.md b/docs/cmd/kn_service_create.md index 834e94f1a9..8e6bd5105f 100644 --- a/docs/cmd/kn_service_create.md +++ b/docs/cmd/kn_service_create.md @@ -77,6 +77,7 @@ kn service create NAME --image IMAGE [flags] --requests-memory string The requested memory (e.g., 64Mi). --revision-name string The revision name to set. Must start with the service name and a dash as a prefix. Empty revision name will result in the server generating a name for the revision. Accepts golang templates, allowing {{.Service}} for the service name, {{.Generation}} for the generation, and {{.Random [n]}} for n random consonants. (default "{{.Service}}-{{.Random 5}}-{{.Generation}}") --service-account string Service account name to set. An empty argument ("") clears the service account. The referenced service account must exist in the service's namespace. + --target-utilization int Percentage of concurrent requests utilization before scaling up (default 70) --user int The user ID to run the container (e.g., 1001). --volume stringArray Add a volume from a ConfigMap (prefix cm: or config-map:) or a Secret (prefix secret: or sc:). Example: --volume myvolume=cm:myconfigmap or --volume myvolume=secret:mysecret. You can use this flag multiple times. To unset a ConfigMap/Secret reference, append "-" to the name, e.g. --volume myvolume-. --wait-timeout int Seconds to wait before giving up on waiting for service to be ready. (default 600) diff --git a/docs/cmd/kn_service_update.md b/docs/cmd/kn_service_update.md index b6afaa9107..fc00e3c14e 100644 --- a/docs/cmd/kn_service_update.md +++ b/docs/cmd/kn_service_update.md @@ -72,6 +72,7 @@ kn service update NAME [flags] --tag strings Set tag (format: --tag revisionRef=tagName) where revisionRef can be a revision or '@latest' string representing latest ready revision. This flag can be specified multiple times. --traffic strings Set traffic distribution (format: --traffic revisionRef=percent) where revisionRef can be a revision or a tag or '@latest' string representing latest ready revision. This flag can be given multiple times with percent summing up to 100%. --untag strings Untag revision (format: --untag tagName). This flag can be specified multiple times. + --target-utilization int Percentage of concurrent requests utilization before scaling up (default 70) --user int The user ID to run the container (e.g., 1001). --volume stringArray Add a volume from a ConfigMap (prefix cm: or config-map:) or a Secret (prefix secret: or sc:). Example: --volume myvolume=cm:myconfigmap or --volume myvolume=secret:mysecret. You can use this flag multiple times. To unset a ConfigMap/Secret reference, append "-" to the name, e.g. --volume myvolume-. --wait-timeout int Seconds to wait before giving up on waiting for service to be ready. (default 600) diff --git a/pkg/kn/commands/revision/describe.go b/pkg/kn/commands/revision/describe.go index b6f9119489..909f7e20ea 100644 --- a/pkg/kn/commands/revision/describe.go +++ b/pkg/kn/commands/revision/describe.go @@ -131,7 +131,8 @@ func WriteConcurrencyOptions(dw printers.PrefixWriter, revision *servingv1.Revis target := clientserving.ConcurrencyTarget(&revision.ObjectMeta) limit := revision.Spec.ContainerConcurrency autoscaleWindow := clientserving.AutoscaleWindow(&revision.ObjectMeta) - if target != nil || limit != nil && *limit != 0 || autoscaleWindow != "" { + targetUtilization := clientserving.ConcurrencyTargetUtilization(&revision.ObjectMeta) + if target != nil || limit != nil && *limit != 0 || autoscaleWindow != "" || targetUtilization != nil { section := dw.WriteAttribute("Concurrency", "") if limit != nil && *limit != 0 { section.WriteAttribute("Limit", strconv.FormatInt(int64(*limit), 10)) @@ -142,6 +143,9 @@ func WriteConcurrencyOptions(dw printers.PrefixWriter, revision *servingv1.Revis if autoscaleWindow != "" { section.WriteAttribute("Window", autoscaleWindow) } + if targetUtilization != nil { + section.WriteAttribute("TargetUtilization", strconv.Itoa(*targetUtilization)) + } } } diff --git a/pkg/kn/commands/service/configuration_edit_flags.go b/pkg/kn/commands/service/configuration_edit_flags.go index 26a1516f2d..d92426879e 100644 --- a/pkg/kn/commands/service/configuration_edit_flags.go +++ b/pkg/kn/commands/service/configuration_edit_flags.go @@ -48,6 +48,7 @@ type ConfigurationEditFlags struct { MaxScale int ConcurrencyTarget int ConcurrencyLimit int + TargetUtilization int AutoscaleWindow string Port int32 Labels []string @@ -180,6 +181,10 @@ func (p *ConfigurationEditFlags) addSharedFlags(command *cobra.Command) { "Hard Limit of concurrent requests to be processed by a single replica.") p.markFlagMakesRevision("concurrency-limit") + command.Flags().IntVar(&p.TargetUtilization, "target-utilization", 70, + "Percentage of concurrent requests utilization before scaling up") + p.markFlagMakesRevision("target-utilization") + command.Flags().Int32VarP(&p.Port, "port", "p", 0, "The port where application listens on.") p.markFlagMakesRevision("port") @@ -404,6 +409,13 @@ func (p *ConfigurationEditFlags) Apply( } } + if cmd.Flags().Changed("target-utilization") { + err = servinglib.UpdateTargetUtilization(template, p.TargetUtilization) + if err != nil { + return err + } + } + if cmd.Flags().Changed("cluster-local") || cmd.Flags().Changed("no-cluster-local") { if p.ClusterLocal { labels := servinglib.UpdateLabels(service.ObjectMeta.Labels, map[string]string{serving.VisibilityLabelKey: serving.VisibilityClusterLocal}, []string{}) diff --git a/pkg/kn/commands/service/create_test.go b/pkg/kn/commands/service/create_test.go index 0ade9c327c..e3ee9be9ba 100644 --- a/pkg/kn/commands/service/create_test.go +++ b/pkg/kn/commands/service/create_test.go @@ -370,6 +370,7 @@ func TestServiceCreateMaxMinScale(t *testing.T) { "service", "create", "foo", "--image", "gcr.io/foo/bar:baz", "--min-scale", "1", "--max-scale", "5", "--concurrency-target", "10", "--concurrency-limit", "100", + "--target-utilization", "50", "--no-wait"}, false) if err != nil { @@ -385,6 +386,7 @@ func TestServiceCreateMaxMinScale(t *testing.T) { "autoscaling.knative.dev/minScale", "1", "autoscaling.knative.dev/maxScale", "5", "autoscaling.knative.dev/target", "10", + "autoscaling.knative.dev/targetUtilizationPercentage", "50", } for i := 0; i < len(expectedAnnos); i += 2 { diff --git a/pkg/kn/commands/service/update_test.go b/pkg/kn/commands/service/update_test.go index bb48aad86f..16d162327a 100644 --- a/pkg/kn/commands/service/update_test.go +++ b/pkg/kn/commands/service/update_test.go @@ -311,7 +311,7 @@ func TestServiceUpdateMaxMinScale(t *testing.T) { action, updated, _, err := fakeServiceUpdate(original, []string{ "service", "update", "foo", - "--min-scale", "1", "--max-scale", "5", "--concurrency-target", "10", "--concurrency-limit", "100", "--no-wait"}) + "--min-scale", "1", "--max-scale", "5", "--concurrency-target", "10", "--concurrency-limit", "100", "--target-utilization", "50", "--no-wait"}) if err != nil { t.Fatal(err) @@ -329,6 +329,7 @@ func TestServiceUpdateMaxMinScale(t *testing.T) { "autoscaling.knative.dev/minScale", "1", "autoscaling.knative.dev/maxScale", "5", "autoscaling.knative.dev/target", "10", + "autoscaling.knative.dev/targetUtilizationPercentage", "50", } for i := 0; i < len(expectedAnnos); i += 2 { diff --git a/pkg/serving/config_changes.go b/pkg/serving/config_changes.go index b91e2612e2..3b12bf99b7 100644 --- a/pkg/serving/config_changes.go +++ b/pkg/serving/config_changes.go @@ -198,6 +198,11 @@ func UpdateConcurrencyTarget(template *servingv1.RevisionTemplateSpec, target in return UpdateRevisionTemplateAnnotation(template, autoscaling.TargetAnnotationKey, strconv.Itoa(target)) } +// UpdateTargetUtilization updates container target utilization percentage annotation +func UpdateTargetUtilization(template *servingv1.RevisionTemplateSpec, target int) error { + return UpdateRevisionTemplateAnnotation(template, autoscaling.TargetUtilizationPercentageKey, strconv.Itoa(target)) +} + // UpdateConcurrencyLimit updates container concurrency limit func UpdateConcurrencyLimit(template *servingv1.RevisionTemplateSpec, limit int64) error { if limit < 0 { diff --git a/pkg/serving/revision_template.go b/pkg/serving/revision_template.go index a42add6e37..553ebc9e95 100644 --- a/pkg/serving/revision_template.go +++ b/pkg/serving/revision_template.go @@ -63,6 +63,11 @@ func ConcurrencyTarget(m *metav1.ObjectMeta) *int { return ret } +func ConcurrencyTargetUtilization(m *metav1.ObjectMeta) *int { + ret, _ := annotationAsInt(m, autoscaling.TargetUtilizationPercentageKey) + return ret +} + func AutoscaleWindow(m *metav1.ObjectMeta) string { return m.Annotations[autoscaling.WindowAnnotationKey] } diff --git a/test/e2e/service_options_test.go b/test/e2e/service_options_test.go index 3de24a8599..b142d432c3 100644 --- a/test/e2e/service_options_test.go +++ b/test/e2e/service_options_test.go @@ -46,9 +46,10 @@ func TestServiceOptions(t *testing.T) { t.Log("create and validate service with concurrency options") defer r.DumpIfFailed() - serviceCreateWithOptions(t, it, r, "svc1", "--concurrency-limit", "250", "--concurrency-target", "300") + serviceCreateWithOptions(t, it, r, "svc1", "--concurrency-limit", "250", "--concurrency-target", "300", "--target-utilization", "50") validateServiceConcurrencyTarget(t, it, r, "svc1", "300") validateServiceConcurrencyLimit(t, it, r, "svc1", "250") + validateServiceTargetUtilization(t, it, r, "svc1", "50") t.Log("update and validate service with concurrency limit") serviceUpdate(t, it, r, "svc1", "--concurrency-limit", "300") @@ -62,6 +63,7 @@ func TestServiceOptions(t *testing.T) { t.Log("returns steady concurrency options for service") validateServiceConcurrencyLimit(t, it, r, "svc1", "300") validateServiceConcurrencyTarget(t, it, r, "svc1", "300") + validateServiceTargetUtilization(t, it, r, "svc1", "50") t.Log("delete service") serviceDelete(t, it, r, "svc1") @@ -134,6 +136,13 @@ func validateServiceConcurrencyTarget(t *testing.T, it *test.KnTest, r *test.KnR r.AssertNoError(out) } +func validateServiceTargetUtilization(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName, targetUtilization string) { + jsonpath := "jsonpath={.items[0].spec.template.metadata.annotations.autoscaling\\.knative\\.dev/targetUtilizationPercentage}" + out := it.Kn().Run("service", "list", serviceName, "-o", jsonpath) + assert.Equal(t, out.Stdout, targetUtilization) + r.AssertNoError(out) +} + func validateAutoscaleWindow(t *testing.T, it *test.KnTest, r *test.KnRunResultCollector, serviceName, window string) { jsonpath := "jsonpath={.items[0].spec.template.metadata.annotations.autoscaling\\.knative\\.dev/window}" out := it.Kn().Run("service", "list", serviceName, "-o", jsonpath)