feature(serving): Add --autoscale-window (#614)

* feature(serving): Add --autoscale-window

Fixes #613

* chore: Update help-text for --autoscale-window

* chore: Add missing generated files
This commit is contained in:
Roland Huß 2020-01-22 09:18:23 +01:00 committed by Knative Prow Robot
parent 9fd763e5dd
commit 5001dcdc16
8 changed files with 121 additions and 57 deletions

View File

@ -44,6 +44,7 @@ kn service create NAME --image IMAGE [flags]
``` ```
--annotation stringArray Service annotation to set. name=value; you may provide this flag any number of times to set multiple annotations. To unset, specify the annotation name followed by a "-" (e.g., name-). --annotation stringArray Service annotation to set. name=value; you may provide this flag any number of times to set multiple annotations. To unset, specify the annotation name followed by a "-" (e.g., name-).
--async Create service and don't wait for it to become ready. --async Create service and don't wait for it to become ready.
--autoscale-window string Duration to look back for making auto-scaling decisions. The service is scaled to zero if no request was received in during that time. (eg: 10s)
--concurrency-limit int Hard Limit of concurrent requests to be processed by a single replica. --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. --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. To unset, specify the environment variable name followed by a "-" (e.g., NAME-). -e, --env stringArray Environment variable to set. NAME=value; you may provide this flag any number of times to set multiple environment variables. To unset, specify the environment variable name followed by a "-" (e.g., NAME-).

View File

@ -40,6 +40,7 @@ kn service update NAME [flags]
``` ```
--annotation stringArray Service annotation to set. name=value; you may provide this flag any number of times to set multiple annotations. To unset, specify the annotation name followed by a "-" (e.g., name-). --annotation stringArray Service annotation to set. name=value; you may provide this flag any number of times to set multiple annotations. To unset, specify the annotation name followed by a "-" (e.g., name-).
--async Update service and don't wait for it to become ready. --async Update service and don't wait for it to become ready.
--autoscale-window string Duration to look back for making auto-scaling decisions. The service is scaled to zero if no request was received in during that time. (eg: 10s)
--concurrency-limit int Hard Limit of concurrent requests to be processed by a single replica. --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. --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. To unset, specify the environment variable name followed by a "-" (e.g., NAME-). -e, --env stringArray Environment variable to set. NAME=value; you may provide this flag any number of times to set multiple environment variables. To unset, specify the environment variable name followed by a "-" (e.g., NAME-).

View File

@ -129,7 +129,8 @@ func describe(w io.Writer, revision *v1alpha1.Revision, service *v1alpha1.Servic
func WriteConcurrencyOptions(dw printers.PrefixWriter, revision *v1alpha1.Revision) { func WriteConcurrencyOptions(dw printers.PrefixWriter, revision *v1alpha1.Revision) {
target := clientserving.ConcurrencyTarget(&revision.ObjectMeta) target := clientserving.ConcurrencyTarget(&revision.ObjectMeta)
limit := revision.Spec.ContainerConcurrency limit := revision.Spec.ContainerConcurrency
if target != nil || limit != nil && *limit != 0 { autoscaleWindow := clientserving.AutoscaleWindow(&revision.ObjectMeta)
if target != nil || limit != nil && *limit != 0 || autoscaleWindow != "" {
section := dw.WriteAttribute("Concurrency", "") section := dw.WriteAttribute("Concurrency", "")
if limit != nil && *limit != 0 { if limit != nil && *limit != 0 {
section.WriteAttribute("Limit", strconv.FormatInt(int64(*limit), 10)) section.WriteAttribute("Limit", strconv.FormatInt(int64(*limit), 10))
@ -137,7 +138,11 @@ func WriteConcurrencyOptions(dw printers.PrefixWriter, revision *v1alpha1.Revisi
if target != nil { if target != nil {
section.WriteAttribute("Target", strconv.Itoa(*target)) section.WriteAttribute("Target", strconv.Itoa(*target))
} }
if autoscaleWindow != "" {
section.WriteAttribute("Window", autoscaleWindow)
} }
}
} }
// Write the image attribute (with // Write the image attribute (with

View File

@ -41,6 +41,7 @@ type ConfigurationEditFlags struct {
MaxScale int MaxScale int
ConcurrencyTarget int ConcurrencyTarget int
ConcurrencyLimit int ConcurrencyLimit int
AutoscaleWindow string
Port int32 Port int32
Labels []string Labels []string
NamePrefix string NamePrefix string
@ -113,6 +114,8 @@ func (p *ConfigurationEditFlags) addSharedFlags(command *cobra.Command) {
p.markFlagMakesRevision("min-scale") p.markFlagMakesRevision("min-scale")
command.Flags().IntVar(&p.MaxScale, "max-scale", 0, "Maximal number of replicas.") command.Flags().IntVar(&p.MaxScale, "max-scale", 0, "Maximal number of replicas.")
p.markFlagMakesRevision("max-scale") p.markFlagMakesRevision("max-scale")
command.Flags().StringVar(&p.AutoscaleWindow, "autoscale-window", "", "Duration to look back for making auto-scaling decisions. The service is scaled to zero if no request was received in during that time. (eg: 10s)")
p.markFlagMakesRevision("autoscale-window")
command.Flags().IntVar(&p.ConcurrencyTarget, "concurrency-target", 0, command.Flags().IntVar(&p.ConcurrencyTarget, "concurrency-target", 0,
"Recommendation for when to scale up based on the concurrent number of incoming request. "+ "Recommendation for when to scale up based on the concurrent number of incoming request. "+
"Defaults to --concurrency-limit when given.") "Defaults to --concurrency-limit when given.")
@ -290,6 +293,13 @@ func (p *ConfigurationEditFlags) Apply(
} }
} }
if cmd.Flags().Changed("autoscale-window") {
err = servinglib.UpdateAutoscaleWindow(template, p.AutoscaleWindow)
if err != nil {
return err
}
}
if cmd.Flags().Changed("concurrency-target") { if cmd.Flags().Changed("concurrency-target") {
err = servinglib.UpdateConcurrencyTarget(template, p.ConcurrencyTarget) err = servinglib.UpdateConcurrencyTarget(template, p.ConcurrencyTarget)
if err != nil { if err != nil {

View File

@ -21,6 +21,7 @@ import (
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"time"
"unicode" "unicode"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
@ -183,6 +184,15 @@ func UpdateMaxScale(template *servingv1alpha1.RevisionTemplateSpec, max int) err
return UpdateRevisionTemplateAnnotation(template, autoscaling.MaxScaleAnnotationKey, strconv.Itoa(max)) return UpdateRevisionTemplateAnnotation(template, autoscaling.MaxScaleAnnotationKey, strconv.Itoa(max))
} }
// UpdateAutoscaleWindow updates the autoscale window annotation
func UpdateAutoscaleWindow(template *servingv1alpha1.RevisionTemplateSpec, window string) error {
_, err := time.ParseDuration(window)
if err != nil {
return fmt.Errorf("invalid duration for 'autoscale-window': %v", err)
}
return UpdateRevisionTemplateAnnotation(template, autoscaling.WindowAnnotationKey, window)
}
// UpdateConcurrencyTarget updates container concurrency annotation // UpdateConcurrencyTarget updates container concurrency annotation
func UpdateConcurrencyTarget(template *servingv1alpha1.RevisionTemplateSpec, target int) error { func UpdateConcurrencyTarget(template *servingv1alpha1.RevisionTemplateSpec, target int) error {
return UpdateRevisionTemplateAnnotation(template, autoscaling.TargetAnnotationKey, strconv.Itoa(target)) return UpdateRevisionTemplateAnnotation(template, autoscaling.TargetAnnotationKey, strconv.Itoa(target))

View File

@ -225,7 +225,7 @@ func TestUpdateMinScale(t *testing.T) {
err := UpdateMinScale(template, 10) err := UpdateMinScale(template, 10)
assert.NilError(t, err) assert.NilError(t, err)
// Verify update is successful or not // Verify update is successful or not
checkAnnotationValue(t, template, autoscaling.MinScaleAnnotationKey, 10) checkAnnotationValueInt(t, template, autoscaling.MinScaleAnnotationKey, 10)
// Update with invalid value // Update with invalid value
err = UpdateMinScale(template, -1) err = UpdateMinScale(template, -1)
assert.ErrorContains(t, err, "minScale") assert.ErrorContains(t, err, "minScale")
@ -236,18 +236,29 @@ func TestUpdateMaxScale(t *testing.T) {
err := UpdateMaxScale(template, 10) err := UpdateMaxScale(template, 10)
assert.NilError(t, err) assert.NilError(t, err)
// Verify update is successful or not // Verify update is successful or not
checkAnnotationValue(t, template, autoscaling.MaxScaleAnnotationKey, 10) checkAnnotationValueInt(t, template, autoscaling.MaxScaleAnnotationKey, 10)
// Update with invalid value // Update with invalid value
err = UpdateMaxScale(template, -1) err = UpdateMaxScale(template, -1)
assert.ErrorContains(t, err, "maxScale") assert.ErrorContains(t, err, "maxScale")
} }
func TestAutoscaleWindow(t *testing.T) {
template, _ := getV1alpha1RevisionTemplateWithOldFields()
err := UpdateAutoscaleWindow(template, "10s")
assert.NilError(t, err)
// Verify update is successful or not
checkAnnotationValue(t, template, autoscaling.WindowAnnotationKey, "10s")
// Update with invalid value
err = UpdateAutoscaleWindow(template, "blub")
assert.Check(t, util.ContainsAll(err.Error(), "invalid duration", "autoscale-window"))
}
func TestUpdateConcurrencyTarget(t *testing.T) { func TestUpdateConcurrencyTarget(t *testing.T) {
template, _ := getV1alpha1RevisionTemplateWithOldFields() template, _ := getV1alpha1RevisionTemplateWithOldFields()
err := UpdateConcurrencyTarget(template, 10) err := UpdateConcurrencyTarget(template, 10)
assert.NilError(t, err) assert.NilError(t, err)
// Verify update is successful or not // Verify update is successful or not
checkAnnotationValue(t, template, autoscaling.TargetAnnotationKey, 10) checkAnnotationValueInt(t, template, autoscaling.TargetAnnotationKey, 10)
// Update with invalid value // Update with invalid value
err = UpdateConcurrencyTarget(template, -1) err = UpdateConcurrencyTarget(template, -1)
assert.ErrorContains(t, err, "invalid") assert.ErrorContains(t, err, "invalid")
@ -708,13 +719,20 @@ func assertNoV1alpha1(t *testing.T, template *servingv1alpha1.RevisionTemplateSp
} }
} }
func checkAnnotationValue(t *testing.T, template *servingv1alpha1.RevisionTemplateSpec, key string, value int) { func checkAnnotationValueInt(t *testing.T, template *servingv1alpha1.RevisionTemplateSpec, key string, value int) {
anno := template.GetAnnotations() anno := template.GetAnnotations()
if v, ok := anno[key]; !ok && v != strconv.Itoa(value) { if v, ok := anno[key]; !ok && v != strconv.Itoa(value) {
t.Errorf("Failed to update %s annotation key: got=%s, want=%d", key, v, value) t.Errorf("Failed to update %s annotation key: got=%s, want=%d", key, v, value)
} }
} }
func checkAnnotationValue(t *testing.T, template *servingv1alpha1.RevisionTemplateSpec, key string, value string) {
anno := template.GetAnnotations()
if v, ok := anno[key]; !ok && v != value {
t.Errorf("Failed to update %s annotation key: got=%s, want=%s", key, v, value)
}
}
func checkContainerConcurrency(t *testing.T, template *servingv1alpha1.RevisionTemplateSpec, value *int64) { func checkContainerConcurrency(t *testing.T, template *servingv1alpha1.RevisionTemplateSpec, value *int64) {
if got, want := *template.Spec.ContainerConcurrency, *value; got != want { if got, want := *template.Spec.ContainerConcurrency, *value; got != want {
t.Errorf("Failed to update containerConcurrency value: got=%d, want=%d", got, want) t.Errorf("Failed to update containerConcurrency value: got=%d, want=%d", got, want)

View File

@ -67,6 +67,10 @@ func ConcurrencyTarget(m *metav1.ObjectMeta) *int {
return ret return ret
} }
func AutoscaleWindow(m *metav1.ObjectMeta) string {
return m.Annotations[autoscaling.WindowAnnotationKey]
}
func Port(revisionSpec *servingv1alpha1.RevisionSpec) *int32 { func Port(revisionSpec *servingv1alpha1.RevisionSpec) *int32 {
c, err := ContainerOfRevisionSpec(revisionSpec) c, err := ContainerOfRevisionSpec(revisionSpec)
if err != nil { if err != nil {

View File

@ -79,6 +79,14 @@ func TestServiceOptions(t *testing.T) {
test.validateServiceAnnotations(t, "svc3", map[string]string{"alpha": "direwolf", "brave": ""}) test.validateServiceAnnotations(t, "svc3", map[string]string{"alpha": "direwolf", "brave": ""})
test.serviceDelete(t, "svc3") test.serviceDelete(t, "svc3")
}) })
t.Run("create, update and validate service with autoscale window option", func(t *testing.T) {
test.serviceCreateWithOptions(t, "svc4", []string{"--autoscale-window", "1m"})
test.validateAutoscaleWindow(t, "svc4", "1m")
test.serviceUpdate(t, "svc4", []string{"--autoscale-window", "15s"})
test.validateAutoscaleWindow(t, "svc4", "15s")
test.serviceDelete(t, "svc4")
})
} }
func (test *e2eTest) serviceCreateWithOptions(t *testing.T, serviceName string, options []string) { func (test *e2eTest) serviceCreateWithOptions(t *testing.T, serviceName string, options []string) {
@ -121,6 +129,13 @@ func (test *e2eTest) validateServiceConcurrencyTarget(t *testing.T, serviceName,
} }
} }
func (test *e2eTest) validateAutoscaleWindow(t *testing.T, serviceName, window string) {
jsonpath := "jsonpath={.items[0].spec.template.metadata.annotations.autoscaling\\.knative\\.dev/window}"
out, err := test.kn.RunWithOpts([]string{"service", "list", serviceName, "-o", jsonpath}, runOpts{})
assert.NilError(t, err)
assert.Equal(t, out, window)
}
func (test *e2eTest) validateServiceMinScale(t *testing.T, serviceName, minScale string) { func (test *e2eTest) validateServiceMinScale(t *testing.T, serviceName, minScale string) {
jsonpath := "jsonpath={.items[0].spec.template.metadata.annotations.autoscaling\\.knative\\.dev/minScale}" jsonpath := "jsonpath={.items[0].spec.template.metadata.annotations.autoscaling\\.knative\\.dev/minScale}"
out, err := test.kn.RunWithOpts([]string{"service", "list", serviceName, "-o", jsonpath}, runOpts{}) out, err := test.kn.RunWithOpts([]string{"service", "list", serviceName, "-o", jsonpath}, runOpts{})