veyron/services/mgmt/node/impl: Add Glob implementation
This change adds a basic Glob implementation for the app invoker. It scans
the config directory to find all the app installations and instances,
and then matches the glob pattern against the result.
Change-Id: I8bb2828eff7429fc005edbfc03787f353200f6ed
diff --git a/services/mgmt/node/impl/app_invoker.go b/services/mgmt/node/impl/app_invoker.go
index 5922123..35a8268 100644
--- a/services/mgmt/node/impl/app_invoker.go
+++ b/services/mgmt/node/impl/app_invoker.go
@@ -729,14 +729,117 @@
return updateLink(prevVersionDir, currLink)
}
+type treeNode struct {
+ children map[string]*treeNode
+}
+
+func newTreeNode() *treeNode {
+ return &treeNode{make(map[string]*treeNode)}
+}
+
+func (n *treeNode) find(names []string, create bool) *treeNode {
+ for {
+ if len(names) == 0 {
+ return n
+ }
+ if next, ok := n.children[names[0]]; ok {
+ n = next
+ names = names[1:]
+ continue
+ }
+ if create {
+ nn := newTreeNode()
+ n.children[names[0]] = nn
+ n = nn
+ names = names[1:]
+ continue
+ }
+ return nil
+ }
+}
+
+// scanConfigDir scans the config directory to build tree representation of all
+// the valid object names.
+func (i *appInvoker) scanConfigDir() *treeNode {
+ tree := newTreeNode()
+
+ // appIDMap[appID]title
+ appIDMap := make(map[string]string)
+
+ // Find all envelopes, extract appID and installID.
+ envGlob := []string{i.config.Root, "app-*", "installation-*", "*", "envelope"}
+ envelopes, err := filepath.Glob(filepath.Join(envGlob...))
+ if err != nil {
+ vlog.Errorf("unexpected error: %v", err)
+ return nil
+ }
+ for _, path := range envelopes {
+ env, err := loadEnvelope(filepath.Dir(path))
+ if err != nil {
+ continue
+ }
+ relpath, _ := filepath.Rel(i.config.Root, path)
+ elems := strings.Split(relpath, string(filepath.Separator))
+ if len(elems) != len(envGlob)-1 {
+ vlog.Errorf("unexpected number of path components: %q (%q)", elems, path)
+ continue
+ }
+ appID := strings.TrimPrefix(elems[0], "app-")
+ installID := strings.TrimPrefix(elems[1], "installation-")
+ appIDMap[appID] = env.Title
+ tree.find([]string{env.Title, installID}, true)
+ }
+
+ // Find all instances.
+ infoGlob := []string{i.config.Root, "app-*", "installation-*", "instances", "instance-*", "info"}
+ instances, err := filepath.Glob(filepath.Join(infoGlob...))
+ if err != nil {
+ vlog.Errorf("unexpected error: %v", err)
+ return nil
+ }
+ for _, path := range instances {
+ if _, err := loadInstanceInfo(filepath.Dir(path)); err != nil {
+ continue
+ }
+ relpath, _ := filepath.Rel(i.config.Root, path)
+ elems := strings.Split(relpath, string(filepath.Separator))
+ if len(elems) != len(infoGlob)-1 {
+ vlog.Errorf("unexpected number of path components: %q (%q)", elems, path)
+ continue
+ }
+ appID := strings.TrimPrefix(elems[0], "app-")
+ installID := strings.TrimPrefix(elems[1], "installation-")
+ instanceID := strings.TrimPrefix(elems[3], "instance-")
+ if title, ok := appIDMap[appID]; ok {
+ tree.find([]string{title, installID, instanceID}, true)
+ }
+ }
+ return tree
+}
+
func (i *appInvoker) Glob(ctx ipc.ServerContext, pattern string, stream mounttable.GlobbableServiceGlobStream) error {
- // TODO(rthellend): Finish implementing Glob
g, err := glob.Parse(pattern)
if err != nil {
return err
}
- if g.Len() == 0 {
- return stream.SendStream().Send(types.MountEntry{Name: ""})
+ n := i.scanConfigDir().find(i.suffix, false)
+ if n == nil {
+ return errInvalidSuffix
}
+ i.globStep("", g, n, stream)
return nil
}
+
+func (i *appInvoker) globStep(prefix string, g *glob.Glob, n *treeNode, stream mounttable.GlobbableServiceGlobStream) {
+ if g.Len() == 0 {
+ stream.SendStream().Send(types.MountEntry{Name: prefix})
+ }
+ if g.Finished() {
+ return
+ }
+ for name, child := range n.children {
+ if ok, _, left := g.MatchInitialSegment(name); ok {
+ i.globStep(naming.Join(prefix, name), left, child, stream)
+ }
+ }
+}
diff --git a/services/mgmt/node/impl/impl_test.go b/services/mgmt/node/impl/impl_test.go
index 1ca4db6..6d66f0f 100644
--- a/services/mgmt/node/impl/impl_test.go
+++ b/services/mgmt/node/impl/impl_test.go
@@ -11,6 +11,7 @@
"os"
goexec "os/exec"
"os/user"
+ "path"
"path/filepath"
"reflect"
"sort"
@@ -920,8 +921,12 @@
*/
func TestNodeManagerGlob(t *testing.T) {
- // Set up mount table.
+ // Set up mount table, application, and binary repositories.
defer setupLocalNamespace(t)()
+ envelope, cleanup := startApplicationRepository()
+ defer cleanup()
+ defer startBinaryRepository()()
+
root, cleanup := setupRootDir(t)
defer cleanup()
@@ -935,33 +940,70 @@
defer nm.Cleanup()
readPID(t, nm)
- c, err := mounttable.BindGlobbable("nm")
- if err != nil {
- t.Fatalf("BindGlobbable failed: %v", err)
+ // Create a script wrapping the test target that implements suidhelper.
+ generateSuidHelperScript(t, root)
+
+ // Create the local server that the app uses to let us know it's ready.
+ server, _ := newServer()
+ defer server.Stop()
+ pingCh := make(chan string, 1)
+ if err := server.Serve("pingserver", ipc.LeafDispatcher(pingServerDisp(pingCh), nil)); err != nil {
+ t.Fatalf("Serve(%q, <dispatcher>) failed: %v", "pingserver", err)
}
- stream, err := c.Glob(rt.R().NewContext(), "...")
- if err != nil {
- t.Errorf("Glob failed: %v", err)
+ // Create the envelope for the first version of the app.
+ app := blackbox.HelperCommand(t, "app", "appV1")
+ defer setupChildCommand(app)()
+ appTitle := "google naps"
+ *envelope = *envelopeFromCmd(appTitle, app.Cmd)
+
+ // Install the app.
+ appID := installApp(t)
+ installID := path.Base(appID)
+
+ // Start an instance of the app.
+ instance1ID := startApp(t, appID)
+ <-pingCh // Wait until the app pings us that it's ready.
+
+ testcases := []struct {
+ name, pattern string
+ expected []string
+ }{
+ {"nm", "...", []string{
+ "",
+ "apps",
+ "apps/google naps",
+ "apps/google naps/" + installID,
+ "apps/google naps/" + installID + "/" + instance1ID,
+ "nm",
+ }},
+ {"nm/apps", "*", []string{"google naps"}},
+ {"nm/apps/google naps", "*", []string{installID}},
}
- results := []string{}
- iterator := stream.RecvStream()
- for iterator.Advance() {
- results = append(results, iterator.Value().Name)
- }
- sort.Strings(results)
- expected := []string{
- "",
- "apps",
- "nm",
- }
- if !reflect.DeepEqual(results, expected) {
- t.Errorf("unexpected result. Got %v, want %v", results, expected)
- }
- if err := iterator.Err(); err != nil {
- t.Errorf("unexpected stream error: %v", err)
- }
- if err := stream.Finish(); err != nil {
- t.Errorf("Finish failed: %v", err)
+ for _, tc := range testcases {
+ c, err := mounttable.BindGlobbable(tc.name)
+ if err != nil {
+ t.Fatalf("BindGlobbable failed: %v", err)
+ }
+
+ stream, err := c.Glob(rt.R().NewContext(), tc.pattern)
+ if err != nil {
+ t.Errorf("Glob failed: %v", err)
+ }
+ results := []string{}
+ iterator := stream.RecvStream()
+ for iterator.Advance() {
+ results = append(results, iterator.Value().Name)
+ }
+ sort.Strings(results)
+ if !reflect.DeepEqual(results, tc.expected) {
+ t.Errorf("unexpected result. Got %q, want %q", results, tc.expected)
+ }
+ if err := iterator.Err(); err != nil {
+ t.Errorf("unexpected stream error: %v", err)
+ }
+ if err := stream.Finish(); err != nil {
+ t.Errorf("Finish failed: %v", err)
+ }
}
}