blob: 66d4acf495ad4ba5fb164cd842d2a63f2af4d698 [file] [log] [blame]
// 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)
// Kill and delete the instance.
deviceBin.Run("kill", instanceName)
deviceBin.Run("delete", instanceName)
// Shut down the device manager.
deviceScript.Run("stop")
// Wait for the mounttable entry to go away.
resolveGone := func(name string) string {
resolver := func() (interface{}, error) {
inv := namespaceBin.Start("resolve", name)
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)
}
resolveGone(mtName)
deviceScript.Run("uninstall")
}
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
}