Merge "services/device/device: globify revert command"
diff --git a/services/device/device/devicemanager_mock_test.go b/services/device/device/devicemanager_mock_test.go
index 2d34144..829f77f 100644
--- a/services/device/device/devicemanager_mock_test.go
+++ b/services/device/device/devicemanager_mock_test.go
@@ -205,7 +205,9 @@
return mdi.simpleCore("Run", "Run")
}
-func (i *mockDeviceInvoker) Revert(*context.T, rpc.ServerCall) error { return nil }
+func (mdi *mockDeviceInvoker) Revert(*context.T, rpc.ServerCall) error {
+ return mdi.simpleCore("Revert", "Revert")
+}
type InstantiateResponse struct {
err error
diff --git a/services/device/device/doc.go b/services/device/device/doc.go
index 881e621..54a832c 100644
--- a/services/device/device/doc.go
+++ b/services/device/device/doc.go
@@ -23,8 +23,8 @@
delete Delete the given application instance.
run Run the given application instance.
kill Kill the given application instance.
- revert Revert the device manager or application
- update Update device manager or applications.
+ revert Revert the device manager or applications.
+ update Update the device manager or applications.
status Get device manager or application status.
debug Debug the device.
acl Tool for setting device manager Permissions
@@ -283,24 +283,24 @@
Device revert
-Revert the device manager or application to its previous version
+Revert the device manager or application instances and installations to a
+previous version of their current version
Usage:
- device revert <object>
+ device revert <name patterns...>
-<object> is the vanadium object name of the device manager or application
-installation to revert.
+<name patterns...> are vanadium object names or glob name patterns corresponding
+to the device manager service, or to application installations and instances.
Device update
Update the device manager or application instances and installations
Usage:
- device update <app name patterns...>
+ device update <name patterns...>
-<app name patterns...> are vanadium object names or glob name patterns
-corresponding to the device manager service, or to application installations and
-instances.
+<name patterns...> are vanadium object names or glob name patterns corresponding
+to the device manager service, or to application installations and instances.
Device status
diff --git a/services/device/device/revert.go b/services/device/device/revert.go
deleted file mode 100644
index 0f27078..0000000
--- a/services/device/device/revert.go
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package main
-
-import (
- "fmt"
-
- "v.io/v23/context"
- "v.io/v23/services/device"
- "v.io/x/lib/cmdline"
- "v.io/x/ref/lib/v23cmd"
-)
-
-var cmdRevert = &cmdline.Command{
- Runner: v23cmd.RunnerFunc(runRevert),
- Name: "revert",
- Short: "Revert the device manager or application",
- Long: "Revert the device manager or application to its previous version",
- ArgsName: "<object>",
- ArgsLong: `
-<object> is the vanadium object name of the device manager or application
-installation to revert.`,
-}
-
-func runRevert(ctx *context.T, env *cmdline.Env, args []string) error {
- if expected, got := 1, len(args); expected != got {
- return env.UsageErrorf("revert: incorrect number of arguments, expected %d, got %d", expected, got)
- }
- deviceName := args[0]
- if err := device.ApplicationClient(deviceName).Revert(ctx); err != nil {
- return err
- }
- fmt.Fprintln(env.Stdout, "Revert successful.")
- return nil
-}
diff --git a/services/device/device/update.go b/services/device/device/update.go
index e6cb007..e2d1f9a 100644
--- a/services/device/device/update.go
+++ b/services/device/device/update.go
@@ -4,6 +4,8 @@
package main
+// TODO(caprita): Rename to update_revert.go
+
import (
"fmt"
"io"
@@ -20,11 +22,21 @@
var cmdUpdate = &cmdline.Command{
Runner: globRunner(runUpdate),
Name: "update",
- Short: "Update device manager or applications.",
+ Short: "Update the device manager or applications.",
Long: "Update the device manager or application instances and installations",
- ArgsName: "<app name patterns...>",
+ ArgsName: "<name patterns...>",
ArgsLong: `
-<app name patterns...> are vanadium object names or glob name patterns corresponding to the device manager service, or to application installations and instances.`,
+<name patterns...> are vanadium object names or glob name patterns corresponding to the device manager service, or to application installations and instances.`,
+}
+
+var cmdRevert = &cmdline.Command{
+ Runner: globRunner(runRevert),
+ Name: "revert",
+ Short: "Revert the device manager or applications.",
+ Long: "Revert the device manager or application instances and installations to a previous version of their current version",
+ ArgsName: "<name patterns...>",
+ ArgsLong: `
+<name patterns...> are vanadium object names or glob name patterns corresponding to the device manager service, or to application installations and instances.`,
}
func instanceIsRunning(ctx *context.T, von string) (bool, error) {
@@ -39,7 +51,11 @@
return s.Value.State == device.InstanceStateRunning, nil
}
-func updateInstance(ctx *context.T, stdout, stderr io.Writer, name string, status device.StatusInstance) (retErr error) {
+var revertOrUpdate = map[bool]string{true: "revert", false: "update"}
+var revertOrUpdateMethod = map[bool]string{true: "Revert", false: "Update"}
+var revertOrUpdateNoOp = map[bool]string{true: "no previous version available", false: "already up to date"}
+
+func changeVersionInstance(ctx *context.T, stdout, stderr io.Writer, name string, status device.StatusInstance, revert bool) (retErr error) {
if status.Value.State == device.InstanceStateRunning {
if err := device.ApplicationClient(name).Kill(ctx, killDeadline); err != nil {
// Check the app's state again in case we killed it,
@@ -54,10 +70,10 @@
if running {
return fmt.Errorf("Kill failed: %v", err)
}
- fmt.Fprintf(stderr, "WARNING for \"%s\": recovered from Kill error (%s). Proceeding with update.\n", name, err)
+ fmt.Fprintf(stderr, "WARNING for \"%s\": recovered from Kill error (%s). Proceeding with %s.\n", name, err, revertOrUpdate[revert])
}
// App was running, and we killed it, so we need to run it again
- // after the update.
+ // after the update/revert.
defer func() {
if err := device.ApplicationClient(name).Run(ctx); err != nil {
err = fmt.Errorf("Run failed: %v", err)
@@ -69,43 +85,63 @@
}
}()
}
- // Update the instance.
- switch err := device.ApplicationClient(name).Update(ctx); {
+ // Update/revert the instance.
+ var err error
+ if revert {
+ err = device.ApplicationClient(name).Revert(ctx)
+ } else {
+ err = device.ApplicationClient(name).Update(ctx)
+ }
+ switch {
case err == nil:
- fmt.Fprintf(stdout, "Successfully updated instance \"%s\".\n", name)
+ fmt.Fprintf(stdout, "Successful %s of version for instance \"%s\".\n", revertOrUpdate[revert], name)
return nil
case verror.ErrorID(err) == deviceimpl.ErrUpdateNoOp.ID:
// TODO(caprita): Ideally, we wouldn't even attempt a kill /
- // restart if there's no newer version of the application.
- fmt.Fprintf(stdout, "Instance \"%s\" already up to date.\n", name)
+ // restart if the update/revert is a no-op.
+ fmt.Fprintf(stdout, "Instance \"%s\": %s.\n", name, revertOrUpdateNoOp[revert])
return nil
default:
- return fmt.Errorf("Update failed: %v", err)
+ return fmt.Errorf("%s failed: %v", revertOrUpdateMethod[revert], err)
}
}
-func updateOne(ctx *context.T, what string, stdout, stderr io.Writer, name string) error {
- switch err := device.ApplicationClient(name).Update(ctx); {
+func changeVersionOne(ctx *context.T, what string, stdout, stderr io.Writer, name string, revert bool) error {
+ var err error
+ if revert {
+ err = device.ApplicationClient(name).Revert(ctx)
+ } else {
+ err = device.ApplicationClient(name).Update(ctx)
+ }
+ switch {
case err == nil:
- fmt.Fprintf(stdout, "Successfully updated version for %s \"%s\".\n", what, name)
+ fmt.Fprintf(stdout, "Successful %s of version for %s \"%s\".\n", revertOrUpdate[revert], what, name)
return nil
case verror.ErrorID(err) == deviceimpl.ErrUpdateNoOp.ID:
- fmt.Fprintf(stdout, "%s \"%s\" already up to date.\n", what, name)
+ fmt.Fprintf(stdout, "%s \"%s\": %s.\n", what, name, revertOrUpdateNoOp[revert])
return nil
default:
- return fmt.Errorf("Update failed: %v", err)
+ return fmt.Errorf("%s failed: %v", revertOrUpdateMethod[revert], err)
+ }
+}
+
+func changeVersion(entry globResult, ctx *context.T, stdout, stderr io.Writer, revert bool) error {
+ switch entry.kind {
+ case applicationInstanceObject:
+ return changeVersionInstance(ctx, stdout, stderr, entry.name, entry.status.(device.StatusInstance), revert)
+ case applicationInstallationObject:
+ return changeVersionOne(ctx, "installation", stdout, stderr, entry.name, revert)
+ case deviceServiceObject:
+ return changeVersionOne(ctx, "device service", stdout, stderr, entry.name, revert)
+ default:
+ return fmt.Errorf("unhandled object kind %v", entry.kind)
}
}
func runUpdate(entry globResult, ctx *context.T, stdout, stderr io.Writer) error {
- switch entry.kind {
- case applicationInstanceObject:
- return updateInstance(ctx, stdout, stderr, entry.name, entry.status.(device.StatusInstance))
- case applicationInstallationObject:
- return updateOne(ctx, "installation", stdout, stderr, entry.name)
- case deviceServiceObject:
- return updateOne(ctx, "device service", stdout, stderr, entry.name)
- default:
- return fmt.Errorf("unhandled object kind %v", entry.kind)
- }
+ return changeVersion(entry, ctx, stdout, stderr, false)
+}
+
+func runRevert(entry globResult, ctx *context.T, stdout, stderr io.Writer) error {
+ return changeVersion(entry, ctx, stdout, stderr, true)
}
diff --git a/services/device/device/update_test.go b/services/device/device/update_test.go
index 1c90110..a6f0a40 100644
--- a/services/device/device/update_test.go
+++ b/services/device/device/update_test.go
@@ -4,6 +4,8 @@
package main_test
+// TODO(caprita): Rename to update_revert_test.go
+
import (
"bytes"
"fmt"
@@ -11,6 +13,8 @@
"strings"
"testing"
"time"
+ "unicode"
+ "unicode/utf8"
"v.io/v23/naming"
"v.io/x/lib/cmdline"
@@ -20,8 +24,16 @@
cmd_device "v.io/x/ref/services/device/device"
)
-// TestUpdateCommand verifies the device update command.
-func TestUpdateCommand(t *testing.T) {
+func capitalize(s string) string {
+ r, size := utf8.DecodeRuneInString(s)
+ if r == utf8.RuneError {
+ return ""
+ }
+ return string(unicode.ToUpper(r)) + s[size:]
+}
+
+// TestUpdateAndRevertCommands verifies the device update and revert commands.
+func TestUpdateAndRevertCommands(t *testing.T) {
ctx, shutdown := test.V23Init()
defer shutdown()
tapes := newTapeMap()
@@ -31,7 +43,7 @@
}
defer stopServer(t, server)
- cmd := cmd_device.CmdRoot
+ root := cmd_device.CmdRoot
appName := naming.JoinAddressName(endpoint.String(), "app")
rootTape := tapes.forSuffix("")
globName := naming.JoinAddressName(endpoint.String(), "glob")
@@ -39,99 +51,101 @@
joinLines := func(args ...string) string {
return strings.Join(args, "\n")
}
- for _, c := range []struct {
- globResponses []string
- statusResponses map[string][]interface{}
- expectedStimuli map[string][]interface{}
- expectedStdout string
- expectedStderr string
- expectedError string
- }{
- { // Everything succeeds.
- []string{"app/2", "app/1", "app/3"},
- map[string][]interface{}{
- "app/1": []interface{}{instanceRunning, nil, nil, nil},
- "app/2": []interface{}{instanceNotRunning, nil},
- "app/3": []interface{}{installationActive, nil},
+ for _, cmd := range []string{"update", "revert"} {
+ for _, c := range []struct {
+ globResponses []string
+ statusResponses map[string][]interface{}
+ expectedStimuli map[string][]interface{}
+ expectedStdout string
+ expectedStderr string
+ expectedError string
+ }{
+ { // Everything succeeds.
+ []string{"app/2", "app/1", "app/3"},
+ map[string][]interface{}{
+ "app/1": []interface{}{instanceRunning, nil, nil, nil},
+ "app/2": []interface{}{instanceNotRunning, nil},
+ "app/3": []interface{}{installationActive, nil},
+ },
+ map[string][]interface{}{
+ "app/1": []interface{}{"Status", KillStimulus{"Kill", 10 * time.Second}, capitalize(cmd), "Run"},
+ "app/2": []interface{}{"Status", capitalize(cmd)},
+ "app/3": []interface{}{"Status", capitalize(cmd)},
+ },
+ joinLines(
+ fmt.Sprintf("Successful %s of version for installation \"%s/3\".", cmd, appName),
+ fmt.Sprintf("Successful %s of version for instance \"%s/1\".", cmd, appName),
+ fmt.Sprintf("Successful %s of version for instance \"%s/2\".", cmd, appName)),
+ "",
+ "",
},
- map[string][]interface{}{
- "app/1": []interface{}{"Status", KillStimulus{"Kill", 10 * time.Second}, "Update", "Run"},
- "app/2": []interface{}{"Status", "Update"},
- "app/3": []interface{}{"Status", "Update"},
+ { // Assorted failure modes.
+ []string{"app/1", "app/2", "app/3", "app/4", "app/5"},
+ map[string][]interface{}{
+ // Starts as running, fails Kill, but then
+ // recovers. This ultimately counts as a success.
+ "app/1": []interface{}{instanceRunning, fmt.Errorf("Simulate Kill failing"), instanceNotRunning, nil, nil},
+ // Starts as running, fails Kill, and stays running.
+ "app/2": []interface{}{instanceRunning, fmt.Errorf("Simulate Kill failing"), instanceRunning},
+ // Starts as running, Kill and Update succeed, but Run fails.
+ "app/3": []interface{}{instanceRunning, nil, nil, fmt.Errorf("Simulate Run failing")},
+ // Starts as running, Kill succeeds, Update fails, but Run succeeds.
+ "app/4": []interface{}{instanceRunning, nil, fmt.Errorf("Simulate %s failing", capitalize(cmd)), nil},
+ // Starts as running, Kill succeeds, Update fails, and Run fails.
+ "app/5": []interface{}{instanceRunning, nil, fmt.Errorf("Simulate %s failing", capitalize(cmd)), fmt.Errorf("Simulate Run failing")},
+ },
+ map[string][]interface{}{
+ "app/1": []interface{}{"Status", KillStimulus{"Kill", 10 * time.Second}, "Status", capitalize(cmd), "Run"},
+ "app/2": []interface{}{"Status", KillStimulus{"Kill", 10 * time.Second}, "Status"},
+ "app/3": []interface{}{"Status", KillStimulus{"Kill", 10 * time.Second}, capitalize(cmd), "Run"},
+ "app/4": []interface{}{"Status", KillStimulus{"Kill", 10 * time.Second}, capitalize(cmd), "Run"},
+ "app/5": []interface{}{"Status", KillStimulus{"Kill", 10 * time.Second}, capitalize(cmd), "Run"},
+ },
+ joinLines(
+ fmt.Sprintf("Successful %s of version for instance \"%s/1\".", cmd, appName),
+ fmt.Sprintf("Successful %s of version for instance \"%s/3\".", cmd, appName),
+ ),
+ joinLines(
+ fmt.Sprintf("WARNING for \"%s/1\": recovered from Kill error (device.test:<rpc.Client>\"%s/1\".Kill: Error: Simulate Kill failing). Proceeding with %s.", appName, appName, cmd),
+ fmt.Sprintf("ERROR for \"%s/2\": Kill failed: device.test:<rpc.Client>\"%s/2\".Kill: Error: Simulate Kill failing.", appName, appName),
+ fmt.Sprintf("ERROR for \"%s/3\": Run failed: device.test:<rpc.Client>\"%s/3\".Run: Error: Simulate Run failing.", appName, appName),
+ fmt.Sprintf("ERROR for \"%s/4\": %s failed: device.test:<rpc.Client>\"%s/4\".%s: Error: Simulate %s failing.", appName, capitalize(cmd), appName, capitalize(cmd), capitalize(cmd)),
+ fmt.Sprintf("ERROR for \"%s/5\": Run failed: device.test:<rpc.Client>\"%s/5\".Run: Error: Simulate Run failing.", appName, appName),
+ fmt.Sprintf("ERROR for \"%s/5\": %s failed: device.test:<rpc.Client>\"%s/5\".%s: Error: Simulate %s failing.", appName, capitalize(cmd), appName, capitalize(cmd), capitalize(cmd)),
+ ),
+ "encountered a total of 4 error(s)",
},
- joinLines(
- fmt.Sprintf("Successfully updated version for installation \"%s/3\".", appName),
- fmt.Sprintf("Successfully updated instance \"%s/1\".", appName),
- fmt.Sprintf("Successfully updated instance \"%s/2\".", appName)),
- "",
- "",
- },
- { // Assorted failure modes.
- []string{"app/1", "app/2", "app/3", "app/4", "app/5"},
- map[string][]interface{}{
- // Starts as running, fails Kill, but then
- // recovers. This ultimately counts as a success.
- "app/1": []interface{}{instanceRunning, fmt.Errorf("Simulate Kill failing"), instanceNotRunning, nil, nil},
- // Starts as running, fails Kill, and stays running.
- "app/2": []interface{}{instanceRunning, fmt.Errorf("Simulate Kill failing"), instanceRunning},
- // Starts as running, Kill and Update succeed, but Run fails.
- "app/3": []interface{}{instanceRunning, nil, nil, fmt.Errorf("Simulate Run failing")},
- // Starts as running, Kill succeeds, Update fails, but Run succeeds.
- "app/4": []interface{}{instanceRunning, nil, fmt.Errorf("Simulate Update failing"), nil},
- // Starts as running, Kill succeeds, Update fails, and Run fails.
- "app/5": []interface{}{instanceRunning, nil, fmt.Errorf("Simulate Update failing"), fmt.Errorf("Simulate Run failing")},
- },
- map[string][]interface{}{
- "app/1": []interface{}{"Status", KillStimulus{"Kill", 10 * time.Second}, "Status", "Update", "Run"},
- "app/2": []interface{}{"Status", KillStimulus{"Kill", 10 * time.Second}, "Status"},
- "app/3": []interface{}{"Status", KillStimulus{"Kill", 10 * time.Second}, "Update", "Run"},
- "app/4": []interface{}{"Status", KillStimulus{"Kill", 10 * time.Second}, "Update", "Run"},
- "app/5": []interface{}{"Status", KillStimulus{"Kill", 10 * time.Second}, "Update", "Run"},
- },
- joinLines(
- fmt.Sprintf("Successfully updated instance \"%s/1\".", appName),
- fmt.Sprintf("Successfully updated instance \"%s/3\".", appName),
- ),
- joinLines(
- fmt.Sprintf("WARNING for \"%s/1\": recovered from Kill error (device.test:<rpc.Client>\"%s/1\".Kill: Error: Simulate Kill failing). Proceeding with update.", appName, appName),
- fmt.Sprintf("ERROR for \"%s/2\": Kill failed: device.test:<rpc.Client>\"%s/2\".Kill: Error: Simulate Kill failing.", appName, appName),
- fmt.Sprintf("ERROR for \"%s/3\": Run failed: device.test:<rpc.Client>\"%s/3\".Run: Error: Simulate Run failing.", appName, appName),
- fmt.Sprintf("ERROR for \"%s/4\": Update failed: device.test:<rpc.Client>\"%s/4\".Update: Error: Simulate Update failing.", appName, appName),
- fmt.Sprintf("ERROR for \"%s/5\": Run failed: device.test:<rpc.Client>\"%s/5\".Run: Error: Simulate Run failing.", appName, appName),
- fmt.Sprintf("ERROR for \"%s/5\": Update failed: device.test:<rpc.Client>\"%s/5\".Update: Error: Simulate Update failing.", appName, appName),
- ),
- "encountered a total of 4 error(s)",
- },
- } {
- var stdout, stderr bytes.Buffer
- env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
- tapes.rewind()
- rootTape.SetResponses(GlobResponse{c.globResponses})
- for n, r := range c.statusResponses {
- tapes.forSuffix(n).SetResponses(r...)
- }
- args := []string{"update", globName}
- if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, args); err != nil {
- if want, got := c.expectedError, err.Error(); want != got {
- t.Errorf("Unexpected error: want %v, got %v", want, got)
+ } {
+ var stdout, stderr bytes.Buffer
+ env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
+ tapes.rewind()
+ rootTape.SetResponses(GlobResponse{c.globResponses})
+ for n, r := range c.statusResponses {
+ tapes.forSuffix(n).SetResponses(r...)
}
- } else {
- if c.expectedError != "" {
- t.Errorf("Expected to get error %v, but didn't get any error.", c.expectedError)
+ args := []string{cmd, globName}
+ if err := v23cmd.ParseAndRunForTest(root, ctx, env, args); err != nil {
+ if want, got := c.expectedError, err.Error(); want != got {
+ t.Errorf("Unexpected error: want %v, got %v", want, got)
+ }
+ } else {
+ if c.expectedError != "" {
+ t.Errorf("Expected to get error %v, but didn't get any error.", c.expectedError)
+ }
}
- }
- if expected, got := c.expectedStdout, strings.TrimSpace(stdout.String()); got != expected {
- t.Errorf("Unexpected stdout output from update. Got %q, expected %q", got, expected)
- }
- if expected, got := c.expectedStderr, strings.TrimSpace(stderr.String()); got != expected {
- t.Errorf("Unexpected stderr output from update. Got %q, expected %q", got, expected)
- }
- for n, m := range c.expectedStimuli {
- if want, got := m, tapes.forSuffix(n).Play(); !reflect.DeepEqual(want, got) {
- t.Errorf("Unexpected stimuli for %v. Want: %v, got %v.", n, want, got)
+ if expected, got := c.expectedStdout, strings.TrimSpace(stdout.String()); got != expected {
+ t.Errorf("Unexpected stdout output from %s.\nGot:\n%v\nExpected:\n%v", cmd, got, expected)
}
+ if expected, got := c.expectedStderr, strings.TrimSpace(stderr.String()); got != expected {
+ t.Errorf("Unexpected stderr output from %s.\nGot:\n%v\nExpected:\n%v", cmd, got, expected)
+ }
+ for n, m := range c.expectedStimuli {
+ if want, got := m, tapes.forSuffix(n).Play(); !reflect.DeepEqual(want, got) {
+ t.Errorf("Unexpected stimuli for %v. Want: %v, got %v.", n, want, got)
+ }
+ }
+ cmd_device.ResetGlobFlags()
}
- cmd_device.ResetGlobFlags()
}
}