Merge "device manager test"
diff --git a/v23/syncbase/mgmt_v23_test.go b/v23/syncbase/mgmt_v23_test.go
new file mode 100644
index 0000000..0384c7e
--- /dev/null
+++ b/v23/syncbase/mgmt_v23_test.go
@@ -0,0 +1,305 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package syncbase_test
+
+import (
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "math/rand"
+ "os"
+ "path/filepath"
+ "regexp"
+ "strings"
+ "time"
+
+ _ "v.io/x/ref/runtime/factories/generic"
+ "v.io/x/ref/test/v23tests"
+)
+
+//go:generate v23 test generate
+
+var (
+ hostname string
+ errTimeout = errors.New("timeout")
+)
+
+func init() {
+ name, err := os.Hostname()
+ if err != nil {
+ panic(fmt.Sprintf("Hostname() failed: %v", err))
+ }
+ hostname = name
+}
+
+// V23TestDeviceManager publishes and runs syncbased using the device manager
+// and verifies that the running instance of syncbased has appropriate
+// blessings.
+func V23TestDeviceManager(i *v23tests.T) {
+ defer fmt.Fprintf(os.Stderr, "--------------- SHUTDOWN ---------------\n")
+ var (
+ workDir = i.NewTempDir("")
+ binStagingDir = mkSubdir(i, workDir, "bin")
+ dmInstallDir = filepath.Join(workDir, "dm")
+
+ // Most vanadium command-line utilities will be run by a
+ // principal that has "root/u/alice" as its blessing.
+ // (Where "root" comes from i.Principal().BlessingStore().Default()).
+ // Create those credentials and options to use to setup the
+ // binaries with them.
+ aliceCreds, _ = i.Shell().NewChildCredentials("u/alice")
+ aliceOpts = i.Shell().DefaultStartOpts().ExternalProgram().WithCustomCredentials(aliceCreds)
+
+ // Build all the command-line tools and set them up to run as alice.
+ // applicationd/binaryd servers will be run by alice too.
+ // TODO: applicationd/binaryd should run as a separate "service" role, as
+ // alice is just a user.
+ namespaceBin = i.BuildV23Pkg("v.io/x/ref/cmd/namespace").WithStartOpts(aliceOpts)
+ deviceBin = i.BuildV23Pkg("v.io/x/ref/services/device/device").WithStartOpts(aliceOpts)
+ binarydBin = i.BuildV23Pkg("v.io/x/ref/services/binary/binaryd").WithStartOpts(aliceOpts)
+ applicationdBin = i.BuildV23Pkg("v.io/x/ref/services/application/applicationd").WithStartOpts(aliceOpts)
+ syncbasedBin = i.BuildV23Pkg("v.io/syncbase/x/ref/services/syncbase/syncbased")
+
+ // The devicex script is not provided with any credentials, it
+ // will generate its own. This means that on "devicex start"
+ // the device will have no useful credentials and until "device
+ // claim" is invoked (as alice), it will just sit around
+ // waiting to be claimed.
+ //
+ // Other binaries, like applicationd and binaryd will be run by alice.
+ deviceScriptPath = filepath.Join(os.Getenv("V23_ROOT"), "release", "go", "src", "v.io", "x", "ref", "services", "device", "devicex")
+ deviceScript = i.BinaryFromPath(deviceScriptPath).WithEnv("V23_DEVICE_DIR=" + dmInstallDir)
+
+ mtName = "devices/" + hostname // Name under which the device manager will publish itself.
+ )
+
+ // We also need some tools running with different sets of credentials...
+
+ // Administration tasks will be performed with a blessing that represents a corporate
+ // administrator (which is usually a role account)
+ adminCreds, err := i.Shell().NewChildCredentials("r/admin")
+ if err != nil {
+ i.Fatalf("generating admin creds: %v", err)
+ }
+ adminOpts := i.Shell().DefaultStartOpts().ExternalProgram().WithCustomCredentials(adminCreds)
+ adminDeviceBin := deviceBin.WithStartOpts(adminOpts)
+ debugBin := i.BuildV23Pkg("v.io/x/ref/services/debug/debug").WithStartOpts(adminOpts)
+
+ // A special set of credentials will be used to give two blessings to the device manager
+ // when claiming it -- one blessing will be from the corporate administrator role who owns
+ // the machine, and the other will be a manufacturer blessing. (This is a hack until
+ // there's a way to separately supply a manufacturer blessing. Eventually, the claim
+ // would really be done by the administrator, and the administrator's blessing would get
+ // added to the manufacturer's blessing, which would already be present.)
+ claimCreds, err := i.Shell().AddToChildCredentials(adminCreds, "m/orange/zphone5/ime-i007")
+ if err != nil {
+ i.Fatalf("adding the mfr blessing to admin creds: %v", err)
+ }
+ claimOpts := i.Shell().DefaultStartOpts().ExternalProgram().WithCustomCredentials(claimCreds)
+ claimDeviceBin := deviceBin.WithStartOpts(claimOpts)
+
+ // Another set of credentials be used to represent the application publisher, who
+ // signs and pushes binaries
+ pubCreds, err := i.Shell().NewChildCredentials("a/rovio")
+ if err != nil {
+ i.Fatalf("generating publisher creds: %v", err)
+ }
+ pubOpts := i.Shell().DefaultStartOpts().ExternalProgram().WithCustomCredentials(pubCreds)
+ pubDeviceBin := deviceBin.WithStartOpts(pubOpts)
+ applicationBin := i.BuildV23Pkg("v.io/x/ref/services/application/application").WithStartOpts(pubOpts)
+
+ v23tests.RunRootMT(i, "--v23.tcp.address=127.0.0.1:0")
+ buildAndCopyBinaries(
+ i,
+ binStagingDir,
+ "v.io/x/ref/services/device/deviced",
+ "v.io/x/ref/services/agent/agentd",
+ "v.io/x/ref/services/device/suidhelper",
+ "v.io/x/ref/services/device/inithelper")
+
+ appDName := "applications"
+ devicedAppName := filepath.Join(appDName, "deviced", "test")
+
+ deviceScript.Start(
+ "install",
+ binStagingDir,
+ "--single_user",
+ "--origin="+devicedAppName,
+ "--",
+ "--v23.tcp.address=127.0.0.1:0",
+ "--neighborhood-name="+fmt.Sprintf("%s-%d-%d", hostname, os.Getpid(), rand.Int()),
+ ).WaitOrDie(os.Stdout, os.Stderr)
+ deviceScript.Start("start").WaitOrDie(os.Stdout, os.Stderr)
+ // Grab the endpoint for the claimable service from the device manager's
+ // log.
+ dmLog := filepath.Join(dmInstallDir, "dmroot/device-manager/logs/deviced.INFO")
+ var claimableEP string
+ expiry := time.Now().Add(30 * time.Second)
+ for {
+ if time.Now().After(expiry) {
+ i.Fatalf("Timed out looking for claimable endpoint in %v", dmLog)
+ }
+ startLog, err := ioutil.ReadFile(dmLog)
+ if err != nil {
+ i.Logf("Couldn't read log %v: %v", dmLog, err)
+ time.Sleep(time.Second)
+ continue
+ }
+ re := regexp.MustCompile(`Unclaimed device manager \((.*)\)`)
+ matches := re.FindSubmatch(startLog)
+ if len(matches) == 0 {
+ i.Logf("Couldn't find match in %v [%v]", dmLog, startLog)
+ time.Sleep(time.Second)
+ continue
+ }
+ if len(matches) != 2 {
+ i.Fatalf("Wrong match in %v (%d) %v", dmLog, len(matches), string(matches[0]))
+ }
+ claimableEP = string(matches[1])
+ break
+ }
+ // Claim the device as "root/u/alice/myworkstation".
+ claimDeviceBin.Start("claim", claimableEP, "myworkstation")
+
+ resolve := func(name string) string {
+ resolver := func() (interface{}, error) {
+ // Use Start, rather than Run, since it's ok for 'namespace resolve'
+ // to fail with 'name doesn't exist'
+ inv := namespaceBin.Start("resolve", name)
+ // Cleanup after ourselves to avoid leaving a ton of invocations
+ // lying around which obscure logging output.
+ defer inv.Wait(nil, os.Stderr)
+ if r := strings.TrimRight(inv.Output(), "\n"); len(r) > 0 {
+ return r, nil
+ }
+ return nil, nil
+ }
+ return i.WaitFor(resolver, 100*time.Millisecond, time.Minute).(string)
+ }
+
+ // Wait for the device manager to publish its mount table entry.
+ resolve(mtName)
+ adminDeviceBin.Run("acl", "set", mtName+"/devmgr/device", "root/u/alice", "Read,Resolve,Write")
+
+ // Verify the device's default blessing is as expected.
+ mfrBlessing := "root/m/orange/zphone5/ime-i007/myworkstation"
+ ownerBlessing := "root/r/admin/myworkstation"
+ inv := debugBin.Start("stats", "read", mtName+"/devmgr/__debug/stats/security/principal/*/blessingstore")
+ inv.ExpectSetEventuallyRE(".*Default Blessings[ ]+" + mfrBlessing + "," + ownerBlessing)
+
+ // Get the device's profile, which should be set to non-empty string
+ inv = adminDeviceBin.Start("describe", mtName+"/devmgr/device")
+
+ parts := inv.ExpectRE(`{Profiles:map\[(.*):{}\]}`, 1)
+ expectOneMatch := func(parts [][]string) string {
+ if len(parts) != 1 || len(parts[0]) != 2 {
+ loc := v23tests.Caller(1)
+ i.Fatalf("%s: failed to match profile: %#v", loc, parts)
+ }
+ return parts[0][1]
+ }
+ deviceProfile := expectOneMatch(parts)
+ if len(deviceProfile) == 0 {
+ i.Fatalf("failed to get profile")
+ }
+
+ // Start a binaryd server that will serve the binary for the test
+ // application to be installed on the device.
+ binarydName := "binaries"
+ binarydBin.Start(
+ "--name=binaries",
+ "--root-dir="+filepath.Join(workDir, "binstore"),
+ "--v23.tcp.address=127.0.0.1:0",
+ "--http=127.0.0.1:0")
+ // Allow publishers to update binaries
+ deviceBin.Run("acl", "set", binarydName, "root/a", "Write")
+
+ // Start an applicationd server that will serve the application
+ // envelope for the test application to be installed on the device.
+ applicationdBin.Start(
+ "--name="+appDName,
+ "--store="+mkSubdir(i, workDir, "appstore"),
+ "--v23.tcp.address=127.0.0.1:0",
+ )
+ // Allow publishers to create and update envelopes
+ deviceBin.Run("acl", "set", appDName, "root/a", "Read,Write,Resolve")
+
+ syncbasedName := appDName + "/syncbased/0"
+ syncbasedBinName := binarydName + "/syncbased"
+ syncbasedEnvelopeFilename := filepath.Join(workDir, "syncbased.envelope")
+ syncbasedEnvelope := fmt.Sprintf("{\"Title\":\"syncbased\","+
+ "\"Args\":[\"-v=0\", \"--name=syncbased\", \"--root-dir=%s\", \"--v23.tcp.address=127.0.0.1:0\"]}",
+ mkSubdir(i, workDir, "syncbased"))
+ ioutil.WriteFile(syncbasedEnvelopeFilename, []byte(syncbasedEnvelope), 0666)
+ defer os.Remove(syncbasedEnvelopeFilename)
+
+ output := applicationBin.Run("put", syncbasedName, deviceProfile, syncbasedEnvelopeFilename)
+ if got, want := output, "Application envelope added successfully."; got != want {
+ i.Fatalf("got %q, want %q", got, want)
+ }
+
+ // Publish the app.
+ pubDeviceBin.Start("publish", "-from", filepath.Dir(syncbasedBin.Path()), "-readers", "root/r/admin", "syncbased").WaitOrDie(os.Stdout, os.Stderr)
+ if got := namespaceBin.Run("glob", syncbasedBinName); len(got) == 0 {
+ i.Fatalf("glob failed for %q", syncbasedBinName)
+ }
+
+ // Install the app on the device.
+ inv = deviceBin.Start("install", mtName+"/devmgr/apps", syncbasedName)
+ installationName := inv.ReadLine()
+ if installationName == "" {
+ i.Fatalf("got empty installation name from install")
+ }
+
+ // Verify that the installation shows up when globbing the device manager.
+ output = namespaceBin.Run("glob", mtName+"/devmgr/apps/syncbased/*")
+ if got, want := output, installationName; got != want {
+ i.Fatalf("got %q, want %q", got, want)
+ }
+
+ // Start an instance of the app, granting it blessing extension syncbased.
+ inv = deviceBin.Start("instantiate", installationName, "syncbased")
+ instanceName := inv.ReadLine()
+ if instanceName == "" {
+ i.Fatalf("got empty instance name from new")
+ }
+ deviceBin.Start("run", instanceName).Wait(os.Stdout, os.Stderr)
+
+ resolve(mtName + "/syncbased")
+
+ // Verify that the instance shows up when globbing the device manager.
+ output = namespaceBin.Run("glob", mtName+"/devmgr/apps/syncbased/*/*")
+ if got, want := output, instanceName; got != want {
+ i.Fatalf("got %q, want %q", got, want)
+ }
+
+ // Verify the app's blessings. We check the default blessing, as well as the
+ // "..." blessing, which should be the default blessing plus a publisher blessing.
+ userBlessing := "root/u/alice/syncbased"
+ pubBlessing := "root/a/rovio/apps/published/syncbased"
+ // Just to remind:
+ // mfrBlessing = "root/m/orange/zphone5/ime-i007/myworkstation"
+ // ownerBlessing = "root/r/admin/myworkstation"
+ appBlessing := mfrBlessing + "/a/" + pubBlessing + "," + ownerBlessing + "/a/" + pubBlessing
+ inv = debugBin.Start("stats", "read", instanceName+"/stats/security/principal/*/blessingstore")
+ inv.ExpectSetEventuallyRE(".*Default Blessings[ ]+"+userBlessing+"$", "[.][.][.][ ]+"+userBlessing+","+appBlessing)
+}
+
+func buildAndCopyBinaries(i *v23tests.T, destinationDir string, packages ...string) {
+ var args []string
+ for _, pkg := range packages {
+ args = append(args, i.BuildGoPkg(pkg).Path())
+ }
+ args = append(args, destinationDir)
+ i.BinaryFromPath("/bin/cp").Start(args...).WaitOrDie(os.Stdout, os.Stderr)
+}
+
+func mkSubdir(i *v23tests.T, parent, child string) string {
+ dir := filepath.Join(parent, child)
+ if err := os.Mkdir(dir, 0755); err != nil {
+ i.Fatalf("failed to create %q: %v", dir, err)
+ }
+ return dir
+}
diff --git a/v23/syncbase/v23_test.go b/v23/syncbase/v23_test.go
index 914c0a2..c075d3f 100644
--- a/v23/syncbase/v23_test.go
+++ b/v23/syncbase/v23_test.go
@@ -32,3 +32,7 @@
func TestV23ServiceRestart(t *testing.T) {
v23tests.RunTest(t, V23TestServiceRestart)
}
+
+func TestV23DeviceManager(t *testing.T) {
+ v23tests.RunTest(t, V23TestDeviceManager)
+}