| // 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. |
| |
| // Test the device manager and related services and tools. |
| // |
| // By default, this script tests the device manager in a fashion amenable |
| // to automatic testing: the --single_user is passed to the device |
| // manager so that all device manager components run as the same user and |
| // no user input (such as an agent pass phrase) is needed. |
| // |
| // This script can exercise the device manager in two different modes. It |
| // can be executed like so: |
| // |
| // jiri go test -v . --v23.tests |
| // |
| // This will exercise the device manager's single user mode where all |
| // processes run as the same invoking user. |
| // |
| // Alternatively, the device manager can be executed in multiple account |
| // mode by providing the --deviceuser <deviceuser> and --appuser |
| // <appuser> flags. In this case, the device manager will run as user |
| // <devicemgr> and the test will run applications as user <appuser>. If |
| // executed in this fashion, root permissions will be required to install |
| // and it may require configuring an agent passphrase. For example: |
| // |
| // jiri go test -v . --v23.tests --deviceuser devicemanager --appuser vana |
| // |
| // NB: the accounts provided as arguments to this test must already exist. |
| // Also, the --v23.tests.shell-on-fail flag is useful to enable debugging |
| // output. Note that this flag does not work for some shells. Set |
| // $SHELL in that case. |
| |
| package device_test |
| |
| import ( |
| "errors" |
| "flag" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "math/rand" |
| "os" |
| "os/user" |
| "path/filepath" |
| "regexp" |
| "runtime" |
| "strings" |
| "sync" |
| "testing" |
| "time" |
| |
| "v.io/x/ref" |
| _ "v.io/x/ref/runtime/factories/generic" |
| "v.io/x/ref/services/internal/dirprinter" |
| "v.io/x/ref/test/testutil" |
| "v.io/x/ref/test/v23test" |
| ) |
| |
| var ( |
| appUserFlag string |
| deviceUserFlag string |
| hostname string |
| errTimeout = errors.New("timeout") |
| ) |
| |
| func init() { |
| flag.StringVar(&appUserFlag, "appuser", "", "launch apps as the specified user") |
| flag.StringVar(&deviceUserFlag, "deviceuser", "", "run the device manager as the specified user") |
| name, err := os.Hostname() |
| if err != nil { |
| panic(fmt.Sprintf("Hostname() failed: %v", err)) |
| } |
| hostname = name |
| } |
| |
| func TestV23DeviceManagerSingleUser(t *testing.T) { |
| v23test.SkipUnlessRunningIntegrationTests(t) |
| sh := v23test.NewShell(t, nil) |
| defer sh.Cleanup() |
| |
| u, err := user.Current() |
| if err != nil { |
| t.Fatalf("couldn't get the current user: %v", err) |
| } |
| testCore(t, sh, u.Username, "", false) |
| } |
| |
| func TestV23DeviceManagerMultiUser(t *testing.T) { |
| t.Skip("Permissions need to be configured properly on credentials.") |
| v23test.SkipUnlessRunningIntegrationTests(t) |
| sh := v23test.NewShell(t, nil) |
| defer sh.Cleanup() |
| |
| u, err := user.Current() |
| if err != nil { |
| t.Fatalf("couldn't get the current user: %v", err) |
| } |
| |
| if u.Username == "veyron" && runTestOnThisPlatform { |
| // We are running on the builder so run the multiuser |
| // test with default user names. These will be created as |
| // required. |
| makeTestAccounts(t, sh) |
| testCore(t, sh, "vana", "devicemanager", true) |
| return |
| } |
| |
| if len(deviceUserFlag) > 0 && len(appUserFlag) > 0 { |
| testCore(t, sh, appUserFlag, deviceUserFlag, true) |
| } else { |
| t.Logf("Test skipped because running in multiuser mode requires --appuser and --deviceuser flags") |
| } |
| } |
| |
| func testCore(t *testing.T, sh *v23test.Shell, appUser, deviceUser string, withSuid bool) { |
| defer fmt.Fprintf(os.Stderr, "--------------- SHUTDOWN ---------------\n") |
| |
| // Call sh.StartRootMountTable() first, since it updates sh.Vars, which is |
| // copied by various Cmds at Cmd creation time. |
| sh.StartRootMountTable() |
| |
| // When running with --with_suid, TMPDIR must grant the invoking user rwx |
| // permissions and world x permissions for all parent directories back to /. |
| // Otherwise, the with_suid user will not be able to use absolute paths. On |
| // Darwin, TMPDIR defaults to a directory hierararchy in /var that is 0700. |
| // This is unworkable, so force TMPDIR to /tmp in this case. |
| // |
| // In addition, even when running without --with_suid, on Darwin the default |
| // TMPDIR results in "socket path (...) exceeds maximum allowed socket path |
| // length" errors, so we always set TMPDIR to /tmp. |
| oldTmpdir := os.Getenv("TMPDIR") |
| os.Setenv("TMPDIR", "/tmp") |
| defer os.Setenv("TMPDIR", oldTmpdir) |
| |
| var ( |
| workDir = sh.MakeTempDir() |
| binStagingDir = mkSubdir(t, 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 = sh.ForkCredentials("u:alice") |
| |
| // 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 = sh.Cmd(v23test.BuildGoPkg(sh, "v.io/x/ref/cmd/namespace")).WithCredentials(aliceCreds) |
| deviceBin = sh.Cmd(v23test.BuildGoPkg(sh, "v.io/x/ref/services/device/device")).WithCredentials(aliceCreds) |
| binarydBin = sh.Cmd(v23test.BuildGoPkg(sh, "v.io/x/ref/services/binary/binaryd")).WithCredentials(aliceCreds) |
| applicationdBin = sh.Cmd(v23test.BuildGoPkg(sh, "v.io/x/ref/services/application/applicationd")).WithCredentials(aliceCreds) |
| |
| // 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. |
| deviceScript = sh.Cmd("./devicex") |
| |
| mtName = "devices/" + hostname // Name under which the device manager will publish itself. |
| ) |
| |
| defer func() { |
| if !t.Failed() { |
| return |
| } |
| fmt.Fprintf(os.Stderr, "--------------- START DUMP %s ---------------\n", workDir) |
| if err := dirprinter.DumpDir(os.Stderr, workDir); err != nil { |
| fmt.Fprintf(os.Stderr, "Failed: %v\n", err) |
| } |
| fmt.Fprintf(os.Stderr, "--------------- END DUMP %s ---------------\n", workDir) |
| }() |
| deviceScript.Vars["V23_DEVICE_DIR"] = dmInstallDir |
| // Make sure the devicex command is not provided with credentials. Note, this |
| // is analogous to what's done in |
| // v.io/x/ref/services/device/deviced/internal/impl/utiltest.RunDeviceManager. |
| delete(deviceScript.Vars, ref.EnvCredentials) |
| delete(deviceScript.Vars, ref.EnvAgentPath) |
| |
| // We also need some tools running with different sets of credentials... |
| |
| // Administration tasks will be performed with a blessing that represents a corporate |
| // adminstrator (which is usually a role account) |
| adminCreds := sh.ForkCredentials("r:admin") |
| adminDeviceBin := deviceBin.WithCredentials(adminCreds) |
| debugBin := sh.Cmd(v23test.BuildGoPkg(sh, "v.io/x/ref/services/debug/debug")).WithCredentials(adminCreds) |
| |
| // 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 administator, and the adminstrator's blessing would get |
| // added to the manufacturer's blessing, which would already be present.) |
| claimCreds := sh.ForkCredentials("r:admin", "m:orange:zphone5:ime-i007") |
| claimDeviceBin := deviceBin.WithCredentials(claimCreds) |
| |
| // Another set of credentials be used to represent the application publisher, who |
| // signs and pushes binaries |
| pubCreds := sh.ForkCredentials("a:rovio") |
| pubDeviceBin := deviceBin.WithCredentials(pubCreds) |
| applicationBin := sh.Cmd(v23test.BuildGoPkg(sh, "v.io/x/ref/services/application/application")).WithCredentials(pubCreds) |
| binaryBin := sh.Cmd(v23test.BuildGoPkg(sh, "v.io/x/ref/services/binary/binary")).WithCredentials(pubCreds) |
| |
| if withSuid { |
| // In multiuser mode, deviceUserFlag needs execute access to |
| // tempDir. |
| if err := os.Chmod(workDir, 0711); err != nil { |
| t.Fatalf("os.Chmod() failed: %v", err) |
| } |
| } |
| |
| buildAndCopyBinaries( |
| t, sh, binStagingDir, |
| "v.io/x/ref/services/device/deviced", |
| "v.io/x/ref/services/device/restarter", |
| "v.io/x/ref/services/agent/v23agentd", |
| "v.io/x/ref/services/device/suidhelper", |
| "v.io/x/ref/services/device/inithelper") |
| |
| appDName := "applications" |
| devicedAppName := filepath.Join(appDName, "deviced", "test") |
| |
| deviceScriptArguments := []string{ |
| "install", |
| binStagingDir, |
| } |
| |
| if withSuid { |
| deviceScriptArguments = append(deviceScriptArguments, "--devuser="+deviceUser) |
| } else { |
| deviceScriptArguments = append(deviceScriptArguments, "--single_user") |
| } |
| |
| deviceScriptArguments = append(deviceScriptArguments, []string{ |
| "--origin=" + devicedAppName, |
| "--", |
| "--v23.tcp.address=127.0.0.1:0", |
| "--neighborhood-name=" + fmt.Sprintf("%s-%d-%d", hostname, os.Getpid(), rand.Int()), |
| }...) |
| |
| withArgs(deviceScript, deviceScriptArguments...).Run() |
| withArgs(deviceScript, "start").Run() |
| dmLog := filepath.Join(dmInstallDir, "dmroot/device-manager/logs/deviced.INFO") |
| stopDevMgr := func() { |
| withArgs(deviceScript, "stop").Run() |
| if dmLogF, err := os.Open(dmLog); err != nil { |
| t.Errorf("Failed to read dm log: %v", err) |
| } else { |
| fmt.Fprintf(os.Stderr, "--------------- START DM LOG ---------------\n") |
| defer dmLogF.Close() |
| if _, err := io.Copy(os.Stderr, dmLogF); err != nil { |
| t.Errorf("Error dumping dm log: %v", err) |
| } |
| fmt.Fprintf(os.Stderr, "--------------- END DM LOG ---------------\n") |
| } |
| } |
| var stopDevMgrOnce sync.Once |
| defer stopDevMgrOnce.Do(stopDevMgr) |
| // Grab the endpoint for the claimable service from the device manager's |
| // log. |
| var claimableEP string |
| expiry := time.Now().Add(30 * time.Second) |
| for { |
| if time.Now().After(expiry) { |
| t.Fatalf("Timed out looking for claimable endpoint in %v", dmLog) |
| } |
| startLog, err := ioutil.ReadFile(dmLog) |
| if err != nil { |
| t.Logf("Couldn't read log %v: %v", dmLog, err) |
| time.Sleep(time.Second) |
| continue |
| } |
| re := regexp.MustCompile(`Unclaimed device manager endpoint: (.*)`) |
| matches := re.FindSubmatch(startLog) |
| if len(matches) == 0 { |
| t.Logf("Couldn't find match in %v [%s]", dmLog, startLog) |
| time.Sleep(time.Second) |
| continue |
| } |
| if len(matches) < 2 { |
| t.Fatalf("Wrong match in %v (%d) %v", dmLog, len(matches), string(matches[0])) |
| } |
| claimableEP = string(matches[len(matches)-1]) |
| break |
| } |
| // Claim the device as "root:u:alice:myworkstation". |
| claimCmd := withArgs(claimDeviceBin, "claim", claimableEP, "myworkstation") |
| // TODO(caprita): Sometimes, the claim RPC fails to complete, even |
| // though the server-side claiming seems to succeed. See |
| // https://github.com/vanadium/build/issues/58 |
| claimCmd.ExitErrorIsOk = true |
| claimCmd.Run() |
| |
| resolve := func(name string) string { |
| res := "" |
| if err := testutil.RetryFor(10*time.Second, func() error { |
| // Set ExitErrorIsOk to true since we expect "namespace resolve" to fail |
| // if the name doesn't exist. |
| c := withArgs(namespaceBin, "resolve", "-s", name) |
| c.ExitErrorIsOk = true |
| c.AddStderrWriter(os.Stderr) |
| if res = tr(c.Stdout()); len(res) > 0 { |
| return nil |
| } |
| return testutil.TryAgain(errors.New("resolve returned nothing")) |
| }); err != nil { |
| t.Fatal(err) |
| } |
| return res |
| } |
| |
| // Wait for the device manager to publish its mount table entry. |
| mtEP := resolve(mtName) |
| withArgs(adminDeviceBin, "acl", "set", mtName+"/devmgr/device", "root:u:alice", "Read,Resolve,Write").Run() |
| |
| if withSuid { |
| withArgs(adminDeviceBin, "associate", "add", mtName+"/devmgr/device", appUser, "root:u:alice").Run() |
| associations := withArgs(adminDeviceBin, "associate", "list", mtName+"/devmgr/device").Stdout() |
| if got, expected := strings.Trim(associations, "\n "), "root:u:alice "+appUser; got != expected { |
| t.Fatalf("association test, got %v, expected %v", got, expected) |
| } |
| } |
| |
| // Verify the device's default blessing is as expected. |
| mfrBlessing := "root:m:orange:zphone5:ime-i007:myworkstation" |
| ownerBlessing := "root:r:admin:myworkstation" |
| c := withArgs(debugBin, "stats", "read", mtName+"/devmgr/__debug/stats/security/principal/*/blessingstore/*") |
| c.Run() |
| c.S.ExpectSetEventuallyRE(".*Default Blessings[ ]+" + mfrBlessing + "," + ownerBlessing) |
| |
| // Get the device's profile, which should be set to non-empty string |
| c = withArgs(adminDeviceBin, "describe", mtName+"/devmgr/device") |
| c.Run() |
| parts := c.S.ExpectRE(`{Profiles:map\[(.*):{}\]}`, 1) |
| expectOneMatch := func(parts [][]string) string { |
| if len(parts) != 1 || len(parts[0]) != 2 { |
| t.Fatalf("%s: failed to match profile: %#v", caller(1), parts) |
| } |
| return parts[0][1] |
| } |
| deviceProfile := expectOneMatch(parts) |
| if len(deviceProfile) == 0 { |
| t.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" |
| withArgs(binarydBin, |
| "--name="+binarydName, |
| "--root-dir="+filepath.Join(workDir, "binstore"), |
| "--v23.tcp.address=127.0.0.1:0", |
| "--http=127.0.0.1:0").Start() |
| // Allow publishers to update binaries |
| withArgs(deviceBin, "acl", "set", binarydName, "root:a", "Write").Run() |
| |
| // We are also going to use the binaryd binary as our test app binary. Once our test app |
| // binary is published to the binaryd server started above, this (augmented with a |
| // timestamp) is the name the test app binary will have. |
| sampleAppBinName := binarydName + "/binaryd" |
| |
| // Start an applicationd server that will serve the application |
| // envelope for the test application to be installed on the device. |
| withArgs(applicationdBin, |
| "--name="+appDName, |
| "--store="+mkSubdir(t, workDir, "appstore"), |
| "--v23.tcp.address=127.0.0.1:0").Start() |
| // Allow publishers to create and update envelopes |
| withArgs(deviceBin, "acl", "set", appDName, "root:a", "Read,Write,Resolve").Run() |
| |
| sampleAppName := appDName + "/testapp" |
| appPubName := "testbinaryd" |
| appEnvelopeFilename := filepath.Join(workDir, "app.envelope") |
| appEnvelope := fmt.Sprintf("{\"Title\":\"BINARYD\", \"Args\":[\"--name=%s\", \"--root-dir=./binstore\", \"--v23.tcp.address=127.0.0.1:0\", \"--http=127.0.0.1:0\"], \"Binary\":{\"File\":%q}, \"Env\":[ \"%s=1\", \"PATH=%s\"]}", appPubName, sampleAppBinName, ref.EnvCredentialsNoAgent, os.Getenv("PATH")) |
| ioutil.WriteFile(appEnvelopeFilename, []byte(appEnvelope), 0666) |
| defer os.Remove(appEnvelopeFilename) |
| |
| output := withArgs(applicationBin, "put", sampleAppName+"/0", deviceProfile, appEnvelopeFilename).Stdout() |
| if got, want := tr(output), fmt.Sprintf("Application envelope added for profile %s.", deviceProfile); got != want { |
| t.Fatalf("got %q, want %q", got, want) |
| } |
| |
| // Verify that the envelope we uploaded shows up with glob. |
| c = withArgs(applicationBin, "match", sampleAppName, deviceProfile) |
| c.Run() |
| parts = c.S.ExpectSetEventuallyRE(`"Title": "(.*)",`, `"File": "(.*)",`) |
| if got, want := len(parts), 2; got != want { |
| t.Fatalf("got %d, want %d", got, want) |
| } |
| for line, want := range []string{"BINARYD", sampleAppBinName} { |
| if got := parts[line][1]; got != want { |
| t.Fatalf("got %q, want %q", got, want) |
| } |
| } |
| |
| // Publish the app (This uses the binarydBin binary and the testapp envelope from above) |
| withArgs(pubDeviceBin, "publish", "-from", filepath.Dir(binarydBin.Path), "-readers", "root:r:admin", filepath.Base(binarydBin.Path)+":testapp").Run() |
| if got := withArgs(namespaceBin, "glob", sampleAppBinName).Stdout(); len(got) == 0 { |
| t.Fatalf("glob failed for %q", sampleAppBinName) |
| } |
| |
| // Install the app on the device. |
| c = withArgs(deviceBin, "install", mtName+"/devmgr/apps", sampleAppName) |
| c.Run() |
| installationName := c.S.ReadLine() |
| if installationName == "" { |
| t.Fatalf("got empty installation name from install") |
| } |
| |
| // Verify that the installation shows up when globbing the device manager. |
| output = withArgs(namespaceBin, "glob", mtName+"/devmgr/apps/BINARYD/*").Stdout() |
| if got, want := tr(output), installationName; got != want { |
| t.Fatalf("got %q, want %q", got, want) |
| } |
| |
| // Start an instance of the app, granting it blessing extension myapp. |
| c = withArgs(deviceBin, "instantiate", installationName, "myapp") |
| c.Run() |
| instanceName := c.S.ReadLine() |
| if instanceName == "" { |
| t.Fatalf("got empty instance name from new") |
| } |
| withArgs(deviceBin, "run", instanceName).Run() |
| |
| resolve(mtName + "/" + appPubName) |
| |
| // Verify that the instance shows up when globbing the device manager. |
| output = withArgs(namespaceBin, "glob", mtName+"/devmgr/apps/BINARYD/*/*").Stdout() |
| if got, want := tr(output), instanceName; got != want { |
| t.Fatalf("got %q, want %q", got, want) |
| } |
| |
| c = withArgs(debugBin, "stats", "read", instanceName+"/stats/system/pid") |
| c.Run() |
| pid := c.S.ExpectRE("[0-9]+$", 1)[0][0] |
| uname, err := getUserForPid(sh, pid) |
| if err != nil { |
| t.Errorf("getUserForPid could not determine the user running pid %v", pid) |
| } else if uname != appUser { |
| t.Errorf("app expected to be running as %v but is running as %v", appUser, uname) |
| } |
| |
| // 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:myapp" |
| pubBlessing := "root:a:rovio:apps:published:binaryd" |
| appBlessing := mfrBlessing + ":a:" + pubBlessing + "," + ownerBlessing + ":a:" + pubBlessing |
| c = withArgs(debugBin, "stats", "read", instanceName+"/stats/security/principal/*/blessingstore/*") |
| c.Run() |
| c.S.ExpectSetEventuallyRE(".*Default Blessings[ ]+"+userBlessing+"$", "[.][.][.][ ]+"+userBlessing+","+appBlessing) |
| |
| // Kill and delete the instance. |
| withArgs(deviceBin, "kill", instanceName).Run() |
| withArgs(deviceBin, "delete", instanceName).Run() |
| |
| // Verify that logs, but not stats, show up when globbing the |
| // not-running instance. |
| if output = withArgs(namespaceBin, "glob", instanceName+"/stats/...").Stdout(); len(output) > 0 { |
| t.Fatalf("no output expected for glob %s/stats/..., got %q", output, instanceName) |
| } |
| if output = withArgs(namespaceBin, "glob", instanceName+"/logs/...").Stdout(); len(output) == 0 { |
| t.Fatalf("output expected for glob %s/logs/..., but got none", instanceName) |
| } |
| |
| // TODO: The deviced binary should probably be published by someone other than rovio :-) |
| // Maybe publishing the deviced binary should eventually use "device publish" too? |
| // For now, it uses the "application" and "binary" tools directly to ensure that those work |
| |
| // Upload a deviced binary |
| devicedAppBinName := binarydName + "/deviced" |
| withArgs(binaryBin, "upload", devicedAppBinName, v23test.BuildGoPkg(sh, "v.io/x/ref/services/device/deviced")).Run() |
| // Allow root:r:admin and its devices to read the binary |
| withArgs(deviceBin, "acl", "set", devicedAppBinName, "root:r:admin", "Read").Run() |
| |
| // Upload a device manager envelope. |
| devicedEnvelopeFilename := filepath.Join(workDir, "deviced.envelope") |
| devicedEnvelope := fmt.Sprintf("{\"Title\":\"device manager\", \"Binary\":{\"File\":%q}, \"Env\":[ \"%s=1\", \"PATH=%s\"]}", devicedAppBinName, ref.EnvCredentialsNoAgent, os.Getenv("PATH")) |
| ioutil.WriteFile(devicedEnvelopeFilename, []byte(devicedEnvelope), 0666) |
| defer os.Remove(devicedEnvelopeFilename) |
| withArgs(applicationBin, "put", devicedAppName, deviceProfile, devicedEnvelopeFilename).Run() |
| // Allow root:r:admin and its devices to read the envelope |
| withArgs(deviceBin, "acl", "set", devicedAppName, "root:r:admin", "Read").Run() |
| |
| // Update the device manager. |
| withArgs(adminDeviceBin, "update", mtName+"/devmgr/device").Run() |
| resolveChange := func(name, old string) string { |
| res := "" |
| if err := testutil.RetryFor(10*time.Second, func() error { |
| // Set ExitErrorIsOk to true since we expect "namespace resolve" to fail |
| // if the name doesn't exist. |
| c := withArgs(namespaceBin, "resolve", "-s", name) |
| c.ExitErrorIsOk = true |
| c.AddStderrWriter(os.Stderr) |
| switch res = tr(c.Stdout()); { |
| case res == "": |
| return testutil.TryAgain(errors.New("resolve returned nothing")) |
| case res == old: |
| return testutil.TryAgain(errors.New("no change")) |
| } |
| return nil |
| }); err != nil { |
| t.Fatal(err) |
| } |
| return res |
| } |
| mtEP = resolveChange(mtName, mtEP) |
| |
| // Verify that device manager's mounttable is still published under the |
| // expected name (hostname). |
| if withArgs(namespaceBin, "glob", mtName).Stdout() == "" { |
| t.Fatalf("failed to glob %s", mtName) |
| } |
| |
| // Revert the device manager |
| // The argument to "device revert" is a glob pattern. So we need to |
| // wait for devmgr to be mounted before running the command. |
| resolve(mtEP + "/devmgr") |
| withArgs(adminDeviceBin, "revert", mtName+"/devmgr/device").Run() |
| mtEP = resolveChange(mtName, mtEP) |
| |
| // Verify that device manager's mounttable is still published under the |
| // expected name (hostname). |
| if withArgs(namespaceBin, "glob", mtName).Stdout() == "" { |
| t.Fatalf("failed to glob %s", mtName) |
| } |
| |
| // Verify that the local mounttable exists, and that the device manager, |
| // the global namespace, and the neighborhood are mounted on it. |
| resolve(mtEP + "/devmgr") |
| resolve(mtEP + "/nh") |
| resolve(mtEP + "/global") |
| |
| namespaceRoot := sh.Vars[ref.EnvNamespacePrefix] |
| output = withArgs(namespaceBin, "resolve", "-s", mtEP+"/global").Stdout() |
| if got, want := tr(output), namespaceRoot; got != want { |
| t.Fatalf("got %q, want %q", got, want) |
| } |
| |
| // Kill the device manager (which causes it to be restarted), wait for |
| // the endpoint to change. |
| withArgs(deviceBin, "kill", mtName+"/devmgr/device").Run() |
| mtEP = resolveChange(mtName, mtEP) |
| |
| // Shut down the device manager. |
| stopDevMgrOnce.Do(stopDevMgr) |
| |
| // Wait for the mounttable entry to go away. |
| resolveGone := func(name string) string { |
| res := "" |
| if err := testutil.RetryFor(10*time.Second, func() error { |
| // Set ExitErrorIsOk to true since we expect "namespace resolve" to fail |
| // if the name doesn't exist. |
| c := withArgs(namespaceBin, "resolve", "-s", name) |
| c.ExitErrorIsOk = true |
| c.AddStderrWriter(os.Stderr) |
| if res = tr(c.Stdout()); len(res) == 0 { |
| return nil |
| } |
| return testutil.TryAgain(errors.New("mount table entry still exists")) |
| }); err != nil { |
| t.Fatal(err) |
| } |
| return res |
| } |
| resolveGone(mtName) |
| |
| var fi []os.FileInfo |
| |
| // This doesn't work in multiuser mode because dmInstallDir is |
| // owned by the device manager user and unreadable by the user |
| // running this test. |
| if !withSuid { |
| fi, err = ioutil.ReadDir(dmInstallDir) |
| if err != nil { |
| t.Fatalf("failed to readdir for %q: %v", dmInstallDir, err) |
| } |
| } |
| |
| withArgs(deviceScript, "uninstall").Run() |
| |
| fi, err = ioutil.ReadDir(dmInstallDir) |
| if err == nil || len(fi) > 0 { |
| t.Fatalf("managed to read %d entries from %q", len(fi), dmInstallDir) |
| } |
| if err != nil && !strings.Contains(err.Error(), "no such file or directory") { |
| t.Fatalf("wrong error: %v", err) |
| } |
| } |
| |
| func withArgs(cmd *v23test.Cmd, args ...string) *v23test.Cmd { |
| res := cmd.Clone() |
| res.Args = append(res.Args, args...) |
| return res |
| } |
| |
| func buildAndCopyBinaries(t *testing.T, sh *v23test.Shell, destinationDir string, packages ...string) { |
| var args []string |
| for _, pkg := range packages { |
| args = append(args, v23test.BuildGoPkg(sh, pkg)) |
| } |
| args = append(args, destinationDir) |
| sh.Cmd("/bin/cp", args...).Run() |
| } |
| |
| func mkSubdir(t *testing.T, parent, child string) string { |
| dir := filepath.Join(parent, child) |
| if err := os.Mkdir(dir, 0755); err != nil { |
| t.Fatalf("failed to create %q: %v", dir, err) |
| } |
| return dir |
| } |
| |
| var re = regexp.MustCompile("[ \t]+") |
| |
| // getUserForPid determines the username running process pid. |
| func getUserForPid(sh *v23test.Shell, pid string) (string, error) { |
| pidString := sh.Cmd("/bin/ps", psFlags).Stdout() |
| for _, line := range strings.Split(pidString, "\n") { |
| fields := re.Split(line, -1) |
| if len(fields) > 1 && pid == fields[1] { |
| return fields[0], nil |
| } |
| } |
| return "", fmt.Errorf("Couldn't determine the user for pid %s", pid) |
| } |
| |
| // tr trims off trailing newline characters. |
| func tr(s string) string { |
| return strings.TrimRight(s, "\n") |
| } |
| |
| // caller returns a string of the form <filename>:<lineno>. |
| func caller(skip int) string { |
| _, file, line, _ := runtime.Caller(skip + 1) |
| return fmt.Sprintf("%s:%d", filepath.Base(file), line) |
| } |
| |
| func TestMain(m *testing.M) { |
| v23test.TestMain(m) |
| } |