veyron/services/mgmt/node/impl: fix globbing on installations

The issue is that globbing on apps/<app title>/* or apps/... fails to reveal
installations of the app that do not have any instances.

The fix is to include a node for the installation itself in the tree of nodes
created in GlobChildren__, such that apps/<app title>/<installation id> will
still be found even if <installation id> has an empty set of children.

To cover this case, adding an example to the unit test of an installation
without instances, and showing that it correctly shows up in Globbing.

Change-Id: Id4b201059521059ce4cac5fce17d0c3f66fbffeb
diff --git a/services/mgmt/node/impl/app_service.go b/services/mgmt/node/impl/app_service.go
index 56f23ab..30582ef 100644
--- a/services/mgmt/node/impl/app_service.go
+++ b/services/mgmt/node/impl/app_service.go
@@ -1162,6 +1162,8 @@
 	if err != nil {
 		return
 	}
+	// Add the node corresponding to the installation itself.
+	tree.find(i.suffix[:2], true)
 	// Find all instances.
 	infoGlob := []string{installDir, "instances", "instance-*", "info"}
 	instances, err := filepath.Glob(filepath.Join(infoGlob...))
diff --git a/services/mgmt/node/impl/impl_test.go b/services/mgmt/node/impl/impl_test.go
index 03f9d49..62fabd7 100644
--- a/services/mgmt/node/impl/impl_test.go
+++ b/services/mgmt/node/impl/impl_test.go
@@ -1033,7 +1033,7 @@
 
 	// Install the app.
 	appID := installApp(t)
-	installID := path.Base(appID)
+	install1ID := path.Base(appID)
 
 	// Start an instance of the app.
 	instance1ID := startApp(t, appID)
@@ -1045,6 +1045,9 @@
 		t.Fatalf("failed to get ping")
 	}
 
+	app2ID := installApp(t)
+	install2ID := path.Base(app2ID)
+
 	testcases := []struct {
 		name, pattern string
 		expected      []string
@@ -1053,32 +1056,33 @@
 			"",
 			"apps",
 			"apps/google naps",
-			"apps/google naps/" + installID,
-			"apps/google naps/" + installID + "/" + instance1ID,
-			"apps/google naps/" + installID + "/" + instance1ID + "/logs",
-			"apps/google naps/" + installID + "/" + instance1ID + "/logs/STDERR-<timestamp>",
-			"apps/google naps/" + installID + "/" + instance1ID + "/logs/STDOUT-<timestamp>",
-			"apps/google naps/" + installID + "/" + instance1ID + "/logs/bin.INFO",
-			"apps/google naps/" + installID + "/" + instance1ID + "/logs/bin.<*>.INFO.<timestamp>",
-			"apps/google naps/" + installID + "/" + instance1ID + "/pprof",
-			"apps/google naps/" + installID + "/" + instance1ID + "/stats",
-			"apps/google naps/" + installID + "/" + instance1ID + "/stats/ipc",
-			"apps/google naps/" + installID + "/" + instance1ID + "/stats/system",
-			"apps/google naps/" + installID + "/" + instance1ID + "/stats/system/start-time-rfc1123",
-			"apps/google naps/" + installID + "/" + instance1ID + "/stats/system/start-time-unix",
+			"apps/google naps/" + install1ID,
+			"apps/google naps/" + install1ID + "/" + instance1ID,
+			"apps/google naps/" + install1ID + "/" + instance1ID + "/logs",
+			"apps/google naps/" + install1ID + "/" + instance1ID + "/logs/STDERR-<timestamp>",
+			"apps/google naps/" + install1ID + "/" + instance1ID + "/logs/STDOUT-<timestamp>",
+			"apps/google naps/" + install1ID + "/" + instance1ID + "/logs/bin.INFO",
+			"apps/google naps/" + install1ID + "/" + instance1ID + "/logs/bin.<*>.INFO.<timestamp>",
+			"apps/google naps/" + install1ID + "/" + instance1ID + "/pprof",
+			"apps/google naps/" + install1ID + "/" + instance1ID + "/stats",
+			"apps/google naps/" + install1ID + "/" + instance1ID + "/stats/ipc",
+			"apps/google naps/" + install1ID + "/" + instance1ID + "/stats/system",
+			"apps/google naps/" + install1ID + "/" + instance1ID + "/stats/system/start-time-rfc1123",
+			"apps/google naps/" + install1ID + "/" + instance1ID + "/stats/system/start-time-unix",
+			"apps/google naps/" + install2ID,
 			"nm",
 		}},
 		{"nm/apps", "*", []string{"google naps"}},
-		{"nm/apps/google naps", "*", []string{installID}},
-		{"nm/apps/google naps/" + installID, "*", []string{instance1ID}},
-		{"nm/apps/google naps/" + installID + "/" + instance1ID, "*", []string{"logs", "pprof", "stats"}},
-		{"nm/apps/google naps/" + installID + "/" + instance1ID + "/logs", "*", []string{
+		{"nm/apps/google naps", "*", []string{install1ID, install2ID}},
+		{"nm/apps/google naps/" + install1ID, "*", []string{instance1ID}},
+		{"nm/apps/google naps/" + install1ID + "/" + instance1ID, "*", []string{"logs", "pprof", "stats"}},
+		{"nm/apps/google naps/" + install1ID + "/" + instance1ID + "/logs", "*", []string{
 			"STDERR-<timestamp>",
 			"STDOUT-<timestamp>",
 			"bin.INFO",
 			"bin.<*>.INFO.<timestamp>",
 		}},
-		{"nm/apps/google naps/" + installID + "/" + instance1ID + "/stats/system", "start-time*", []string{"start-time-rfc1123", "start-time-unix"}},
+		{"nm/apps/google naps/" + install1ID + "/" + instance1ID + "/stats/system", "start-time*", []string{"start-time-rfc1123", "start-time-unix"}},
 	}
 	logFileTimeStampRE := regexp.MustCompile("(STDOUT|STDERR)-[0-9]+$")
 	logFileTrimInfoRE := regexp.MustCompile(`bin\..*\.INFO\.[0-9.-]+$`)
@@ -1113,7 +1117,7 @@
 	}
 
 	// Call Size() on the log file objects.
-	files, err := testutil.GlobName(globalRT.NewContext(), "nm", "apps/google naps/"+installID+"/"+instance1ID+"/logs/*")
+	files, err := testutil.GlobName(globalRT.NewContext(), "nm", "apps/google naps/"+install1ID+"/"+instance1ID+"/logs/*")
 	if err != nil {
 		t.Errorf("unexpected glob error: %v", err)
 	}
@@ -1129,7 +1133,7 @@
 	}
 
 	// Call Value() on some of the stats objects.
-	objects, err := testutil.GlobName(globalRT.NewContext(), "nm", "apps/google naps/"+installID+"/"+instance1ID+"/stats/system/start-time*")
+	objects, err := testutil.GlobName(globalRT.NewContext(), "nm", "apps/google naps/"+install1ID+"/"+instance1ID+"/stats/system/start-time*")
 	if err != nil {
 		t.Errorf("unexpected glob error: %v", err)
 	}
@@ -1146,7 +1150,7 @@
 
 	// Call CmdLine() on the pprof object.
 	{
-		name := "nm/apps/google naps/" + installID + "/" + instance1ID + "/pprof"
+		name := "nm/apps/google naps/" + install1ID + "/" + instance1ID + "/pprof"
 		c := pprof.PProfClient(name)
 		v, err := c.CmdLine(globalRT.NewContext())
 		if err != nil {