blob: 0293a990cbae5d6d23b708e7616dcfb8550dcc39 [file] [log] [blame]
package impl_test
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"runtime"
"sort"
"strings"
"testing"
"time"
"veyron.io/veyron/veyron2"
"veyron.io/veyron/veyron2/ipc"
"veyron.io/veyron/veyron2/naming"
"veyron.io/veyron/veyron2/security"
"veyron.io/veyron/veyron2/services/mgmt/device"
"veyron.io/veyron/veyron2/verror"
"veyron.io/veyron/veyron2/verror2"
"veyron.io/veyron/veyron2/vlog"
"veyron.io/veyron/veyron/lib/expect"
"veyron.io/veyron/veyron/lib/flags/consts"
"veyron.io/veyron/veyron/lib/modules"
"veyron.io/veyron/veyron/lib/modules/core"
tsecurity "veyron.io/veyron/veyron/lib/testutil/security"
_ "veyron.io/veyron/veyron/profiles/static"
"veyron.io/veyron/veyron/services/mgmt/device/impl"
"veyron.io/veyron/veyron2/services/mgmt/application"
)
const (
// Setting this environment variable to any non-empty value avoids
// removing the device manager's workspace for successful test runs (for
// failed test runs, this is already the case). This is useful when
// developing test cases.
preserveDMWorkspaceEnv = "VEYRON_TEST_PRESERVE_DM_WORKSPACE"
// TODO(caprita): Set the timeout in a more principled manner.
expectTimeout = 20 * time.Second
)
func loc(d int) string {
_, file, line, _ := runtime.Caller(d + 1)
return fmt.Sprintf("%s:%d", filepath.Base(file), line)
}
func startRootMT(t *testing.T, sh *modules.Shell) (string, modules.Handle) {
h, err := sh.Start(core.RootMTCommand, nil, "--", "--veyron.tcp.address=127.0.0.1:0")
if err != nil {
t.Fatalf("failed to start root mount table: %s", err)
}
s := expect.NewSession(t, h.Stdout(), expectTimeout)
s.ExpectVar("PID")
rootName := s.ExpectVar("MT_NAME")
if t.Failed() {
t.Fatalf("failed to read mt name: %s", s.Error())
}
return rootName, h
}
func credentialsForChild(blessing string) (string, []string) {
creds, _ := tsecurity.ForkCredentials(globalRT.Principal(), blessing)
return creds, []string{consts.VeyronCredentials + "=" + creds}
}
// setNSRoots sets the roots for the local runtime's namespace.
func setNSRoots(t *testing.T, roots ...string) {
if err := globalRT.Namespace().SetRoots(roots...); err != nil {
t.Fatalf("%s: SetRoots(%v) failed with %v", loc(2), roots, err)
}
}
func createShellAndMountTable(t *testing.T) (*modules.Shell, func()) {
sh, err := modules.NewShell(nil)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
// The shell, will, by default share credentials with its children.
sh.ClearVar(consts.VeyronCredentials)
mtName, mtHandle := startRootMT(t, sh)
vlog.VI(1).Infof("Started shell mounttable with name %v", mtName)
// Make sure the root mount table is the last process to be shutdown
// since the others will likely want to communicate with it during
// their shutdown process
sh.Forget(mtHandle)
// TODO(caprita): Define a GetNamespaceRootsCommand in modules/core and
// use that?
oldNamespaceRoots := globalRT.Namespace().Roots()
fn := func() {
vlog.VI(1).Info("------------ CLEANUP ------------")
vlog.VI(1).Info("---------------------------------")
vlog.VI(1).Info("--(cleaning up shell)------------")
if err := sh.Cleanup(os.Stdout, os.Stderr); err != nil {
t.Fatalf("sh.Cleanup failed with %v", err)
}
vlog.VI(1).Info("--(done cleaning up shell)-------")
vlog.VI(1).Info("--(shutting down root mt)--------")
if err := mtHandle.Shutdown(os.Stdout, os.Stderr); err != nil {
t.Fatalf("mtHandle.Shutdown failed with %v", err)
}
vlog.VI(1).Info("--(done shutting down root mt)---")
vlog.VI(1).Info("--------- DONE CLEANUP ----------")
setNSRoots(t, oldNamespaceRoots...)
}
setNSRoots(t, mtName)
sh.SetVar(consts.NamespaceRootPrefix, mtName)
return sh, fn
}
func runShellCommand(t *testing.T, sh *modules.Shell, env []string, cmd string, args ...string) (modules.Handle, *expect.Session) {
h, err := sh.Start(cmd, env, args...)
if err != nil {
t.Fatalf("%s: failed to start %q: %s", loc(1), cmd, err)
return nil, nil
}
s := expect.NewSession(t, h.Stdout(), expectTimeout)
s.SetVerbosity(testing.Verbose())
return h, s
}
func envelopeFromShell(sh *modules.Shell, env []string, cmd, title string, args ...string) application.Envelope {
args, nenv := sh.CommandEnvelope(cmd, env, args...)
return application.Envelope{
Title: title,
Args: args[1:],
// TODO(caprita): revisit how the environment is sanitized for arbirary
// apps.
Env: impl.VeyronEnvironment(nenv),
Binary: mockBinaryRepoName,
}
}
// setupRootDir sets up and returns the local filesystem location that the
// device manager is told to use, as well as a cleanup function.
func setupRootDir(t *testing.T) (string, func()) {
rootDir, err := ioutil.TempDir("", "devicemanager")
if err != nil {
t.Fatalf("Failed to set up temporary dir for test: %v", err)
}
// On some operating systems (e.g. darwin) os.TempDir() can return a
// symlink. To avoid having to account for this eventuality later,
// evaluate the symlink.
rootDir, err = filepath.EvalSymlinks(rootDir)
if err != nil {
vlog.Fatalf("EvalSymlinks(%v) failed: %v", rootDir, err)
}
return rootDir, func() {
if t.Failed() || os.Getenv(preserveDMWorkspaceEnv) != "" {
t.Logf("You can examine the device manager workspace at %v", rootDir)
} else {
os.RemoveAll(rootDir)
}
}
}
func newServer() (ipc.Server, string) {
server, err := globalRT.NewServer()
if err != nil {
vlog.Fatalf("NewServer() failed: %v", err)
}
spec := ipc.ListenSpec{Addrs: ipc.ListenAddrs{{"tcp", "127.0.0.1:0"}}}
endpoints, err := server.Listen(spec)
if err != nil {
vlog.Fatalf("Listen(%s) failed: %v", spec, err)
}
return server, endpoints[0].String()
}
// resolveExpectNotFound verifies that the given name is not in the mounttable.
func resolveExpectNotFound(t *testing.T, name string) {
if results, err := globalRT.Namespace().Resolve(globalRT.NewContext(), name); err == nil {
t.Fatalf("%s: Resolve(%v) succeeded with results %v when it was expected to fail", loc(1), name, results)
} else if expectErr := naming.ErrNoSuchName.ID; !verror2.Is(err, expectErr) {
t.Fatalf("%s: Resolve(%v) failed with error %v, expected error ID %v", loc(1), name, err, expectErr)
}
}
// resolve looks up the given name in the mounttable.
func resolve(t *testing.T, name string, replicas int) []string {
results, err := globalRT.Namespace().Resolve(globalRT.NewContext(), name)
if err != nil {
t.Fatalf("Resolve(%v) failed: %v", name, err)
}
filteredResults := []string{}
for _, r := range results {
if strings.Index(r, "@tcp") != -1 {
filteredResults = append(filteredResults, r)
}
}
// We are going to get a websocket and a tcp endpoint for each replica.
if want, got := replicas, len(filteredResults); want != got {
t.Fatalf("Resolve(%v) expected %d result(s), got %d instead", name, want, got)
}
return filteredResults
}
// The following set of functions are convenience wrappers around Update and
// Revert for device manager.
func deviceStub(name string) device.DeviceClientMethods {
deviceName := naming.Join(name, "device")
return device.DeviceClient(deviceName)
}
func updateDeviceExpectError(t *testing.T, name string, errID verror.ID) {
if err := deviceStub(name).Update(globalRT.NewContext()); !verror2.Is(err, errID) {
t.Fatalf("%s: Update(%v) expected to fail with %v, got %v instead", loc(1), name, errID, err)
}
}
func updateDevice(t *testing.T, name string) {
if err := deviceStub(name).Update(globalRT.NewContext()); err != nil {
t.Fatalf("%s: Update(%v) failed: %v", loc(1), name, err)
}
}
func revertDeviceExpectError(t *testing.T, name string, errID verror.ID) {
if err := deviceStub(name).Revert(globalRT.NewContext()); !verror2.Is(err, errID) {
t.Fatalf("%s: Revert(%v) expected to fail with %v, got %v instead", loc(1), name, errID, err)
}
}
func revertDevice(t *testing.T, name string) {
if err := deviceStub(name).Revert(globalRT.NewContext()); err != nil {
t.Fatalf("%s: Revert(%v) failed: %v", loc(1), name, err)
}
}
// The following set of functions are convenience wrappers around various app
// management methods.
func ort(opt []veyron2.Runtime) veyron2.Runtime {
if len(opt) > 0 {
return opt[0]
} else {
return globalRT
}
}
func appStub(nameComponents ...string) device.ApplicationClientMethods {
appsName := "dm//apps"
appName := naming.Join(append([]string{appsName}, nameComponents...)...)
return device.ApplicationClient(appName)
}
func installApp(t *testing.T, opt ...veyron2.Runtime) string {
appID, err := appStub().Install(ort(opt).NewContext(), mockApplicationRepoName)
if err != nil {
t.Fatalf("%s: Install failed: %v", loc(1), err)
}
return appID
}
type granter struct {
ipc.CallOpt
p security.Principal
extension string
}
func (g *granter) Grant(other security.Blessings) (security.Blessings, error) {
return g.p.Bless(other.PublicKey(), g.p.BlessingStore().Default(), g.extension, security.UnconstrainedUse())
}
func startAppImpl(t *testing.T, appID, grant string, opt ...veyron2.Runtime) (string, error) {
var opts []ipc.CallOpt
if grant != "" {
opts = append(opts, &granter{p: ort(opt).Principal(), extension: grant})
}
if instanceIDs, err := appStub(appID).Start(ort(opt).NewContext(), opts...); err != nil {
return "", err
} else {
if want, got := 1, len(instanceIDs); want != got {
t.Fatalf("%s: Start(%v): expected %v instance ids, got %v instead", loc(1), appID, want, got)
}
return instanceIDs[0], nil
}
}
func startApp(t *testing.T, appID string, opt ...veyron2.Runtime) string {
instanceID, err := startAppImpl(t, appID, "forapp", opt...)
if err != nil {
t.Fatalf("%s: Start(%v) failed: %v", loc(1), appID, err)
}
return instanceID
}
func startAppExpectError(t *testing.T, appID string, expectedError verror.ID, opt ...veyron2.Runtime) {
if _, err := startAppImpl(t, appID, "forapp", opt...); err == nil || !verror2.Is(err, expectedError) {
t.Fatalf("%s: Start(%v) expected to fail with %v, got %v instead", loc(1), appID, expectedError, err)
}
}
func stopApp(t *testing.T, appID, instanceID string, opt ...veyron2.Runtime) {
if err := appStub(appID, instanceID).Stop(ort(opt).NewContext(), 5); err != nil {
t.Fatalf("%s: Stop(%v/%v) failed: %v", loc(1), appID, instanceID, err)
}
}
func suspendApp(t *testing.T, appID, instanceID string, opt ...veyron2.Runtime) {
if err := appStub(appID, instanceID).Suspend(ort(opt).NewContext()); err != nil {
t.Fatalf("%s: Suspend(%v/%v) failed: %v", loc(1), appID, instanceID, err)
}
}
func resumeApp(t *testing.T, appID, instanceID string, opt ...veyron2.Runtime) {
if err := appStub(appID, instanceID).Resume(ort(opt).NewContext()); err != nil {
t.Fatalf("%s: Resume(%v/%v) failed: %v", loc(1), appID, instanceID, err)
}
}
func resumeAppExpectError(t *testing.T, appID, instanceID string, expectedError verror.ID, opt ...veyron2.Runtime) {
if err := appStub(appID, instanceID).Resume(ort(opt).NewContext()); err == nil || !verror2.Is(err, expectedError) {
t.Fatalf("%s: Resume(%v/%v) expected to fail with %v, got %v instead", loc(1), appID, instanceID, expectedError, err)
}
}
func updateApp(t *testing.T, appID string, opt ...veyron2.Runtime) {
if err := appStub(appID).Update(ort(opt).NewContext()); err != nil {
t.Fatalf("%s: Update(%v) failed: %v", loc(1), appID, err)
}
}
func updateAppExpectError(t *testing.T, appID string, expectedError verror.ID) {
if err := appStub(appID).Update(globalRT.NewContext()); err == nil || !verror2.Is(err, expectedError) {
t.Fatalf("%s: Update(%v) expected to fail with %v, got %v instead", loc(1), appID, expectedError, err)
}
}
func revertApp(t *testing.T, appID string) {
if err := appStub(appID).Revert(globalRT.NewContext()); err != nil {
t.Fatalf("%s: Revert(%v) failed: %v", loc(1), appID, err)
}
}
func revertAppExpectError(t *testing.T, appID string, expectedError verror.ID) {
if err := appStub(appID).Revert(globalRT.NewContext()); err == nil || !verror2.Is(err, expectedError) {
t.Fatalf("%s: Revert(%v) expected to fail with %v, got %v instead", loc(1), appID, expectedError, err)
}
}
func uninstallApp(t *testing.T, appID string) {
if err := appStub(appID).Uninstall(globalRT.NewContext()); err != nil {
t.Fatalf("%s: Uninstall(%v) failed: %v", loc(1), appID, err)
}
}
// Code to make Association lists sortable.
type byIdentity []device.Association
func (a byIdentity) Len() int { return len(a) }
func (a byIdentity) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byIdentity) Less(i, j int) bool { return a[i].IdentityName < a[j].IdentityName }
func compareAssociations(t *testing.T, got, expected []device.Association) {
sort.Sort(byIdentity(got))
sort.Sort(byIdentity(expected))
if !reflect.DeepEqual(got, expected) {
t.Fatalf("ListAssociations() got %v, expected %v", got, expected)
}
}
// generateSuidHelperScript builds a script to execute the test target as
// a suidhelper instance and returns the path to the script.
func generateSuidHelperScript(t *testing.T, root string) string {
output := "#!/bin/bash\n"
output += "VEYRON_SUIDHELPER_TEST=1"
output += " "
output += "exec " + os.Args[0] + " -minuid=1 -test.run=TestSuidHelper $*"
output += "\n"
vlog.VI(1).Infof("script\n%s", output)
if err := os.MkdirAll(root, 0755); err != nil {
t.Fatalf("MkdirAll failed: %v", err)
}
// Helper does not need to live under the device manager's root dir, but
// we put it there for convenience.
path := filepath.Join(root, "helper.sh")
if err := ioutil.WriteFile(path, []byte(output), 0755); err != nil {
t.Fatalf("WriteFile(%v) failed: %v", path, err)
}
return path
}