Skip to content

Commit

Permalink
update: Change default to server-side generated revision names (exten…
Browse files Browse the repository at this point in the history
…ded) (#1240)

* update: Change default to server-side generated revision names

Fixes #1144

* update: Fix wait loop for synthetic events for which the generations are already in sync

* fix assertion as a service label change does not create a new revision

* fix: --cluster-local does not create a new revision but just changes a service's label

* chore: Fixed formatting

* update: Check generation in update already before doing a watch

* fixed lint issue

* fix test assertions
  • Loading branch information
rhuss authored Feb 24, 2021
1 parent 09c5129 commit 5f68d61
Show file tree
Hide file tree
Showing 21 changed files with 136 additions and 88 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@
| 🐣
| Do not print `serviceUID` and `configUID` labels in service export results
| https://github.com/knative/client/pull/1194[#1194]

| ✨
| Use server-side generated revision names by default now. For BYO revision names use `--revision-name` for service commands
| https://github.com/knative/client/issues/1240[#1240]
|===

## v0.20.0 (2021-01-14)
Expand Down
2 changes: 1 addition & 1 deletion docs/cmd/kn_service_apply.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ kn service apply s0 --filename my-svc.yml
-p, --port string The port where application listens on, in the format 'NAME:PORT', where 'NAME' is optional. Examples: '--port h2c:8080' , '--port 8080'.
--pull-secret string Image pull secret to set. An empty argument ("") clears the pull secret. The referenced secret must exist in the service's namespace.
--request strings The resource requirement requests for this Service. For example, 'cpu=100m,memory=256Mi'. You can use this flag multiple times. To unset a resource request, append "-" to the resource name, e.g. '--request cpu-'.
--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}}")
--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 (e.g. {{.Service}}-{{.Random 5}}-{{.Generation}})
--scale string Set the Minimum and Maximum number of replicas. You can use this flag to set both to a single value, or set a range with min/max values, or set either min or max values without specifying the other. Example: --scale 5 (scale-min = 5, scale-max = 5) or --scale 1..5 (scale-min = 1, scale-max = 5) or --scale 1.. (scale-min = 1, scale-max = unchanged) or --scale ..5 (scale-min = unchanged, scale-max = 5)
--scale-init int Initial number of replicas with which a service starts. Can be 0 or a positive integer.
--scale-max int Maximum number of replicas.
Expand Down
2 changes: 1 addition & 1 deletion docs/cmd/kn_service_create.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ kn service create NAME --image IMAGE
-p, --port string The port where application listens on, in the format 'NAME:PORT', where 'NAME' is optional. Examples: '--port h2c:8080' , '--port 8080'.
--pull-secret string Image pull secret to set. An empty argument ("") clears the pull secret. The referenced secret must exist in the service's namespace.
--request strings The resource requirement requests for this Service. For example, 'cpu=100m,memory=256Mi'. You can use this flag multiple times. To unset a resource request, append "-" to the resource name, e.g. '--request cpu-'.
--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}}")
--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 (e.g. {{.Service}}-{{.Random 5}}-{{.Generation}})
--scale string Set the Minimum and Maximum number of replicas. You can use this flag to set both to a single value, or set a range with min/max values, or set either min or max values without specifying the other. Example: --scale 5 (scale-min = 5, scale-max = 5) or --scale 1..5 (scale-min = 1, scale-max = 5) or --scale 1.. (scale-min = 1, scale-max = unchanged) or --scale ..5 (scale-min = unchanged, scale-max = 5)
--scale-init int Initial number of replicas with which a service starts. Can be 0 or a positive integer.
--scale-max int Maximum number of replicas.
Expand Down
2 changes: 1 addition & 1 deletion docs/cmd/kn_service_update.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ kn service update NAME
-p, --port string The port where application listens on, in the format 'NAME:PORT', where 'NAME' is optional. Examples: '--port h2c:8080' , '--port 8080'.
--pull-secret string Image pull secret to set. An empty argument ("") clears the pull secret. The referenced secret must exist in the service's namespace.
--request strings The resource requirement requests for this Service. For example, 'cpu=100m,memory=256Mi'. You can use this flag multiple times. To unset a resource request, append "-" to the resource name, e.g. '--request cpu-'.
--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}}")
--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 (e.g. {{.Service}}-{{.Random 5}}-{{.Generation}})
--scale string Set the Minimum and Maximum number of replicas. You can use this flag to set both to a single value, or set a range with min/max values, or set either min or max values without specifying the other. Example: --scale 5 (scale-min = 5, scale-max = 5) or --scale 1..5 (scale-min = 1, scale-max = 5) or --scale 1.. (scale-min = 1, scale-max = unchanged) or --scale ..5 (scale-min = unchanged, scale-max = 5)
--scale-init int Initial number of replicas with which a service starts. Can be 0 or a positive integer.
--scale-max int Maximum number of replicas.
Expand Down
33 changes: 16 additions & 17 deletions pkg/kn/commands/service/configuration_edit_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,6 @@ func (p *ConfigurationEditFlags) addSharedFlags(command *cobra.Command) {

knflags.AddBothBoolFlagsUnhidden(command.Flags(), &p.ClusterLocal, "cluster-local", "", false,
"Specify that the service be private. (--no-cluster-local will make the service publicly available)")
//TODO: Need to also not change revision when already set (solution to issue #646)
p.markFlagMakesRevision("cluster-local")
p.markFlagMakesRevision("no-cluster-local")

command.Flags().IntVar(&p.ConcurrencyTarget, "concurrency-target", 0,
"Recommendation for when to scale up based on the concurrent number of incoming request. "+
Expand Down Expand Up @@ -140,11 +137,12 @@ func (p *ConfigurationEditFlags) addSharedFlags(command *cobra.Command) {
"precedence over the \"label\" flag.")
p.markFlagMakesRevision("label-revision")

command.Flags().StringVar(&p.RevisionName, "revision-name", "{{.Service}}-{{.Random 5}}-{{.Generation}}",
command.Flags().StringVar(&p.RevisionName, "revision-name", "",
"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.")
"{{.Generation}} for the generation, and {{.Random [n]}} for n random consonants "+
"(e.g. {{.Service}}-{{.Random 5}}-{{.Generation}})")
p.markFlagMakesRevision("revision-name")

knflags.AddBothBoolFlagsUnhidden(command.Flags(), &p.LockToDigest, "lock-to-digest", "", true,
Expand Down Expand Up @@ -206,30 +204,31 @@ func (p *ConfigurationEditFlags) Apply(
return err
}

name, err := servinglib.GenerateRevisionName(p.RevisionName, service)
if err != nil {
return err
}
name := ""
if p.RevisionName != "" {
name, err = servinglib.GenerateRevisionName(p.RevisionName, service)
if err != nil {
return err
}

if p.AnyMutation(cmd) {
template.Name = name
if p.AnyMutation(cmd) {
template.Name = name
}
}

imageSet := false
if cmd.Flags().Changed("image") {
imageSet = true
}
_, userImagePresent := template.Annotations[servinglib.UserImageAnnotationKey]
freezeMode := userImagePresent || cmd.Flags().Changed("lock-to-digest")
if p.LockToDigest && p.AnyMutation(cmd) && freezeMode {
servinglib.SetUserImageAnnot(template)
if !imageSet {
if !cmd.Flags().Changed("image") {
err = servinglib.FreezeImageToDigest(template, baseRevision)
if err != nil {
return err
}
}
} else if !p.LockToDigest {
}

if !p.LockToDigest {
servinglib.UnsetUserImageAnnot(template)
}

Expand Down
16 changes: 10 additions & 6 deletions pkg/kn/commands/service/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,14 @@ func createService(client clientservingv1.KnServingClient, service *servingv1.Se
}

func replaceService(client clientservingv1.KnServingClient, service *servingv1.Service, waitFlags commands.WaitFlags, out io.Writer, targetFlag string) error {
err := prepareAndUpdateService(client, service)
changed, err := prepareAndUpdateService(client, service)
if err != nil {
return err
}
if !changed {
fmt.Fprintf(out, "Service '%s' replaced in namespace '%s' (unchanged).\n", service.Name, client.Namespace())
return nil
}
return waitIfRequested(client, waitFlags, service.Name, "Replacing", "replaced", targetFlag, out)
}

Expand All @@ -171,12 +175,12 @@ func waitIfRequested(client clientservingv1.KnServingClient, waitFlags commands.
return waitForServiceToGetReady(client, serviceName, waitFlags.TimeoutInSeconds, verbDone, out)
}

func prepareAndUpdateService(client clientservingv1.KnServingClient, service *servingv1.Service) error {
func prepareAndUpdateService(client clientservingv1.KnServingClient, service *servingv1.Service) (bool, error) {
var retries = 0
for {
existingService, err := client.GetService(service.Name)
if err != nil {
return err
return false, err
}

// Copy over some annotations that we want to keep around. Erase others
Expand All @@ -200,16 +204,16 @@ func prepareAndUpdateService(client clientservingv1.KnServingClient, service *se
}

service.ResourceVersion = existingService.ResourceVersion
err = client.UpdateService(service)
changed, err := client.UpdateService(service)
if err != nil {
// Retry to update when a resource version conflict exists
if apierrors.IsConflict(err) && retries < MaxUpdateRetries {
retries++
continue
}
return err
return changed, err
}
return nil
return changed, nil
}
}

Expand Down
3 changes: 3 additions & 0 deletions pkg/kn/commands/service/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ func TestServiceCreateImage(t *testing.T) {
!strings.Contains(output, commands.FakeNamespace) {
t.Fatalf("wrong stdout message: %v", output)
}

// No revision name set by default
assert.Assert(t, template.Name == "")
}

func TestServiceCreateWithMultipleImages(t *testing.T) {
Expand Down
14 changes: 7 additions & 7 deletions pkg/kn/commands/service/service_update_mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func recordServiceUpdateWithSuccess(r *clientservingv1.ServingRecorder, svcName
r.GetService(svcName, nil, errors.NewNotFound(servingv1.Resource("service"), svcName))
r.CreateService(newService, nil)
r.GetService(svcName, newService, nil)
r.UpdateService(updatedService, nil)
r.UpdateService(updatedService, true, nil)
}

func TestServiceUpdateEnvFromAddingWithConfigMap(t *testing.T) {
Expand Down Expand Up @@ -283,7 +283,7 @@ func TestServiceUpdateEnvFromRemovalWithConfigMap(t *testing.T) {
r.GetService(svcName, updatedService1, nil)
//r.UpdateService(updatedService2, nil) // since an error happens, update is not triggered here
r.GetService(svcName, updatedService2, nil)
r.UpdateService(updatedService3, nil)
r.UpdateService(updatedService3, true, nil)

output, err := executeServiceCommand(client,
"create", svcName, "--image", "gcr.io/foo/bar:baz",
Expand Down Expand Up @@ -372,7 +372,7 @@ func TestServiceUpdateEnvFromRemovalWithEmptyName(t *testing.T) {
r.CreateService(newService, nil)
r.GetService(svcName, newService, nil)
r.GetService(svcName, newService, nil)
r.UpdateService(updatedService1, nil)
r.UpdateService(updatedService1, true, nil)

output, err := executeServiceCommand(client,
"create", svcName, "--image", "gcr.io/foo/bar:baz",
Expand Down Expand Up @@ -625,11 +625,11 @@ func TestServiceUpdateEnvFromRemovalWithSecret(t *testing.T) {
r.GetService(svcName, nil, errors.NewNotFound(servingv1.Resource("service"), svcName))
r.CreateService(newService, nil)
r.GetService(svcName, newService, nil)
r.UpdateService(updatedService1, nil)
r.UpdateService(updatedService1, true, nil)
r.GetService(svcName, updatedService1, nil)
//r.UpdateService(updatedService2, nil) // since an error happens, update is not triggered here
r.GetService(svcName, updatedService2, nil)
r.UpdateService(updatedService3, nil)
r.UpdateService(updatedService3, true, nil)

output, err := executeServiceCommand(client,
"create", svcName, "--image", "gcr.io/foo/bar:baz",
Expand Down Expand Up @@ -1383,9 +1383,9 @@ func TestServiceUpdateWithRemovingMount(t *testing.T) {
r.GetService(svcName, nil, errors.NewNotFound(servingv1.Resource("service"), svcName))
r.CreateService(newService, nil)
r.GetService(svcName, newService, nil)
r.UpdateService(updatedService1, nil)
r.UpdateService(updatedService1, true, nil)
r.GetService(svcName, updatedService1, nil)
r.UpdateService(updatedService2, nil)
r.UpdateService(updatedService2, true, nil)

output, err := executeServiceCommand(client,
"create", svcName, "--image", "gcr.io/foo/bar:baz",
Expand Down
11 changes: 9 additions & 2 deletions pkg/kn/commands/service/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,19 @@ func NewServiceUpdateCommand(p *commands.KnParams) *cobra.Command {
}

// Do the actual update with retry in case of conflicts
err = client.UpdateServiceWithRetry(name, updateFunc, MaxUpdateRetries)
changed, err := client.UpdateServiceWithRetry(name, updateFunc, MaxUpdateRetries)
if err != nil {
return err
}

out := cmd.OutOrStdout()

// No need to wait if not changed
if !changed {
fmt.Fprintf(out, "Service '%s' updated in namespace '%s'.\n", args[0], namespace)
fmt.Fprintln(out, "No new revision has been created.")
return nil
}

if waitFlags.Wait && targetFlag == "" {
fmt.Fprintf(out, "Updating Service '%s' in namespace '%s':\n", args[0], namespace)
fmt.Fprintln(out, "")
Expand Down
24 changes: 22 additions & 2 deletions pkg/kn/commands/service/update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,12 @@ func fakeServiceUpdate(original *servingv1.Service, args []string) (
if !ok {
return true, nil, fmt.Errorf("wrong kind of action %v", action)
}
updated, ok = updateAction.GetObject().(*servingv1.Service)
given, ok := updateAction.GetObject().(*servingv1.Service)
if !ok {
return true, nil, errors.New("was passed the wrong object")
}
updated = given.DeepCopy()
updated.Generation = given.Generation + 1
return true, updated, nil
})
fakeServing.AddReactor("get", "services",
Expand Down Expand Up @@ -271,7 +273,7 @@ func TestServiceUpdateRevisionNameGenerated(t *testing.T) {

// Test prefix added by command
action, updated, _, err := fakeServiceUpdate(orig, []string{
"service", "update", "foo", "--image", "gcr.io/foo/quux:xyzzy", "--namespace", "bar", "--no-wait"})
"service", "update", "foo", "--image", "gcr.io/foo/quux:xyzzy", "--namespace", "bar", "--no-wait", "--revision-name", "{{.Service}}-{{.Random 5}}-{{.Generation}}"})
assert.NilError(t, err)
if !action.Matches("update", "services") {
t.Fatalf("Bad action %v", action)
Expand All @@ -282,6 +284,24 @@ func TestServiceUpdateRevisionNameGenerated(t *testing.T) {
assert.Assert(t, !(template.Name == "foo-asdf"))
}

func TestServiceUpdateRevisionNameDefault(t *testing.T) {
orig := newEmptyService()

template := orig.Spec.Template
template.Name = "foo-asdf"

// Test prefix added by command
action, updated, _, err := fakeServiceUpdate(orig, []string{
"service", "update", "foo", "--image", "gcr.io/foo/quux:xyzzy"})
assert.NilError(t, err)
if !action.Matches("update", "services") {
t.Fatalf("Bad action %v", action)
}

template = updated.Spec.Template
assert.Assert(t, cmp.Equal(template.Name, ""))
}

func TestServiceUpdateRevisionNameCleared(t *testing.T) {
orig := newEmptyService()

Expand Down
31 changes: 16 additions & 15 deletions pkg/serving/v1/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,12 @@ type KnServingClient interface {

// UpdateService updates the given service. For a more robust variant with automatic
// conflict resolution see UpdateServiceWithRetry
UpdateService(service *servingv1.Service) error
UpdateService(service *servingv1.Service) (bool, error)

// UpdateServiceWithRetry updates service and retries if there is a version conflict.
// The updateFunc receives a deep copy of the existing service and can add update it in
// place.
UpdateServiceWithRetry(name string, updateFunc ServiceUpdateFunc, nrRetries int) error
// place. Return if the service creates a new generation or not
UpdateServiceWithRetry(name string, updateFunc ServiceUpdateFunc, nrRetries int) (bool, error)

// Apply a service's definition to the cluster. The full service declaration needs to be provided,
// which is different to UpdateService which can also do a partial update. If the given
Expand Down Expand Up @@ -241,36 +241,37 @@ func (cl *knServingClient) CreateService(service *servingv1.Service) error {
}

// Update the given service
func (cl *knServingClient) UpdateService(service *servingv1.Service) error {
_, err := cl.client.Services(cl.namespace).Update(context.TODO(), service, v1.UpdateOptions{})
func (cl *knServingClient) UpdateService(service *servingv1.Service) (bool, error) {
updated, err := cl.client.Services(cl.namespace).Update(context.TODO(), service, v1.UpdateOptions{})
if err != nil {
return err
return false, err
}
return updateServingGvk(service)
changed := service.ObjectMeta.Generation != updated.ObjectMeta.Generation
return changed, updateServingGvk(service)
}

// Update the given service with a retry in case of a conflict
func (cl *knServingClient) UpdateServiceWithRetry(name string, updateFunc ServiceUpdateFunc, nrRetries int) error {
func (cl *knServingClient) UpdateServiceWithRetry(name string, updateFunc ServiceUpdateFunc, nrRetries int) (bool, error) {
return updateServiceWithRetry(cl, name, updateFunc, nrRetries)
}

// Extracted to be usable with the Mocking client
func updateServiceWithRetry(cl KnServingClient, name string, updateFunc ServiceUpdateFunc, nrRetries int) error {
func updateServiceWithRetry(cl KnServingClient, name string, updateFunc ServiceUpdateFunc, nrRetries int) (bool, error) {
var retries = 0
for {
service, err := cl.GetService(name)
if err != nil {
return err
return false, err
}
if service.GetDeletionTimestamp() != nil {
return fmt.Errorf("can't update service %s because it has been marked for deletion", name)
return false, fmt.Errorf("can't update service %s because it has been marked for deletion", name)
}
updatedService, err := updateFunc(service.DeepCopy())
if err != nil {
return err
return false, err
}

err = cl.UpdateService(updatedService)
changed, err := cl.UpdateService(updatedService)
if err != nil {
// Retry to update when a resource version conflict exists
if apierrors.IsConflict(err) && retries < nrRetries {
Expand All @@ -279,9 +280,9 @@ func updateServiceWithRetry(cl KnServingClient, name string, updateFunc ServiceU
time.Sleep(time.Second)
continue
}
return fmt.Errorf("giving up after %d retries: %w", nrRetries, err)
return false, fmt.Errorf("giving up after %d retries: %w", nrRetries, err)
}
return nil
return changed, nil
}
}

Expand Down
Loading

0 comments on commit 5f68d61

Please sign in to comment.