Merge "veyron/services/mgmt/node/impl: implement app Uninstall"
diff --git a/services/mgmt/node/impl/app_invoker.go b/services/mgmt/node/impl/app_invoker.go
index efbfe8e..c81e079 100644
--- a/services/mgmt/node/impl/app_invoker.go
+++ b/services/mgmt/node/impl/app_invoker.go
@@ -9,6 +9,7 @@
// <config.Root>/
// app-<hash 1>/ - the application dir is named using a hash of the application title
// installation-<id 1>/ - installations are labelled with ids
+// <status> - one of the values for installationState enum
// <version 1 timestamp>/ - timestamp of when the version was downloaded
// bin - application binary
// previous - symbolic link to previous version directory (TODO)
@@ -105,6 +106,43 @@
"veyron2/vlog"
)
+// installationState describes the states that an installation can be in at any
+// time.
+type installationState int
+
+const (
+ active installationState = iota
+ uninstalled
+)
+
+// String returns the name that will be used to encode the state as a file name
+// in the installation's dir.
+func (s installationState) String() string {
+ switch s {
+ case active:
+ return "active"
+ case uninstalled:
+ return "uninstalled"
+ default:
+ return "unknown"
+ }
+}
+
+func installationStateIs(installationDir string, state installationState) bool {
+ if _, err := os.Stat(filepath.Join(installationDir, state.String())); err != nil {
+ return false
+ }
+ return true
+}
+
+func transitionInstallation(installationDir string, initial, target installationState) error {
+ return transitionState(installationDir, initial, target)
+}
+
+func initializeInstallation(installationDir string, initial installationState) error {
+ return initializeState(installationDir, initial)
+}
+
// instanceState describes the states that an instance can be in at any time.
type instanceState int
@@ -138,9 +176,17 @@
}
}
-func transition(instanceDir string, initial, target instanceState) error {
- initialState := filepath.Join(instanceDir, initial.String())
- targetState := filepath.Join(instanceDir, target.String())
+func transitionInstance(instanceDir string, initial, target instanceState) error {
+ return transitionState(instanceDir, initial, target)
+}
+
+func initializeInstance(instanceDir string, initial instanceState) error {
+ return initializeState(instanceDir, initial)
+}
+
+func transitionState(dir string, initial, target fmt.Stringer) error {
+ initialState := filepath.Join(dir, initial.String())
+ targetState := filepath.Join(dir, target.String())
if err := os.Rename(initialState, targetState); err != nil {
if os.IsNotExist(err) {
return errInvalidOperation
@@ -151,8 +197,8 @@
return nil
}
-func initializeState(instanceDir string, initial instanceState) error {
- initialStatus := filepath.Join(instanceDir, initial.String())
+func initializeState(dir string, initial fmt.Stringer) error {
+ initialStatus := filepath.Join(dir, initial.String())
if err := ioutil.WriteFile(initialStatus, []byte("status"), 0600); err != nil {
vlog.Errorf("WriteFile(%v) failed: %v", initialStatus, err)
return errOperationFailed
@@ -303,7 +349,7 @@
return "", errOperationFailed
}
deferrer := func() {
- if err := os.RemoveAll(versionDir); err != nil {
+ if err := os.RemoveAll(installationDir); err != nil {
vlog.Errorf("RemoveAll(%v) failed: %v", versionDir, err)
}
}
@@ -327,6 +373,9 @@
vlog.Errorf("Symlink(%v, %v) failed: %v", versionDir, link, err)
return "", errOperationFailed
}
+ if err := initializeInstallation(installationDir, active); err != nil {
+ return "", err
+ }
deferrer = nil
return naming.Join(envelope.Title, installationID), nil
}
@@ -383,6 +432,9 @@
if err != nil {
return "", "", err
}
+ if !installationStateIs(installationDir, active) {
+ return "", "", errInvalidOperation
+ }
instanceID := generateID()
instanceDir := filepath.Join(installationDir, "instances", instanceDirName(instanceID))
if mkdir(instanceDir) != nil {
@@ -399,7 +451,7 @@
vlog.Errorf("Symlink(%v, %v) failed: %v", versionDir, versionLink, err)
return instanceDir, instanceID, errOperationFailed
}
- if err := initializeState(instanceDir, suspended); err != nil {
+ if err := initializeInstance(instanceDir, suspended); err != nil {
return instanceDir, instanceID, err
}
return instanceDir, instanceID, nil
@@ -494,7 +546,7 @@
}
func (i *appInvoker) run(instanceDir string) error {
- if err := transition(instanceDir, suspended, starting); err != nil {
+ if err := transitionInstance(instanceDir, suspended, starting); err != nil {
return err
}
cmd, err := genCmd(instanceDir)
@@ -502,10 +554,10 @@
err = i.startCmd(instanceDir, cmd)
}
if err != nil {
- transition(instanceDir, starting, suspended)
+ transitionInstance(instanceDir, starting, suspended)
return err
}
- return transition(instanceDir, starting, started)
+ return transitionInstance(instanceDir, starting, started)
}
func (i *appInvoker) Start(ipc.ServerContext) ([]string, error) {
@@ -589,17 +641,17 @@
if err != nil {
return err
}
- if err := transition(instanceDir, suspended, stopped); err == errOperationFailed || err == nil {
+ if err := transitionInstance(instanceDir, suspended, stopped); err == errOperationFailed || err == nil {
return err
}
- if err := transition(instanceDir, started, stopping); err != nil {
+ if err := transitionInstance(instanceDir, started, stopping); err != nil {
return err
}
if err := stop(instanceDir); err != nil {
- transition(instanceDir, stopping, started)
+ transitionInstance(instanceDir, stopping, started)
return err
}
- return transition(instanceDir, stopping, stopped)
+ return transitionInstance(instanceDir, stopping, stopped)
}
func (i *appInvoker) Suspend(ipc.ServerContext) error {
@@ -607,27 +659,30 @@
if err != nil {
return err
}
- if err := transition(instanceDir, started, suspending); err != nil {
+ if err := transitionInstance(instanceDir, started, suspending); err != nil {
return err
}
if err := stop(instanceDir); err != nil {
- transition(instanceDir, suspending, started)
+ transitionInstance(instanceDir, suspending, started)
return err
}
- return transition(instanceDir, suspending, suspended)
+ return transitionInstance(instanceDir, suspending, suspended)
}
-func (*appInvoker) Uninstall(ipc.ServerContext) error {
+func (i *appInvoker) Uninstall(ipc.ServerContext) error {
+ installationDir, err := i.installationDir()
+ if err != nil {
+ return err
+ }
+ return transitionInstallation(installationDir, active, uninstalled)
+}
+
+func (*appInvoker) Update(ipc.ServerContext) error {
// TODO(jsimsa): Implement.
return nil
}
-func (i *appInvoker) Update(ipc.ServerContext) error {
- // TODO(jsimsa): Implement.
- return nil
-}
-
-func (i *appInvoker) UpdateTo(_ ipc.ServerContext, von string) error {
+func (*appInvoker) UpdateTo(_ ipc.ServerContext, von string) error {
// TODO(jsimsa): Implement.
return nil
}
diff --git a/services/mgmt/node/impl/impl_test.go b/services/mgmt/node/impl/impl_test.go
index 2dd443f..d6856f2 100644
--- a/services/mgmt/node/impl/impl_test.go
+++ b/services/mgmt/node/impl/impl_test.go
@@ -452,25 +452,37 @@
return appID
}
-func startApp(t *testing.T, appID string) string {
+func startAppImpl(t *testing.T, appID string) (string, error) {
appsName := "nm//apps"
appName := naming.Join(appsName, appID)
stub, err := node.BindApplication(appName)
if err != nil {
t.Fatalf("BindApplication(%v) failed: %v", appName, err)
}
- var instanceID string
if instanceIDs, err := stub.Start(rt.R().NewContext()); err != nil {
- t.Fatalf("Start failed: %v", err)
+ return "", err
} else {
if want, got := 1, len(instanceIDs); want != got {
t.Fatalf("Expected %v instance ids, got %v instead", want, got)
}
- instanceID = instanceIDs[0]
+ return instanceIDs[0], nil
+ }
+}
+
+func startApp(t *testing.T, appID string) string {
+ instanceID, err := startAppImpl(t, appID)
+ if err != nil {
+ t.Fatalf("Start failed: %v", err)
}
return instanceID
}
+func startAppShouldFail(t *testing.T, appID string, expectedError verror.ID) {
+ if _, err := startAppImpl(t, appID); err == nil || !verror.Is(err, expectedError) {
+ t.Fatalf("Start(%v) expected to fail with %v, got %v instead", appID, expectedError, err)
+ }
+}
+
func stopApp(t *testing.T, appID, instanceID string) {
appsName := "nm//apps"
appName := naming.Join(appsName, appID)
@@ -510,6 +522,18 @@
}
}
+func uninstallApp(t *testing.T, appID string) {
+ appsName := "nm//apps"
+ appName := naming.Join(appsName, appID)
+ stub, err := node.BindApplication(appName)
+ if err != nil {
+ t.Fatalf("BindApplication(%v) failed: %v", appName, err)
+ }
+ if err := stub.Uninstall(rt.R().NewContext()); err != nil {
+ t.Fatalf("Uninstall failed: %v", err)
+ }
+}
+
func verifyAppWorkspace(t *testing.T, root, appID, instanceID string) {
// HACK ALERT: for now, we peek inside the node manager's directory
// structure (which ought to be opaque) to check for what the app has
@@ -596,6 +620,12 @@
verifyAppWorkspace(t, root, appID, instanceID)
+ // Uninstall the app.
+ uninstallApp(t, appID)
+
+ // Starting new instances should no longer be allowed.
+ startAppShouldFail(t, appID, verror.BadArg)
+
// Cleanly shut down the node manager.
syscall.Kill(nm.Cmd.Process.Pid, syscall.SIGINT)
nm.Expect("nm terminating")