veyron/services/mgmt/application: fix applicationd persistence

Applicationd's Dispatcher was not configuring a persistent backing
store for its state per
https://github.com/veyron/release-issues/issues/299. Fix this.

Change-Id: Ia0a60461308d3b6adb09c3da17228afa968479c4
diff --git a/services/mgmt/application/impl/dispatcher.go b/services/mgmt/application/impl/dispatcher.go
index 3fda1a4..3d9346b 100644
--- a/services/mgmt/application/impl/dispatcher.go
+++ b/services/mgmt/application/impl/dispatcher.go
@@ -1,6 +1,8 @@
 package impl
 
 import (
+	"path/filepath"
+
 	"veyron.io/veyron/veyron/services/mgmt/repository"
 
 	"veyron.io/veyron/veyron/services/mgmt/lib/fs"
@@ -17,14 +19,14 @@
 
 var _ ipc.Dispatcher = (*dispatcher)(nil)
 
-// NewDispatcher is the dispatcher factory.
-func NewDispatcher(name string, authorizer security.Authorizer) (*dispatcher, error) {
-	// TODO(rjkroege@google.com): Use the config service.
-	store, err := fs.NewMemstore("")
+// NewDispatcher is the dispatcher factory. storeDir is a path to a directory in which to
+// serialize the applicationd state.
+func NewDispatcher(storeDir string, authorizer security.Authorizer) (*dispatcher, error) {
+	store, err := fs.NewMemstore(filepath.Join(storeDir, "applicationdstate.db"))
 	if err != nil {
 		return nil, err
 	}
-	return &dispatcher{store: store, storeRoot: name, auth: authorizer}, nil
+	return &dispatcher{store: store, storeRoot: storeDir, auth: authorizer}, nil
 }
 
 // DISPATCHER INTERFACE IMPLEMENTATION
diff --git a/services/mgmt/application/impl/impl_test.go b/services/mgmt/application/impl/impl_test.go
index 845c5ee..50e9ce2 100644
--- a/services/mgmt/application/impl/impl_test.go
+++ b/services/mgmt/application/impl/impl_test.go
@@ -2,6 +2,7 @@
 
 import (
 	"io/ioutil"
+	"os"
 	"reflect"
 	"testing"
 
@@ -17,21 +18,15 @@
 // TestInterface tests that the implementation correctly implements
 // the Application interface.
 func TestInterface(t *testing.T) {
-	runtime := rt.Init()
-	ctx := runtime.NewContext()
-	defer runtime.Cleanup()
+	ctx := rt.R().NewContext()
 
 	// Setup and start the application repository server.
-	server, err := runtime.NewServer()
+	server, err := rt.R().NewServer()
 	if err != nil {
 		t.Fatalf("NewServer() failed: %v", err)
 	}
 	defer server.Stop()
 
-	server, err = runtime.NewServer()
-	if err != nil {
-		t.Fatalf("NewServer() failed: %v", err)
-	}
 	dir, prefix := "", ""
 	store, err := ioutil.TempDir(dir, prefix)
 	if err != nil {
@@ -156,3 +151,88 @@
 		t.Fatalf("Stop() failed: %v", err)
 	}
 }
+
+func init() {
+	rt.Init()
+}
+
+func TestPreserveAcrossRestarts(t *testing.T) {
+	server, err := rt.R().NewServer()
+	if err != nil {
+		t.Fatalf("NewServer() failed: %v", err)
+	}
+	defer server.Stop()
+	dir, prefix := "", ""
+	storedir, err := ioutil.TempDir(dir, prefix)
+	if err != nil {
+		t.Fatalf("TempDir(%q, %q) failed: %v", dir, prefix, err)
+	}
+	defer os.RemoveAll(storedir)
+
+	dispatcher, err := NewDispatcher(storedir, nil)
+	if err != nil {
+		t.Fatalf("NewDispatcher() failed: %v", err)
+	}
+
+	endpoint, err := server.Listen(profiles.LocalListenSpec)
+	if err != nil {
+		t.Fatalf("Listen(%s) failed: %v", profiles.LocalListenSpec, err)
+	}
+	if err := server.ServeDispatcher("", dispatcher); err != nil {
+		t.Fatalf("Serve(%v) failed: %v", dispatcher, err)
+	}
+
+	// Create client stubs for talking to the server.
+	stubV1 := repository.ApplicationClient(naming.JoinAddressName(endpoint.String(), "search/v1"))
+
+	// Create example envelopes.
+	envelopeV1 := application.Envelope{
+		Args:   []string{"--help"},
+		Env:    []string{"DEBUG=1"},
+		Binary: "/veyron/name/of/binary",
+	}
+
+	if err := stubV1.Put(rt.R().NewContext(), []string{"media"}, envelopeV1); err != nil {
+		t.Fatalf("Put() failed: %v", err)
+	}
+
+	// There is content here now.
+	output, err := stubV1.Match(rt.R().NewContext(), []string{"media"})
+	if err != nil {
+		t.Fatalf("Match(%v) failed: %v", "media", err)
+	}
+	if !reflect.DeepEqual(envelopeV1, output) {
+		t.Fatalf("Incorrect output: expected %v, got %v", envelopeV1, output)
+	}
+
+	server.Stop()
+
+	// Setup and start a second application server in its place.
+	server, err = rt.R().NewServer()
+	if err != nil {
+		t.Fatalf("NewServer() failed: %v", err)
+	}
+	defer server.Stop()
+
+	dispatcher, err = NewDispatcher(storedir, nil)
+	if err != nil {
+		t.Fatalf("NewDispatcher() failed: %v", err)
+	}
+	endpoint, err = server.Listen(profiles.LocalListenSpec)
+	if err != nil {
+		t.Fatalf("Listen(%s) failed: %v", profiles.LocalListenSpec, err)
+	}
+	if err := server.ServeDispatcher("", dispatcher); err != nil {
+		t.Fatalf("Serve(%v) failed: %v", dispatcher, err)
+	}
+
+	stubV1 = repository.ApplicationClient(naming.JoinAddressName(endpoint.String(), "search/v1"))
+
+	output, err = stubV1.Match(rt.R().NewContext(), []string{"media"})
+	if err != nil {
+		t.Fatalf("Match(%v) failed: %v", "media", err)
+	}
+	if !reflect.DeepEqual(envelopeV1, output) {
+		t.Fatalf("Incorrect output: expected %v, got %v", envelopeV1, output)
+	}
+}
diff --git a/services/mgmt/profile/impl/dispatcher.go b/services/mgmt/profile/impl/dispatcher.go
index 6c28885..25c1862 100644
--- a/services/mgmt/profile/impl/dispatcher.go
+++ b/services/mgmt/profile/impl/dispatcher.go
@@ -1,6 +1,8 @@
 package impl
 
 import (
+	"path/filepath"
+
 	"veyron.io/veyron/veyron/services/mgmt/repository"
 
 	"veyron.io/veyron/veyron/services/mgmt/lib/fs"
@@ -17,14 +19,14 @@
 
 var _ ipc.Dispatcher = (*dispatcher)(nil)
 
-// NewDispatcher is the dispatcher factory.
-func NewDispatcher(name string, authorizer security.Authorizer) (*dispatcher, error) {
-	// TODO(rjkroege@google.com): Use the config service.
-	store, err := fs.NewMemstore("")
+// NewDispatcher is the dispatcher factory. storeDir is a path to a
+// directory in which the profile state is persisted.
+func NewDispatcher(storeDir string, authorizer security.Authorizer) (*dispatcher, error) {
+	store, err := fs.NewMemstore(filepath.Join(storeDir, "profilestate.db"))
 	if err != nil {
 		return nil, err
 	}
-	return &dispatcher{store: store, storeRoot: name, auth: authorizer}, nil
+	return &dispatcher{store: store, storeRoot: storeDir, auth: authorizer}, nil
 }
 
 // DISPATCHER INTERFACE IMPLEMENTATION
diff --git a/services/mgmt/profile/impl/impl_test.go b/services/mgmt/profile/impl/impl_test.go
index 8d0a9e7..d0a4521 100644
--- a/services/mgmt/profile/impl/impl_test.go
+++ b/services/mgmt/profile/impl/impl_test.go
@@ -2,6 +2,7 @@
 
 import (
 	"io/ioutil"
+	"os"
 	"reflect"
 	"testing"
 
@@ -29,9 +30,7 @@
 // TestInterface tests that the implementation correctly implements
 // the Profile interface.
 func TestInterface(t *testing.T) {
-	runtime := rt.Init()
-	defer runtime.Cleanup()
-
+	runtime := rt.R()
 	ctx := runtime.NewContext()
 
 	// Setup and start the profile repository server.
@@ -41,11 +40,6 @@
 	}
 	defer server.Stop()
 
-	// Setup and start the profile server.
-	server, err = runtime.NewServer()
-	if err != nil {
-		t.Fatalf("NewServer() failed: %v", err)
-	}
 	dir, prefix := "", ""
 	store, err := ioutil.TempDir(dir, prefix)
 	if err != nil {
@@ -109,3 +103,88 @@
 		t.Fatalf("Stop() failed: %v", err)
 	}
 }
+
+func init() {
+	rt.Init()
+}
+
+func TestPreserveAcrossRestarts(t *testing.T) {
+	runtime := rt.R()
+	ctx := runtime.NewContext()
+
+	// Setup and start the profile repository server.
+	server, err := runtime.NewServer()
+	if err != nil {
+		t.Fatalf("NewServer() failed: %v", err)
+	}
+	defer server.Stop()
+
+	dir, prefix := "", ""
+	storedir, err := ioutil.TempDir(dir, prefix)
+	if err != nil {
+		t.Fatalf("TempDir(%q, %q) failed: %v", dir, prefix, err)
+	}
+	defer os.RemoveAll(storedir)
+
+	dispatcher, err := NewDispatcher(storedir, nil)
+	if err != nil {
+		t.Fatalf("NewDispatcher() failed: %v", err)
+	}
+	endpoint, err := server.Listen(profiles.LocalListenSpec)
+	if err != nil {
+		t.Fatalf("Listen(%s) failed: %v", profiles.LocalListenSpec, err)
+	}
+	if err := server.ServeDispatcher("", dispatcher); err != nil {
+		t.Fatalf("Serve failed: %v", err)
+	}
+	t.Logf("Profile repository at %v", endpoint)
+
+	// Create client stubs for talking to the server.
+	stub := repository.ProfileClient(naming.JoinAddressName(endpoint.String(), "linux/base"))
+
+	if err := stub.Put(ctx, spec); err != nil {
+		t.Fatalf("Put() failed: %v", err)
+	}
+
+	label, err := stub.Label(ctx)
+	if err != nil {
+		t.Fatalf("Label() failed: %v", err)
+	}
+	if label != spec.Label {
+		t.Fatalf("Unexpected output: expected %v, got %v", spec.Label, label)
+	}
+
+	// Stop the first server.
+	server.Stop()
+
+	// Setup and start a second server.
+	server, err = runtime.NewServer()
+	if err != nil {
+		t.Fatalf("NewServer() failed: %v", err)
+	}
+	defer server.Stop()
+
+	dispatcher, err = NewDispatcher(storedir, nil)
+	if err != nil {
+		t.Fatalf("NewDispatcher() failed: %v", err)
+	}
+	endpoint, err = server.Listen(profiles.LocalListenSpec)
+	if err != nil {
+		t.Fatalf("Listen(%s) failed: %v", profiles.LocalListenSpec, err)
+	}
+	if err = server.ServeDispatcher("", dispatcher); err != nil {
+		t.Fatalf("Serve failed: %v", err)
+	}
+
+	// Create client stubs for talking to the server.
+	stub = repository.ProfileClient(naming.JoinAddressName(endpoint.String(), "linux/base"))
+
+	// Label
+	label, err = stub.Label(ctx)
+	if err != nil {
+		t.Fatalf("Label() failed: %v", err)
+	}
+	if label != spec.Label {
+		t.Fatalf("Unexpected output: expected %v, got %v", spec.Label, label)
+	}
+}