blob: e772ee92d94f1267b9771f60df6c79d4eaa8ede8 [file] [log] [blame]
// 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"
"strings"
"sync"
"time"
"v.io/v23"
"v.io/v23/context"
"v.io/v23/naming"
"v.io/v23/services/device"
"v.io/v23/verror"
"v.io/x/lib/cmdline"
"v.io/x/ref/lib/v23cmd"
deviceimpl "v.io/x/ref/services/device/internal/impl"
)
// TODO(caprita): Re-implement this with Glob, so that one can say instead,
// update <devicename>/apps/... or <devicename>/apps/appname/* etc.
// TODO(caprita): Add unit test.
var cmdUpdateAll = &cmdline.Command{
Runner: v23cmd.RunnerFunc(runUpdateAll),
Name: "updateall",
Short: "Update all installations/instances of an application",
Long: "Given a name that can refer to an app instance or app installation or app or all apps on a device, updates all installations and instances under that name",
ArgsName: "<object name>",
ArgsLong: `
<object name> is the vanadium object name to update, as follows:
<devicename>/apps/apptitle/installationid/instanceid: updates the given instance, killing/restarting it if running
<devicename>/apps/apptitle/installationid: updates the given installation and then all its instances
<devicename>/apps/apptitle: updates all installations for the given app
<devicename>/apps: updates all apps on the device
`,
}
type updater func(ctx *context.T, env *cmdline.Env, von string) error
func updateChildren(ctx *context.T, env *cmdline.Env, von string, updateChild updater) error {
ns := v23.GetNamespace(ctx)
pattern := naming.Join(von, "*")
c, err := ns.Glob(ctx, pattern)
if err != nil {
return fmt.Errorf("ns.Glob(%q) failed: %v", pattern, err)
}
var (
pending sync.WaitGroup
numErrors int
numErrorsMu sync.Mutex
)
for res := range c {
switch v := res.(type) {
case *naming.GlobReplyEntry:
pending.Add(1)
go func() {
if err := updateChild(ctx, env, v.Value.Name); err != nil {
numErrorsMu.Lock()
numErrors++
numErrorsMu.Unlock()
}
pending.Done()
}()
case *naming.GlobReplyError:
fmt.Fprintf(env.Stderr, "Glob error for %q: %v\n", v.Value.Name, v.Value.Error)
numErrorsMu.Lock()
numErrors++
numErrorsMu.Unlock()
}
}
pending.Wait()
if numErrors > 0 {
return fmt.Errorf("%d error(s) encountered while updating children", numErrors)
}
return nil
}
func instanceIsRunning(ctx *context.T, von string) (bool, error) {
status, err := device.ApplicationClient(von).Status(ctx)
if err != nil {
return false, fmt.Errorf("Failed to get status for instance %q: %v", von, err)
}
s, ok := status.(device.StatusInstance)
if !ok {
return false, fmt.Errorf("Status for instance %q of wrong type (%T)", von, status)
}
return s.Value.State == device.InstanceStateRunning, nil
}
func updateInstance(ctx *context.T, env *cmdline.Env, von string) (retErr error) {
defer func() {
if retErr == nil {
fmt.Fprintf(env.Stdout, "Successfully updated instance %q.\n", von)
} else {
retErr = fmt.Errorf("failed to update instance %q: %v", von, retErr)
fmt.Fprintf(env.Stderr, "ERROR: %v.\n", retErr)
}
}()
running, err := instanceIsRunning(ctx, von)
if err != nil {
return err
}
if running {
// Try killing the app.
if err := device.ApplicationClient(von).Kill(ctx, killDeadline); err != nil {
// Check the app's state again in case we killed it,
// nevermind any errors. The sleep is because Kill
// currently (4/29/15) returns asynchronously with the
// device manager shooting the app down.
time.Sleep(time.Second)
running, rerr := instanceIsRunning(ctx, von)
if rerr != nil {
return rerr
}
if running {
return fmt.Errorf("failed to kill instance %q: %v", von, err)
}
fmt.Fprintf(env.Stderr, "Kill(%s) returned an error (%s) but app is now not running.\n", von, err)
}
// App was running, and we killed it.
defer func() {
// Re-start the instance.
if err := device.ApplicationClient(von).Run(ctx); err != nil {
err = fmt.Errorf("failed to run instance %q: %v", von, err)
if retErr == nil {
retErr = err
} else {
fmt.Fprintf(env.Stderr, "ERROR: %v.\n", err)
}
}
}()
}
// Update the instance.
switch err := device.ApplicationClient(von).Update(ctx); {
case err == nil:
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(env.Stdout, "Instance %q already up to date.\n", von)
return nil
default:
return err
}
}
func updateInstallation(ctx *context.T, env *cmdline.Env, von string) (retErr error) {
defer func() {
if retErr == nil {
fmt.Fprintf(env.Stdout, "Successfully updated installation %q.\n", von)
} else {
retErr = fmt.Errorf("failed to update installation %q: %v", von, retErr)
fmt.Fprintf(env.Stderr, "ERROR: %v.\n", retErr)
}
}()
// First, update the installation.
switch err := device.ApplicationClient(von).Update(ctx); {
case err == nil:
fmt.Fprintf(env.Stdout, "Successfully updated version for installation %q.\n", von)
case verror.ErrorID(err) == deviceimpl.ErrUpdateNoOp.ID:
fmt.Fprintf(env.Stdout, "Installation %q already up to date.\n", von)
// NOTE: we still proceed to update the instances in this case,
// since it's possible that some instances are still running
// from older versions.
default:
return err
}
// Then, update all the instances for the installation.
return updateChildren(ctx, env, von, updateInstance)
}
func updateApp(ctx *context.T, env *cmdline.Env, von string) error {
if err := updateChildren(ctx, env, von, updateInstallation); err != nil {
err = fmt.Errorf("failed to update app %q: %v", von, err)
fmt.Fprintf(env.Stderr, "ERROR: %v.\n", err)
return err
}
fmt.Fprintf(env.Stdout, "Successfully updated app %q.\n", von)
return nil
}
func updateAllApps(ctx *context.T, env *cmdline.Env, von string) error {
if err := updateChildren(ctx, env, von, updateApp); err != nil {
err = fmt.Errorf("failed to update all apps %q: %v", von, err)
fmt.Fprintf(env.Stderr, "ERROR: %v.\n", err)
return err
}
fmt.Fprintf(env.Stdout, "Successfully updated all apps %q.\n", von)
return nil
}
func runUpdateAll(ctx *context.T, env *cmdline.Env, args []string) error {
if expected, got := 1, len(args); expected != got {
return env.UsageErrorf("updateall: incorrect number of arguments, expected %d, got %d", expected, got)
}
appVON := args[0]
components := strings.Split(appVON, "/")
var prefix string
// TODO(caprita): Trying to figure out what the app suffix is by looking
// for "/apps/" in the name is hacky and error-prone (e.g., what if an
// app has the title "apps"). Instead, we should either query the
// server or use resolution to split up the name into address and
// suffix.
for i := len(components) - 1; i >= 0; i-- {
if components[i] == "apps" {
prefix = naming.Join(components[:i+1]...)
components = components[i+1:]
break
}
}
if prefix == "" {
return fmt.Errorf("couldn't recognize app name: %q", appVON)
}
fmt.Printf("prefix: %q, components: %q\n", prefix, components)
switch len(components) {
case 0:
return updateAllApps(ctx, env, appVON)
case 1:
return updateApp(ctx, env, appVON)
case 2:
return updateInstallation(ctx, env, appVON)
case 3:
return updateInstance(ctx, env, appVON)
}
return env.UsageErrorf("updateall: name %q does not refer to a supported app hierarchy object", appVON)
}