veyron/services/mgmt/node/impl: separate out state management types and routines
into a new source file; add unit tests.
Change-Id: I840a6b77a2fde0eefd4d0106bf8ab6fa38332be7
diff --git a/services/mgmt/node/impl/app_invoker.go b/services/mgmt/node/impl/app_invoker.go
index c81e079..871a78e 100644
--- a/services/mgmt/node/impl/app_invoker.go
+++ b/services/mgmt/node/impl/app_invoker.go
@@ -106,106 +106,6 @@
"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
-
-const (
- starting instanceState = iota
- started
- suspending
- suspended
- stopping
- stopped
-)
-
-// String returns the name that will be used to encode the state as a file name
-// in the instance's dir.
-func (s instanceState) String() string {
- switch s {
- case starting:
- return "starting"
- case started:
- return "started"
- case suspending:
- return "suspending"
- case suspended:
- return "suspended"
- case stopping:
- return "stopping"
- case stopped:
- return "stopped"
- default:
- return "unknown"
- }
-}
-
-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
- }
- vlog.Errorf("Rename(%v, %v) failed: %v", initialState, targetState, err) // Something went really wrong.
- return errOperationFailed
- }
- return nil
-}
-
-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
- }
- return nil
-}
-
// instanceInfo holds state about a running instance.
type instanceInfo struct {
AppCycleMgrName string
diff --git a/services/mgmt/node/impl/app_state.go b/services/mgmt/node/impl/app_state.go
new file mode 100644
index 0000000..744dcb5
--- /dev/null
+++ b/services/mgmt/node/impl/app_state.go
@@ -0,0 +1,110 @@
+package impl
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+
+ "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
+
+const (
+ starting instanceState = iota
+ started
+ suspending
+ suspended
+ stopping
+ stopped
+)
+
+// String returns the name that will be used to encode the state as a file name
+// in the instance's dir.
+func (s instanceState) String() string {
+ switch s {
+ case starting:
+ return "starting"
+ case started:
+ return "started"
+ case suspending:
+ return "suspending"
+ case suspended:
+ return "suspended"
+ case stopping:
+ return "stopping"
+ case stopped:
+ return "stopped"
+ default:
+ return "unknown"
+ }
+}
+
+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
+ }
+ vlog.Errorf("Rename(%v, %v) failed: %v", initialState, targetState, err) // Something went really wrong.
+ return errOperationFailed
+ }
+ return nil
+}
+
+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
+ }
+ return nil
+}
diff --git a/services/mgmt/node/impl/app_state_test.go b/services/mgmt/node/impl/app_state_test.go
new file mode 100644
index 0000000..0fd8c2d
--- /dev/null
+++ b/services/mgmt/node/impl/app_state_test.go
@@ -0,0 +1,70 @@
+package impl
+
+import (
+ "io/ioutil"
+ "os"
+ "testing"
+)
+
+// TestInstallationState verifies the state transition logic for app installations.
+func TestInstallationState(t *testing.T) {
+ dir, err := ioutil.TempDir("", "installation")
+ if err != nil {
+ t.Fatalf("Failed to create temp dir: %v", err)
+ }
+ defer os.RemoveAll(dir)
+ // Uninitialized state.
+ if transitionInstallation(dir, active, uninstalled) == nil {
+ t.Fatalf("transitionInstallation should have failed")
+ }
+ if isActive, isUninstalled := installationStateIs(dir, active), installationStateIs(dir, uninstalled); isActive || isUninstalled {
+ t.Fatalf("isActive, isUninstalled = %t, %t (expected false, false)", isActive, isUninstalled)
+ }
+ // Initialize.
+ if err := initializeInstallation(dir, active); err != nil {
+ t.Fatalf("initializeInstallation failed: %v", err)
+ }
+ if !installationStateIs(dir, active) {
+ t.Fatalf("Installation state expected to be %v", active)
+ }
+ if err := transitionInstallation(dir, active, uninstalled); err != nil {
+ t.Fatalf("transitionInstallation failed: %v", err)
+ }
+ if !installationStateIs(dir, uninstalled) {
+ t.Fatalf("Installation state expected to be %v", uninstalled)
+ }
+ // Invalid transition: wrong initial state.
+ if transitionInstallation(dir, active, uninstalled) == nil {
+ t.Fatalf("transitionInstallation should have failed")
+ }
+ if !installationStateIs(dir, uninstalled) {
+ t.Fatalf("Installation state expected to be %v", uninstalled)
+ }
+}
+
+// TestInstanceState verifies the state transition logic for app instances.
+func TestInstanceState(t *testing.T) {
+ dir, err := ioutil.TempDir("", "instance")
+ if err != nil {
+ t.Fatalf("Failed to create temp dir: %v", err)
+ }
+ defer os.RemoveAll(dir)
+ // Uninitialized state.
+ if transitionInstance(dir, starting, started) == nil {
+ t.Fatalf("transitionInstance should have failed")
+ }
+ // Initialize.
+ if err := initializeInstance(dir, suspending); err != nil {
+ t.Fatalf("initializeInstance failed: %v", err)
+ }
+ if err := transitionInstance(dir, suspending, suspended); err != nil {
+ t.Fatalf("transitionInstance failed: %v", err)
+ }
+ // Invalid transition: wrong initial state.
+ if transitionInstance(dir, suspending, suspended) == nil {
+ t.Fatalf("transitionInstance should have failed")
+ }
+ if err := transitionInstance(dir, suspended, stopped); err != nil {
+ t.Fatalf("transitionInstance failed: %v", err)
+ }
+}