Merge "vdl/javascript: Change the generator to use the new package layout."
diff --git a/cmd/debug/debug_v23_test.go b/cmd/debug/debug_v23_test.go
index a56051c..600eb58 100644
--- a/cmd/debug/debug_v23_test.go
+++ b/cmd/debug/debug_v23_test.go
@@ -18,7 +18,7 @@
func V23TestDebugGlob(i *v23tests.T) {
v23tests.RunRootMT(i, "--veyron.tcp.address=127.0.0.1:0")
- binary := i.BuildGoPkg("v.io/x/ref/cmd/debug")
+ binary := i.BuildV23Pkg("v.io/x/ref/cmd/debug")
inv := binary.Start("glob", "__debug/*")
var want string
@@ -35,7 +35,7 @@
// Create a temp file before we list the logs.
fileName := filepath.Base(i.NewTempFile().Name())
- binary := i.BuildGoPkg("v.io/x/ref/cmd/debug")
+ binary := i.BuildV23Pkg("v.io/x/ref/cmd/debug")
output := binary.Start("glob", "__debug/logs/*").Output()
// The output should contain the filename.
@@ -49,7 +49,7 @@
v23tests.RunRootMT(i, "--veyron.tcp.address=127.0.0.1:0")
path := "__debug/stats/system/hostname"
- binary := i.BuildGoPkg("v.io/x/ref/cmd/debug")
+ binary := i.BuildV23Pkg("v.io/x/ref/cmd/debug")
got := binary.Start("stats", "read", path).Output()
hostname, err := os.Hostname()
if err != nil {
@@ -72,7 +72,7 @@
func V23TestLogSize(i *v23tests.T) {
v23tests.RunRootMT(i, "--veyron.tcp.address=127.0.0.1:0")
- binary := i.BuildGoPkg("v.io/x/ref/cmd/debug")
+ binary := i.BuildV23Pkg("v.io/x/ref/cmd/debug")
testLogData := "This is a test log file"
file := createTestLogFile(i, testLogData)
@@ -91,7 +91,7 @@
func V23TestStatsRead(i *v23tests.T) {
v23tests.RunRootMT(i, "--veyron.tcp.address=127.0.0.1:0")
- binary := i.BuildGoPkg("v.io/x/ref/cmd/debug")
+ binary := i.BuildV23Pkg("v.io/x/ref/cmd/debug")
testLogData := "This is a test log file\n"
file := createTestLogFile(i, testLogData)
logName := filepath.Base(file.Name())
@@ -111,7 +111,7 @@
func V23TestStatsWatch(i *v23tests.T) {
v23tests.RunRootMT(i, "--veyron.tcp.address=127.0.0.1:0")
- binary := i.BuildGoPkg("v.io/x/ref/cmd/debug")
+ binary := i.BuildV23Pkg("v.io/x/ref/cmd/debug")
testLogData := "This is a test log file\n"
file := createTestLogFile(i, testLogData)
logName := filepath.Base(file.Name())
@@ -150,7 +150,7 @@
func V23TestVTrace(i *v23tests.T) {
v23tests.RunRootMT(i, "--veyron.tcp.address=127.0.0.1:0")
- binary := i.BuildGoPkg("v.io/x/ref/cmd/debug")
+ binary := i.BuildV23Pkg("v.io/x/ref/cmd/debug")
logContent := "Hello, world!\n"
logPath := "__debug/logs/" + filepath.Base(createTestLogFile(i, logContent).Name())
// Create a log file with tracing, read it and check that the resulting trace exists.
@@ -203,7 +203,7 @@
func V23TestPprof(i *v23tests.T) {
v23tests.RunRootMT(i, "--veyron.tcp.address=127.0.0.1:0")
- binary := i.BuildGoPkg("v.io/x/ref/cmd/debug")
+ binary := i.BuildV23Pkg("v.io/x/ref/cmd/debug")
inv := binary.Start("pprof", "run", "__debug/pprof", "heap", "--text")
// Assert that a profile indicating the heap size was written out.
diff --git a/cmd/mgmt/device/doc.go b/cmd/mgmt/device/doc.go
index 653d474..45f0d8b 100644
--- a/cmd/mgmt/device/doc.go
+++ b/cmd/mgmt/device/doc.go
@@ -351,9 +351,9 @@
Name of application service.
-binserv=binaryd
Name of binary service.
- -goarch=amd64
+ -goarch=$GOARCH
GOARCH for application.
- -goos=linux
+ -goos=$GOOS
GOOS for application.
-readers=dev.v.io
If non-empty, comma-separated blessing patterns to add to Read and Resolve
diff --git a/cmd/mgmt/device/impl/local_install.go b/cmd/mgmt/device/impl/local_install.go
index 1781dc5..78e06f2 100644
--- a/cmd/mgmt/device/impl/local_install.go
+++ b/cmd/mgmt/device/impl/local_install.go
@@ -82,10 +82,6 @@
return nil, nil, err
}
spec := v23.GetListenSpec(ctx)
- endpoints, err := server.Listen(spec)
- if err != nil {
- return nil, nil, err
- }
var name string
if spec.Proxy != "" {
id, err := uniqueid.Random()
@@ -93,6 +89,14 @@
return nil, nil, err
}
name = id.String()
+ // Disable listening on local addresses to avoid publishing
+ // local endpoints to the mount table. The only thing published
+ // should be the proxied endpoint.
+ spec.Addrs = nil
+ }
+ endpoints, err := server.Listen(spec)
+ if err != nil {
+ return nil, nil, err
}
dispatcher := make(mapDispatcher)
if err := server.ServeDispatcher(name, dispatcher); err != nil {
@@ -137,6 +141,11 @@
func (i binaryInvoker) Download(call repository.BinaryDownloadServerCall, _ int32) error {
fileName := string(i)
+ fStat, err := os.Stat(fileName)
+ if err != nil {
+ return err
+ }
+ vlog.VI(1).Infof("Download commenced for %v (%v bytes)", fileName, fStat.Size())
file, err := os.Open(fileName)
if err != nil {
return err
@@ -145,15 +154,22 @@
bufferLength := 4096
buffer := make([]byte, bufferLength)
sender := call.SendStream()
+ var sentTotal int64
+ const logChunk = 1 << 20
for {
n, err := file.Read(buffer)
switch err {
case io.EOF:
+ vlog.VI(1).Infof("Download complete for %v (%v bytes)", fileName, fStat.Size())
return nil
case nil:
if err := sender.Send(buffer[:n]); err != nil {
return err
}
+ if sentTotal/logChunk < (sentTotal+int64(n))/logChunk {
+ vlog.VI(1).Infof("Download progress for %v: %v/%v", fileName, sentTotal+int64(n), fStat.Size())
+ }
+ sentTotal += int64(n)
default:
return err
}
@@ -277,6 +293,7 @@
if err != nil {
return err
}
+ vlog.VI(1).Infof("binary %v serving as %v", binary, envelope.Binary.File)
// For each package dir/file specified in the arguments list, set up an
// object in the binary service to serve that package, and add the
diff --git a/cmd/mgmt/device/impl/publish.go b/cmd/mgmt/device/impl/publish.go
index 35621e4..21b3bf0 100644
--- a/cmd/mgmt/device/impl/publish.go
+++ b/cmd/mgmt/device/impl/publish.go
@@ -40,11 +40,32 @@
var binaryService, applicationService, goos, goarch, readBlessings string
+const (
+ defaultArch = "$GOARCH"
+ defaultOS = "$GOOS"
+)
+
+var (
+ flagArch string
+ flagOS string
+)
+
+// SubstituteVarsInFlags substitutes environment variables in default
+// values of relevant flags.
+func SubstituteVarsInFlags() {
+ if flagArch == defaultArch {
+ flagArch = runtime.GOARCH
+ }
+ if flagOS == defaultOS {
+ flagOS = runtime.GOOS
+ }
+}
+
func init() {
cmdPublish.Flags.StringVar(&binaryService, "binserv", "binaryd", "Name of binary service.")
cmdPublish.Flags.StringVar(&applicationService, "appserv", "applicationd", "Name of application service.")
- cmdPublish.Flags.StringVar(&goos, "goos", runtime.GOOS, "GOOS for application.")
- cmdPublish.Flags.StringVar(&goarch, "goarch", runtime.GOARCH, "GOARCH for application.")
+ cmdPublish.Flags.StringVar(&goos, "goos", defaultOS, "GOOS for application.")
+ cmdPublish.Flags.StringVar(&goarch, "goarch", defaultArch, "GOARCH for application.")
cmdPublish.Flags.StringVar(&readBlessings, "readers", "dev.v.io", "If non-empty, comma-separated blessing patterns to add to Read and Resolve ACL.")
}
diff --git a/cmd/mgmt/device/main.go b/cmd/mgmt/device/main.go
index c5714e2..93fd359 100644
--- a/cmd/mgmt/device/main.go
+++ b/cmd/mgmt/device/main.go
@@ -15,6 +15,7 @@
func main() {
gctx, shutdown := v23.Init()
impl.SetGlobalContext(gctx)
+ impl.SubstituteVarsInFlags()
exitCode := impl.Root().Main()
shutdown()
os.Exit(exitCode)
diff --git a/cmd/naming/simulator/driver.go b/cmd/naming/simulator/driver.go
index f562216..ec5fbce 100644
--- a/cmd/naming/simulator/driver.go
+++ b/cmd/naming/simulator/driver.go
@@ -117,7 +117,7 @@
// Subprocesses commands are run by fork/execing this binary
// so we must test to see if this instance is a subprocess or the
// the original command line instance.
- if modules.IsModulesProcess() {
+ if modules.IsModulesChildProcess() {
shutdown()
// Subprocess, run the requested command.
if err := modules.Dispatch(); err != nil {
diff --git a/cmd/naming/simulator/v23_test.go b/cmd/naming/simulator/v23_test.go
index c374413..94328a4 100644
--- a/cmd/naming/simulator/v23_test.go
+++ b/cmd/naming/simulator/v23_test.go
@@ -16,7 +16,7 @@
func TestMain(m *testing.M) {
testutil.Init()
- if modules.IsModulesProcess() {
+ if modules.IsModulesChildProcess() {
if err := modules.Dispatch(); err != nil {
fmt.Fprintf(os.Stderr, "modules.Dispatch failed: %v\n", err)
os.Exit(1)
diff --git a/cmd/principal/principal_v23_test.go b/cmd/principal/principal_v23_test.go
index fb7f5ac..41b6433 100644
--- a/cmd/principal/principal_v23_test.go
+++ b/cmd/principal/principal_v23_test.go
@@ -44,7 +44,7 @@
)
bin := t.BuildGoPkg("v.io/x/ref/cmd/principal")
- bin.Start("create", aliceDir, "alice").WaitOrDie(os.Stdout, os.Stderr)
+ bin.Run("create", aliceDir, "alice")
bin = bin.WithEnv("VEYRON_CREDENTIALS=" + aliceDir)
redirect(t, bin.Start("blessself", "alicereborn"), aliceBlessingFile)
diff --git a/cmd/servicerunner/main.go b/cmd/servicerunner/main.go
index 21ac2f2..4d33f26 100644
--- a/cmd/servicerunner/main.go
+++ b/cmd/servicerunner/main.go
@@ -56,7 +56,7 @@
}
func main() {
- if modules.IsModulesProcess() {
+ if modules.IsModulesChildProcess() {
panicOnError(modules.Dispatch())
return
}
diff --git a/examples/rps/rpsbot/v23_internal_test.go b/examples/rps/rpsbot/v23_internal_test.go
index 3a3370a..6ab5fba 100644
--- a/examples/rps/rpsbot/v23_internal_test.go
+++ b/examples/rps/rpsbot/v23_internal_test.go
@@ -15,7 +15,7 @@
func TestMain(m *testing.M) {
testutil.Init()
- if modules.IsModulesProcess() {
+ if modules.IsModulesChildProcess() {
if err := modules.Dispatch(); err != nil {
fmt.Fprintf(os.Stderr, "modules.Dispatch failed: %v\n", err)
os.Exit(1)
diff --git a/examples/tunnel/tunneld/tunneld_v23_test.go b/examples/tunnel/tunneld/tunneld_v23_test.go
index a141ce4..be0361a 100644
--- a/examples/tunnel/tunneld/tunneld_v23_test.go
+++ b/examples/tunnel/tunneld/tunneld_v23_test.go
@@ -15,9 +15,9 @@
func V23TestTunneld(t *v23tests.T) {
v23tests.RunRootMT(t, "--veyron.tcp.address=127.0.0.1:0")
- tunneldBin := t.BuildGoPkg("v.io/x/ref/examples/tunnel/tunneld")
- vsh := t.BuildGoPkg("v.io/x/ref/examples/tunnel/vsh")
- mounttableBin := t.BuildGoPkg("v.io/x/ref/cmd/mounttable")
+ tunneldBin := t.BuildV23Pkg("v.io/x/ref/examples/tunnel/tunneld")
+ vsh := t.BuildV23Pkg("v.io/x/ref/examples/tunnel/vsh")
+ mounttableBin := t.BuildV23Pkg("v.io/x/ref/cmd/mounttable")
// Start tunneld with a known endpoint.
tunnelEndpoint := tunneldBin.Start("--veyron.tcp.address=127.0.0.1:0").ExpectVar("NAME")
diff --git a/lib/exec/parent.go b/lib/exec/parent.go
index ddb3d5b..326d45b 100644
--- a/lib/exec/parent.go
+++ b/lib/exec/parent.go
@@ -179,7 +179,6 @@
return err
}
return nil
-
}
// copy is like io.Copy, but it also treats the receipt of the special eofChar
diff --git a/lib/modules/core/core_test.go b/lib/modules/core/core_test.go
index 0112757..559d611 100644
--- a/lib/modules/core/core_test.go
+++ b/lib/modules/core/core_test.go
@@ -8,7 +8,6 @@
"sort"
"strconv"
"testing"
- "time"
"v.io/x/lib/vlog"
@@ -16,7 +15,6 @@
"v.io/x/ref/lib/modules"
"v.io/x/ref/lib/modules/core"
"v.io/x/ref/lib/testutil"
- "v.io/x/ref/lib/testutil/expect"
_ "v.io/x/ref/profiles"
)
@@ -24,7 +22,7 @@
// recognize that this requires modules.Dispatch().
func TestMain(m *testing.M) {
testutil.Init()
- if modules.IsModulesProcess() {
+ if modules.IsModulesChildProcess() {
if err := modules.Dispatch(); err != nil {
fmt.Fprintf(os.Stderr, "modules.Dispatch failed: %v\n", err)
os.Exit(1)
@@ -48,14 +46,12 @@
func newShell(t *testing.T) (*modules.Shell, func()) {
ctx, shutdown := testutil.InitForTest()
-
- sh, err := modules.NewShell(ctx, nil)
+ sh, err := modules.NewExpectShell(ctx, nil, t, testing.Verbose())
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
return sh, func() {
if testing.Verbose() {
- vlog.Infof("------ cleanup ------")
sh.Cleanup(os.Stderr, os.Stderr)
} else {
sh.Cleanup(nil, nil)
@@ -76,9 +72,8 @@
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
- s := expect.NewSession(t, root.Stdout(), time.Second)
- s.ExpectVar("PID")
- s.ExpectVar("MT_NAME")
+ root.ExpectVar("PID")
+ root.ExpectVar("MT_NAME")
root.CloseStdin()
}
@@ -89,11 +84,10 @@
t.Fatalf("unexpected error for root mt: %s", err)
}
sh.Forget(root)
- rootSession := expect.NewSession(t, root.Stdout(), time.Minute)
- rootSession.ExpectVar("PID")
- rootName := rootSession.ExpectVar("MT_NAME")
+ root.ExpectVar("PID")
+ rootName := root.ExpectVar("MT_NAME")
if t.Failed() {
- return nil, nil, rootSession.Error()
+ return nil, nil, root.Error()
}
sh.SetVar(consts.NamespaceRootPrefix, rootName)
mountAddrs := make(map[string]string)
@@ -105,13 +99,12 @@
if err != nil {
return nil, nil, fmt.Errorf("unexpected error for mt %q: %s", mp, err)
}
- s := expect.NewSession(t, h.Stdout(), time.Minute)
// Wait until each mount table has at least called Serve to
// mount itself.
- s.ExpectVar("PID")
- mountAddrs[mp] = s.ExpectVar("MT_NAME")
- if s.Failed() {
- return nil, nil, s.Error()
+ h.ExpectVar("PID")
+ mountAddrs[mp] = h.ExpectVar("MT_NAME")
+ if h.Failed() {
+ return nil, nil, h.Error()
}
}
deferFn := func() {
@@ -159,10 +152,8 @@
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
- lseSession := expect.NewSession(t, lse.Stdout(), time.Minute)
- lseSession.SetVerbosity(testing.Verbose())
- if got, want := lseSession.ExpectVar("RN"), strconv.Itoa(len(mountPoints)); got != want {
+ if got, want := lse.ExpectVar("RN"), strconv.Itoa(len(mountPoints)); got != want {
t.Fatalf("got %v, want %v", got, want)
}
@@ -177,7 +168,7 @@
pattern = pattern[:len(pattern)-1]
found := []string{}
for i := 0; i < len(mountPoints); i++ {
- found = append(found, getMatchingMountpoint(lseSession.ExpectRE(pattern, 1)))
+ found = append(found, getMatchingMountpoint(lse.ExpectRE(pattern, 1)))
}
sort.Strings(found)
sort.Strings(mountPoints)
@@ -194,9 +185,8 @@
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
- srvSession := expect.NewSession(t, srv.Stdout(), time.Minute)
- srvSession.ExpectVar("PID")
- name := srvSession.ExpectVar("NAME")
+ srv.ExpectVar("PID")
+ name := srv.ExpectVar("NAME")
if len(name) == 0 {
t.Fatalf("failed to get name")
}
@@ -204,14 +194,13 @@
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
- cltSession := expect.NewSession(t, clt.Stdout(), time.Minute)
- cltSession.Expect("test: a message")
+ clt.Expect("test: a message")
}
func TestExec(t *testing.T) {
sh, cleanup := newShell(t)
defer cleanup()
- h, err := sh.StartExternalCommand(nil, nil, []string{"/bin/sh", "-c", "echo hello world"}...)
+ h, err := sh.StartWithOpts(sh.DefaultStartOpts().NoExecCommand(), nil, "/bin/sh", "-c", "echo hello world")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
@@ -227,7 +216,7 @@
func TestExecWithEnv(t *testing.T) {
sh, cleanup := newShell(t)
defer cleanup()
- h, err := sh.StartExternalCommand(nil, []string{"BLAH=hello world"}, "/bin/sh", "-c", "printenv BLAH")
+ h, err := sh.StartWithOpts(sh.DefaultStartOpts().NoExecCommand(), []string{"BLAH=hello world"}, "/bin/sh", "-c", "printenv BLAH")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
diff --git a/lib/modules/examples_test.go b/lib/modules/examples_test.go
index 5b18040..f9ae282 100644
--- a/lib/modules/examples_test.go
+++ b/lib/modules/examples_test.go
@@ -23,7 +23,7 @@
func ExampleDispatch() {
ctx, shutdown := testutil.InitForTest()
defer shutdown()
- if modules.IsModulesProcess() {
+ if modules.IsModulesChildProcess() {
// Child process. Dispatch will invoke the 'echo' command
if err := modules.Dispatch(); err != nil {
panic(fmt.Sprintf("unexpected error: %s", err))
diff --git a/lib/modules/exec.go b/lib/modules/exec.go
index 08b14b5..ad494fc 100644
--- a/lib/modules/exec.go
+++ b/lib/modules/exec.go
@@ -13,22 +13,23 @@
"v.io/v23/mgmt"
"v.io/x/lib/vlog"
vexec "v.io/x/ref/lib/exec"
+ "v.io/x/ref/lib/testutil/expect"
)
// execHandle implements both the command and Handle interfaces.
type execHandle struct {
+ *expect.Session
mu sync.Mutex
cmd *exec.Cmd
name string
entryPoint string
handle *vexec.ParentHandle
sh *Shell
- childStdin io.Reader
stderr *os.File
stdout io.ReadCloser
stdin io.WriteCloser
procErrCh chan error
- external bool
+ opts *StartOpts
}
func testFlags() []string {
@@ -43,7 +44,6 @@
return fl
}
// must be a go test binary
- fl = append(fl, "-test.run=TestHelperProcess")
val := timeout.Value.(flag.Getter).Get().(time.Duration)
if val.String() != timeout.DefValue {
// use supplied command value for subprocesses
@@ -57,23 +57,12 @@
return fl
}
-// IsTestHelperProcess returns true if it is called in via
-// -run=TestHelperProcess which normally only ever happens for subprocesses
-// run from tests.
-func IsTestHelperProcess() bool {
- runFlag := flag.Lookup("test.run")
- if runFlag == nil {
- return false
- }
- return runFlag.Value.String() == "TestHelperProcess"
-}
-
func newExecHandle(name string) command {
return &execHandle{name: name, entryPoint: shellEntryPoint + "=" + name, procErrCh: make(chan error, 1)}
}
-func newExecHandleForExternalCommand(name string, stdin io.Reader) command {
- return &execHandle{name: name, external: true, childStdin: stdin, procErrCh: make(chan error, 1)}
+func newExecHandleForExternalCommand(name string) command {
+ return &execHandle{name: name, procErrCh: make(chan error, 1)}
}
func (eh *execHandle) Stdout() io.Reader {
@@ -116,15 +105,16 @@
return newargs, append(cleaned, eh.entryPoint)
}
-func (eh *execHandle) start(sh *Shell, agentfd *os.File, env []string, args ...string) (Handle, error) {
+func (eh *execHandle) start(sh *Shell, agentfd *os.File, opts *StartOpts, env []string, args ...string) (Handle, error) {
eh.mu.Lock()
defer eh.mu.Unlock()
eh.sh = sh
+ eh.opts = opts
cmdPath := args[0]
newargs, newenv := args, env
// If an entry point is specified, use the envelope execution environment.
- if eh.entryPoint != "" {
+ if len(eh.entryPoint) > 0 {
cmdPath = os.Args[0]
newargs, newenv = eh.envelope(sh, env, args[1:]...)
}
@@ -149,8 +139,8 @@
// If we have an explicit stdin to pass to the child, use that,
// otherwise create a pipe and return the write side of that pipe
// in the handle.
- if eh.childStdin != nil {
- cmd.Stdin = eh.childStdin
+ if eh.opts.Stdin != nil {
+ cmd.Stdin = eh.opts.Stdin
} else {
stdin, err := cmd.StdinPipe()
if err != nil {
@@ -159,22 +149,29 @@
eh.stdin = stdin
}
config := vexec.NewConfig()
- serialized, err := sh.config.Serialize()
- if err != nil {
- return nil, err
- }
- config.MergeFrom(serialized)
- if agentfd != nil {
- childfd := len(cmd.ExtraFiles) + vexec.FileOffset
- config.Set(mgmt.SecurityAgentFDConfigKey, strconv.Itoa(childfd))
- cmd.ExtraFiles = append(cmd.ExtraFiles, agentfd)
- defer agentfd.Close()
+
+ execOpts := []vexec.ParentHandleOpt{}
+ if !eh.opts.ExecProtocol {
+ execOpts = append(execOpts, vexec.UseExecProtocolOpt(false))
+ } else {
+ serialized, err := sh.config.Serialize()
+ if err != nil {
+ return nil, err
+ }
+ config.MergeFrom(serialized)
+ if agentfd != nil {
+ childfd := len(cmd.ExtraFiles) + vexec.FileOffset
+ config.Set(mgmt.SecurityAgentFDConfigKey, strconv.Itoa(childfd))
+ cmd.ExtraFiles = append(cmd.ExtraFiles, agentfd)
+ defer agentfd.Close()
+ }
+ execOpts = append(execOpts, vexec.ConfigOpt{Config: config})
}
// TODO(cnicolaou): for external commands, vexec should either not be
// used or it should taken an option to not use its protocol, and in
// particular to share secrets with children.
- handle := vexec.NewParentHandle(cmd, vexec.ConfigOpt{Config: config})
+ handle := vexec.NewParentHandle(cmd, execOpts...)
eh.stdout = stdout
eh.stderr = stderr
eh.handle = handle
@@ -183,13 +180,25 @@
vlog.VI(1).Infof("Start: %q args: %v", eh.name, cmd.Args)
vlog.VI(2).Infof("Start: %q env: %v", eh.name, cmd.Env)
if err := handle.Start(); err != nil {
- return nil, err
+ // The child process failed to start, either because of some setup
+ // error (e.g. creating pipes for it to use), or a bad binary etc.
+ // A handle is returned, so that Shutdown etc may be called, hence
+ // the error must be sent over eh.procErrCh to allow Shutdown to
+ // terminate.
+ eh.procErrCh <- err
+ return eh, err
}
- // TODO(cnicolaou): we should really call handle.WaitForReady here,
- // but if we do, we'll no longer be able to use this interface
- // for non Vanadium processes. We should extend the API to distinguish
- // between Vanadium and non-Vanadium processes and then the various
- // clients of this API will need to be changed accordingly.
+ if eh.opts.ExecProtocol {
+ if err := eh.handle.WaitForReady(eh.opts.StartTimeout); err != nil {
+ // The child failed to call SetReady, most likely because of bad
+ // command line arguments or some other early exit in the child
+ // process.
+ // As per Start above, a handle is returned and the error
+ // sent over eh.procErrCh.
+ eh.procErrCh <- err
+ return eh, err
+ }
+ }
vlog.VI(1).Infof("Started: %q, pid %d", eh.name, cmd.Process.Pid)
go func() {
eh.procErrCh <- eh.handle.Wait(0)
@@ -199,7 +208,8 @@
// up (they'll receive EOF).
eh.stdout.Close()
}()
-
+ eh.Session = expect.NewSession(opts.ExpectTesting, stdout, opts.ExpectTimeout)
+ eh.Session.SetVerbosity(eh.sh.sessionVerbosity)
return eh, nil
}
@@ -232,7 +242,7 @@
select {
case procErr = <-eh.procErrCh:
// The child has exited already.
- case <-time.After(eh.sh.waitTimeout):
+ case <-time.After(eh.opts.ShutdownTimeout):
// Time out waiting for child to exit.
procErr = vexec.ErrTimeout
// Force close stdout to unblock any readers of stdout
@@ -247,7 +257,3 @@
return procErr
}
-
-func (eh *execHandle) WaitForReady(timeout time.Duration) error {
- return eh.handle.WaitForReady(timeout)
-}
diff --git a/lib/modules/func.go b/lib/modules/func.go
index 04dd7b3..ff0ebd9 100644
--- a/lib/modules/func.go
+++ b/lib/modules/func.go
@@ -8,12 +8,19 @@
"time"
"v.io/x/lib/vlog"
+ "v.io/x/ref/lib/testutil/expect"
)
type pipe struct {
- r, w *os.File
+ // We use the *os.File when we create pipe's that we need
+ // to close etc, but we use the io.Reader when we have
+ // a stdin stream specified via StartOpts. In that case we
+ // don't have a *os.File that we can close.
+ rf, wf *os.File
+ r io.Reader
}
type functionHandle struct {
+ *expect.Session
mu sync.Mutex
name string
main Main
@@ -21,6 +28,7 @@
stderr *os.File
err error
sh *Shell
+ opts *StartOpts
wg sync.WaitGroup
}
@@ -43,12 +51,14 @@
func (fh *functionHandle) Stdin() io.Writer {
fh.mu.Lock()
defer fh.mu.Unlock()
- return fh.stdin.w
+ return fh.stdin.wf
}
func (fh *functionHandle) CloseStdin() {
fh.mu.Lock()
- fh.stdin.w.Close()
+ if fh.stdin.wf != nil {
+ fh.stdin.wf.Close()
+ }
fh.mu.Unlock()
}
@@ -56,20 +66,32 @@
return args, env
}
-func (fh *functionHandle) start(sh *Shell, agent *os.File, env []string, args ...string) (Handle, error) {
+func (fh *functionHandle) start(sh *Shell, agent *os.File, opts *StartOpts, env []string, args ...string) (Handle, error) {
fh.mu.Lock()
defer fh.mu.Unlock()
+ fh.opts = opts
// In process commands need their own reference to a principal.
if agent != nil {
agent.Close()
}
fh.sh = sh
- for _, p := range []*pipe{&fh.stdin, &fh.stdout} {
+
+ var pipes []*pipe
+ if fh.opts.Stdin != nil {
+ pipes = []*pipe{&fh.stdout}
+ fh.stdin.r = fh.opts.Stdin
+ } else {
+ pipes = []*pipe{&fh.stdin, &fh.stdout}
+ }
+
+ for _, p := range pipes {
var err error
- if p.r, p.w, err = os.Pipe(); err != nil {
+ if p.rf, p.wf, err = os.Pipe(); err != nil {
return nil, err
}
+ p.r, _ = p.rf, p.wf
}
+
stderr, err := newLogfile("stderr", args[0])
if err != nil {
return nil, err
@@ -80,7 +102,7 @@
go func(env []string) {
fh.mu.Lock()
stdin := fh.stdin.r
- stdout := fh.stdout.w
+ stdout := io.Writer(fh.stdout.wf)
stderr := fh.stderr
main := fh.main
fh.mu.Unlock()
@@ -94,12 +116,16 @@
}
fh.mu.Lock()
- fh.stdin.r.Close()
- fh.stdout.w.Close()
+ if fh.stdin.rf != nil {
+ fh.stdin.rf.Close()
+ }
+ fh.stdout.wf.Close()
fh.err = err
fh.mu.Unlock()
fh.wg.Done()
}(env)
+ fh.Session = expect.NewSession(opts.ExpectTesting, fh.stdout.r, opts.ExpectTimeout)
+ fh.Session.SetVerbosity(fh.sh.sessionVerbosity)
return fh, nil
}
@@ -110,7 +136,9 @@
func (fh *functionHandle) Shutdown(stdout_w, stderr_w io.Writer) error {
fh.mu.Lock()
vlog.VI(1).Infof("Shutdown: %q", fh.name)
- fh.stdin.w.Close()
+ if fh.stdin.wf != nil {
+ fh.stdin.wf.Close()
+ }
stdout := fh.stdout.r
stderr := fh.stderr
fh.mu.Unlock()
@@ -139,7 +167,9 @@
}
fh.mu.Lock()
- fh.stdout.r.Close()
+ if fh.stdout.rf != nil {
+ fh.stdout.rf.Close()
+ }
fh.sh.Forget(fh)
fh.mu.Unlock()
return funcErr
diff --git a/lib/modules/modules_test.go b/lib/modules/modules_test.go
index 092765d..9a8c8b0 100644
--- a/lib/modules/modules_test.go
+++ b/lib/modules/modules_test.go
@@ -5,9 +5,11 @@
"bytes"
"fmt"
"io"
+ "math/rand"
"os"
"reflect"
"sort"
+ "strconv"
"strings"
"syscall"
"testing"
@@ -34,17 +36,20 @@
modules.RegisterChild("echos", "[args]*", Echo)
modules.RegisterChild("errortestChild", "", ErrorMain)
modules.RegisterChild("ignores_stdin", "", ignoresStdin)
+ modules.RegisterChild("pipeProc", "", pipeEcho)
+ modules.RegisterChild("lifo", "", lifo)
modules.RegisterFunction("envtestf", "envtest: <variables to print>...", PrintFromEnv)
modules.RegisterFunction("echof", "[args]*", Echo)
modules.RegisterFunction("errortestFunc", "", ErrorMain)
+ modules.RegisterFunction("pipeFunc", "", pipeEcho)
}
// We must call Testmain ourselves because using v23 test generate
// creates an import cycle for this package.
func TestMain(m *testing.M) {
testutil.Init()
- if modules.IsModulesProcess() {
+ if modules.IsModulesChildProcess() {
if err := modules.Dispatch(); err != nil {
fmt.Fprintf(os.Stderr, "modules.Dispatch failed: %v\n", err)
os.Exit(1)
@@ -67,6 +72,23 @@
return nil
}
+func pipeEcho(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+ scanner := bufio.NewScanner(stdin)
+ for scanner.Scan() {
+ fmt.Fprintf(stdout, "%p: %s\n", pipeEcho, scanner.Text())
+ }
+ return nil
+}
+
+func lifo(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+ scanner := bufio.NewScanner(stdin)
+ scanner.Scan()
+ msg := scanner.Text()
+ modules.WaitForEOF(stdin)
+ fmt.Fprintf(stdout, "%p: %s\n", lifo, msg)
+ return nil
+}
+
func PrintBlessing(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
ctx, shutdown := testutil.InitForTest()
defer shutdown()
@@ -169,7 +191,7 @@
}
func getCustomBlessing(t *testing.T, sh *modules.Shell, creds *modules.CustomCredentials) string {
- h, err := sh.StartWithCredentials("printblessing", nil, creds)
+ h, err := sh.StartWithOpts(sh.DefaultStartOpts().WithCustomCredentials(creds), nil, "printblessing")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
@@ -297,13 +319,10 @@
}
func TestChildNoRegistration(t *testing.T) {
- // TODO(jsimsa): Re-enable this test when it is no longer flaky.
- // https://github.com/veyron/release-issues/issues/1205
- t.SkipNow()
-
ctx, shutdown := testutil.InitForTest()
defer shutdown()
+ //fmt.Fprintf(os.Stderr, "B\n")
sh, err := modules.NewShell(ctx, nil)
if err != nil {
t.Fatalf("unexpected error: %s", err)
@@ -314,6 +333,7 @@
testCommand(t, sh, "envtest", key, val)
_, err = sh.Start("non-existent-command", nil, "random", "args")
if err == nil {
+ fmt.Fprintf(os.Stderr, "Failed: %v\n", err)
t.Fatalf("expected error")
}
}
@@ -398,8 +418,9 @@
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
- sh.SetWaitTimeout(time.Second)
- h, err := sh.Start("ignores_stdin", nil)
+ opts := sh.DefaultStartOpts()
+ opts.ShutdownTimeout = time.Second
+ h, err := sh.StartWithOpts(opts, nil, "ignores_stdin")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
@@ -426,8 +447,9 @@
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
- sh.SetWaitTimeout(time.Second)
- h, err := sh.Start("ignores_stdin", nil)
+ opts := sh.DefaultStartOpts()
+ opts.ShutdownTimeout = time.Second
+ h, err := sh.StartWithOpts(opts, nil, "ignores_stdin")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
@@ -596,4 +618,190 @@
}
}
-// TODO(cnicolaou): test for error return from cleanup
+func TestNoExec(t *testing.T) {
+ ctx, shutdown := testutil.InitForTest()
+ defer shutdown()
+
+ sh, err := modules.NewShell(ctx, nil)
+ if err != nil {
+ t.Fatalf("unexpected error: %s", err)
+ }
+ defer sh.Cleanup(nil, nil)
+ h, err := sh.StartWithOpts(sh.DefaultStartOpts().NoExecCommand(), nil, "/bin/echo", "hello", "world")
+ if err != nil {
+ t.Fatalf("unexpected error: %s", err)
+ }
+ scanner := bufio.NewScanner(h.Stdout())
+ scanner.Scan()
+ if got, want := scanner.Text(), "hello world"; got != want {
+ t.Fatalf("got %v, want %v", got, want)
+ }
+}
+
+func TestExternal(t *testing.T) {
+ ctx, shutdown := testutil.InitForTest()
+ defer shutdown()
+
+ sh, err := modules.NewShell(ctx, nil)
+ if err != nil {
+ t.Fatalf("unexpected error: %s", err)
+ }
+ defer sh.Cleanup(nil, nil)
+ cookie := strconv.Itoa(rand.Int())
+ sh.SetConfigKey("cookie", cookie)
+ h, err := sh.StartWithOpts(sh.DefaultStartOpts().ExternalCommand(), nil, os.Args[0], "--test.run=TestExternalTestHelper")
+ if err != nil {
+ t.Fatalf("unexpected error: %s", err)
+ }
+ scanner := bufio.NewScanner(h.Stdout())
+ scanner.Scan()
+ if got, want := scanner.Text(), fmt.Sprintf("cookie: %s", cookie); got != want {
+ h.Shutdown(os.Stderr, os.Stderr)
+ t.Fatalf("got %v, want %v", got, want)
+ }
+}
+
+// TestExternalTestHelper is used by TestExternal above and has not utility
+// as a test in it's own right.
+func TestExternalTestHelper(t *testing.T) {
+ child, err := exec.GetChildHandle()
+ if err != nil {
+ return
+ }
+ child.SetReady()
+ val, err := child.Config.Get("cookie")
+ if err != nil {
+ t.Fatalf("failed to get child handle: %s", err)
+ }
+ fmt.Printf("cookie: %s\n", val)
+}
+
+func TestPipe(t *testing.T) {
+ ctx, shutdown := testutil.InitForTest()
+ defer shutdown()
+
+ sh, err := modules.NewShell(ctx, nil)
+ if err != nil {
+ t.Fatalf("unexpected error: %s", err)
+ }
+ defer sh.Cleanup(nil, nil)
+
+ for _, cmd := range []string{"pipeProc", "pipeFunc"} {
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ opts := sh.DefaultStartOpts()
+ opts.Stdin = r
+ h, err := sh.StartWithOpts(opts, nil, cmd)
+ if err != nil {
+ t.Fatal(err)
+ }
+ cookie := strconv.Itoa(rand.Int())
+ go func(w *os.File, s string) {
+ fmt.Fprintf(w, "hello world\n")
+ fmt.Fprintf(w, "%s\n", s)
+ w.Close()
+ }(w, cookie)
+
+ scanner := bufio.NewScanner(h.Stdout())
+ want := []string{
+ fmt.Sprintf("%p: hello world", pipeEcho),
+ fmt.Sprintf("%p: %s", pipeEcho, cookie),
+ }
+ i := 0
+ for scanner.Scan() {
+ if got, want := scanner.Text(), want[i]; got != want {
+ t.Fatalf("%s: got %v, want %v", cmd, got, want)
+ }
+ i++
+ }
+ if got, want := i, 2; got != want {
+ t.Fatalf("%s: got %v, want %v", cmd, got, want)
+ }
+ if err := h.Shutdown(os.Stderr, os.Stderr); err != nil {
+ t.Fatal(err)
+ }
+ r.Close()
+ }
+}
+
+func TestLIFO(t *testing.T) {
+ ctx, shutdown := testutil.InitForTest()
+ defer shutdown()
+ sh, err := modules.NewShell(ctx, nil)
+ if err != nil {
+ t.Fatalf("unexpected error: %s", err)
+ }
+ defer sh.Cleanup(nil, nil)
+
+ cases := []string{"a", "b", "c"}
+ for _, msg := range cases {
+ h, err := sh.Start("lifo", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ fmt.Fprintf(h.Stdin(), "%s\n", msg)
+ }
+ var buf bytes.Buffer
+ if err := sh.Cleanup(&buf, nil); err != nil {
+ t.Fatal(err)
+ }
+ lines := strings.Split(strings.TrimRight(buf.String(), "\n"), "\n")
+ if got, want := len(lines), len(cases); got != want {
+ t.Fatalf("got %v, want %v", got, want)
+ }
+ sort.Sort(sort.Reverse(sort.StringSlice(cases)))
+ for i, msg := range cases {
+ if got, want := lines[i], fmt.Sprintf("%p: %s", lifo, msg); got != want {
+ t.Fatalf("got %v, want %v", got, want)
+ }
+ }
+}
+
+func TestStartOpts(t *testing.T) {
+ sh, err := modules.NewShell(nil, nil)
+ if err != nil {
+ t.Fatalf("unexpected error: %s", err)
+ }
+ opts := modules.StartOpts{
+ External: true,
+ }
+ sh.SetDefaultStartOpts(opts)
+ def := sh.DefaultStartOpts()
+ if got, want := def.External, opts.External; got != want {
+ t.Fatalf("got %v, want %v", got, want)
+ }
+ def.External = false
+ if got, want := def, (modules.StartOpts{}); !reflect.DeepEqual(got, want) {
+ t.Fatalf("got %v, want %v", got, want)
+ }
+
+ // Verify that the shell retains a copy.
+ opts.External = false
+ opts.ExecProtocol = true
+ def = sh.DefaultStartOpts()
+ if got, want := def.External, true; got != want {
+ t.Fatalf("got %v, want %v", got, want)
+ }
+ if got, want := def.ExecProtocol, false; got != want {
+ t.Fatalf("got %v, want %v", got, want)
+ }
+
+ sh.SetDefaultStartOpts(opts)
+ def = sh.DefaultStartOpts()
+ if got, want := def.ExecProtocol, true; got != want {
+ t.Fatalf("got %v, want %v", got, want)
+ }
+}
+
+func TestEmbeddedSession(t *testing.T) {
+ sh, err := modules.NewExpectShell(nil, nil, t, testing.Verbose())
+ if err != nil {
+ t.Fatalf("unexpected error: %s", err)
+ }
+ def := sh.DefaultStartOpts()
+ if def.ExpectTesting == nil {
+ t.Fatalf("ExpectTesting should be non nil")
+ }
+}
diff --git a/lib/modules/registry.go b/lib/modules/registry.go
index 110c145..85c75c1 100644
--- a/lib/modules/registry.go
+++ b/lib/modules/registry.go
@@ -39,6 +39,13 @@
return r.cmds[name]
}
+func (r *cmdRegistry) getExternalCommand(name string) *commandDesc {
+ h := newExecHandleForExternalCommand(name)
+ return &commandDesc{
+ factory: func() command { return h },
+ }
+}
+
// RegisterChild adds a new command to the registry that will be run
// as a subprocess. It must be called before Dispatch or DispatchInTest is
// called, typically from an init function.
@@ -79,9 +86,9 @@
const shellEntryPoint = "VEYRON_SHELL_HELPER_PROCESS_ENTRY_POINT"
-// IsModulesProcess returns true if this process is run using
+// IsModulesChildProcess returns true if this process was started by
// the modules package.
-func IsModulesProcess() bool {
+func IsModulesChildProcess() bool {
return os.Getenv(shellEntryPoint) != ""
}
@@ -104,7 +111,7 @@
// process that does not specify an entry point in its environment.
//
// func main() {
-// if modules.IsModulesProcess() {
+// if modules.IsModulesChildProcess() {
// if err := modules.Dispatch(); err != nil {
// panic("error")
// }
@@ -113,25 +120,12 @@
// parent code...
//
func Dispatch() error {
- if !IsModulesProcess() {
+ if !IsModulesChildProcess() {
return nil
}
return registry.dispatch()
}
-// TODO(cnicolaou): delete this in a subsequent CL.
-// DispatchInTest will execute the requested subproccess command from within
-// a unit test run as a subprocess.
-func DispatchInTest() {
- if !IsTestHelperProcess() {
- return
- }
- if err := registry.dispatch(); err != nil {
- vlog.Fatalf("Failed: %s", err)
- }
- os.Exit(0)
-}
-
// DispatchAndExit is like Dispatch except that it will call os.Exit(0)
// when executed within a child process and the command succeeds, or panic
// on encountering an error.
@@ -141,12 +135,9 @@
// parent code...
//
func DispatchAndExit() {
- if !IsModulesProcess() {
+ if !IsModulesChildProcess() {
return
}
- if IsTestHelperProcess() {
- panic("use DispatchInTest in unittests")
- }
if err := registry.dispatch(); err != nil {
panic(fmt.Sprintf("unexpected error: %s", err))
}
diff --git a/lib/modules/shell.go b/lib/modules/shell.go
index 55be3b3..9965aae 100644
--- a/lib/modules/shell.go
+++ b/lib/modules/shell.go
@@ -1,43 +1,139 @@
// Package modules provides a mechanism for running commonly used services
// as subprocesses and client functionality for accessing those services.
-// Such services and functions are collectively called 'commands' and are
-// registered with a single, per-process, registry, but executed within a
-// context, defined by the Shell type. The Shell is analagous to the original
-// UNIX shell and maintains a key, value store of variables that is accessible
-// to all of the commands that it hosts. These variables may be referenced by
-// the arguments passed to commands.
+// Such services and functions are collectively called 'commands' and
+// are managed by a 'Registry'. The Shell is analagous to the UNIX shell and
+// maintains a key, value store of environment variables and config settings
+// that are accessible to the commands that it hosts. Simple variable
+// expansion is supported.
//
-// Commands are added to the registry in two ways:
-// - via RegisterChild for a subprocess
-// - via RegisterFunction for an in-process function
+// Four types of 'commands' may be invoked via a Shell.
//
-// In all cases commands are started by invoking the Start method on the
-// Shell with the name of the command to run. An instance of the Handle
-// interface is returned which can be used to interact with the function
-// or subprocess, and in particular to read/write data from/to it using io
-// channels that follow the stdin, stdout, stderr convention.
+// - functions of type Shell.Main as subprocesses via fork/exec
+// - functions of type Shell.Main as functions within the current process
+// - arbitrary non-Vanadium commands available on the underlying
+// operating system such as '/bin/cp', 'bash' etc.
+// - arbtirary Vanadium commands available on the underlying
+// operating system such as precompiled Vanadium services.
//
-// A simple protocol must be followed by all commands, namely, they
-// should wait for their stdin stream to be closed before exiting. The
-// caller can then coordinate with any command by writing to that stdin
-// stream and reading responses from the stdout stream, and it can close
-// stdin when it's ready for the command to exit using the CloseStdin method
-// on the command's handle.
-//
+// The first two types require that the function to be executed is compiled
+// into the binary executing the calls to the Shell. These functions
+// are registered with a single, per-process, registry.
// The signature of the function that implements the command is the
// same for both types of command and is defined by the Main function type.
// In particular stdin, stdout and stderr are provided as parameters, as is
// a map representation of the shell's environment.
//
+// The second two types allow for arbitrary binaries to be executed. The
+// distinction between a Vanadium and non-Vanadium command is that the
+// Vanadium command implements the protocol used by v.io/x/ref/lib/exec
+// package to synchronise between the parent and child processes and to share
+// information such as the ConfigKey key,value store supported by the Shell,
+// a shared secret etc.
+//
+// The registry provides the following functions:
+// - RegisterChild: generally called from an init function to register a
+// shell.Main to be executed in a subprocess by fork/exec'ing the calling
+// process.
+// - Dispatch: which must be called in the child process to lookup the
+// requested function in the registry and to invoke it. This will typically
+// be called from a TestMain. modules.IsModulesChildProcess can be used
+// to determine if the calling process is a child started via this package.
+// - DispatchAndExit: essentially the same as Dispatch but intended to be
+// called as the first thing in a main function.
+// - RegisterFunction: for an in-process function that will be invoked
+// via function call.
+//
+// The v23 tool can automate generation of TestMain and calls to RegisterChild,
+// and RegisterFunction. Adding the comment below to a test file will
+// generate the appropriate code.
+//
+// //go:generate v23 test generate .
+//
+// Use 'v23 test generate --help' to get a complete explanation.
+//
+// In all cases commands are started by invoking the StartWithOpts method
+// on the Shell with the name of the command to run. An instance of the Handle
+// interface is returned which can be used to interact with the function
+// or subprocess, and in particular to read/write data from/to it using io
+// channels that follow the stdin, stdout, stderr convention. The StartOpts
+// struct is used to control the detailed behaviour of each such invocation.
+// Various helper functions are provided both for creating appropriate
+// instances of StartOpts and for common uses of StartWithOpts.
+//
+// Each successful call to StartWithOpts returns a handle representing
+// the running command. This handle can be used to gain access to that
+// command's stdin, stdout, stderr and to request or synchronize with
+// its termination via the Shutdown method. The Shutdown method can
+// optionally be used to read any remaining output from the commands stdout
+// and stderr.
+// The Shell maintains a record of all such handles and will call Shutdown
+// on them in LIFO order when the Shell's Cleanup method is called.
+//
+// A simple protocol must be followed by all modules.Main commands,
+// in particular, they should wait for their stdin stream to be closed
+// before exiting. The caller can then coordinate with any command by writing
+// to that stdin stream and reading responses from the stdout stream, and it
+// can close stdin when it's ready for the command to exit using the
+// CloseStdin method on the command's handle. Any binary or script that
+// follows this protocol can be used as well.
+//
// By default, every Shell created by NewShell starts a security agent
-// to manage principals for child processes. These default
-// credentials can be overridden by passing a nil context to NewShell
-// then specifying VeyronCredentials in the environment provided as a
-// parameter to the Start method.
+// to manage principals for child processes. These default credentials
+// can be overridden by passing a nil context to NewShell then specifying
+// VeyronCredentials in the environment provided as a parameter to the
+// StartWithOpts method. It is also possible to specify custom credentials
+// via StartOpts.
+//
+// Interacting with Commands:
+//
+// Handle.Stdout(), Stdin(), Stderr():
+// StartWithOpts returns a Handle which can be used to interact with the
+// running command. In particular, its Stdin() and Stdout() methods give
+// access to the running process' corresponding stdin and stdout and hence
+// can be used to communicate with it. Stderr is handled differently and is
+// configured so that the child's stderr is written to a log file rather
+// than a pipe. This is in order to maximise the liklihood of capturing
+// stderr output from a crashed child process.
+//
+// Handle.Shutdown(stdout, stderr io.Writer):
+// The Shutdown method is used to gracefully shutdown a command and to
+// synchronise with its termination. In particular, Shutdown can be used
+// to read any unread output from the command's stdout and stderr. Note that
+// since Stderr is buffered to a file, Shutdown is able to return the entire
+// contents of that file. This is useful for debugging misbehaving/crashing
+// child processes.
+//
+// Shell.Cleanup(stdout, stderr io.Writer):
+// The Shell keeps track of all Handles that it has issued and in particular
+// if Shutdown (or Forget) have not been called, it will call Shutdown for
+// each such Handle in LIFO order. This ensures that all commands will be
+// Shutdown even if the developer does not explicitly take care to do so
+// for every invocation.
+//
+// Pipes:
+// StartWithOpts allows the caller to pass an io.Reader to the command
+// (StartOpts.Stdin) for it to read from, rather than creating a new pipe
+// internally. This makes it possible to connect the output of one
+// command to the input of another directly.
+//
+// Command Line Arguments:
+// The arguments passed in calls to Start are appended to any system required
+// ones (e.g. for propagating test timeouts, verbosity etc) and the child
+// process will call the command with the result of flag.Args(). In this way
+// the caller can provide flags used by libraries in the child process
+// as well as those specific to the command and the command will only
+// receive the args specific to it. The usual "--" convention can be
+// used to override this default behaviour.
+//
+// Caveats:
+//
+// Handle.Shutdown assumes that the child command/process will terminate
+// when its stdin stream is closed. This assumption is unlikely to be valid
+// for 'external' commands (e.g. /bin/cp) and in these cases Kill or some other
+// application specific mechanism will need to be used.
package modules
import (
- "errors"
"fmt"
"io"
"io/ioutil"
@@ -46,12 +142,12 @@
"syscall"
"time"
- "v.io/v23/security"
-
"v.io/v23"
"v.io/v23/context"
+ "v.io/v23/security"
"v.io/x/ref/lib/exec"
"v.io/x/ref/lib/flags/consts"
+ "v.io/x/ref/lib/testutil/expect"
"v.io/x/ref/security/agent"
"v.io/x/ref/security/agent/keymgr"
)
@@ -59,22 +155,35 @@
const (
shellBlessingExtension = "test-shell"
childBlessingExtension = "child"
+
+ defaultStartTimeout = time.Minute
+ defaultShutdownTimeout = time.Minute
+ defaultExpectTimeout = time.Minute
)
+var defaultStartOpts = StartOpts{
+ StartTimeout: defaultStartTimeout,
+ ShutdownTimeout: defaultShutdownTimeout,
+ ExpectTimeout: defaultExpectTimeout,
+ ExecProtocol: true,
+}
+
// Shell represents the context within which commands are run.
type Shell struct {
- mu sync.Mutex
- env map[string]string
- handles map[Handle]struct{}
+ mu sync.Mutex
+ env map[string]string
+ handles map[Handle]struct{}
+ lifoHandles []Handle
+ defaultStartOpts StartOpts
// tmpCredDir is the temporary directory created by this
// shell. This must be removed when the shell is cleaned up.
- tempCredDir string
- startTimeout, waitTimeout time.Duration
- config exec.Config
- principal security.Principal
- agent *keymgr.Agent
- ctx *context.T
- cancelCtx func()
+ tempCredDir string
+ config exec.Config
+ principal security.Principal
+ agent *keymgr.Agent
+ ctx *context.T
+ sessionVerbosity bool
+ cancelCtx func()
}
// NewShell creates a new instance of Shell.
@@ -86,11 +195,10 @@
// principal blessed by the default blessings of ctx's principal.
func NewShell(ctx *context.T, p security.Principal) (*Shell, error) {
sh := &Shell{
- env: make(map[string]string),
- handles: make(map[Handle]struct{}),
- startTimeout: time.Minute,
- waitTimeout: 10 * time.Second,
- config: exec.NewConfig(),
+ env: make(map[string]string),
+ handles: make(map[Handle]struct{}),
+ config: exec.NewConfig(),
+ defaultStartOpts: defaultStartOpts,
}
if ctx == nil {
return sh, nil
@@ -115,6 +223,28 @@
return sh, nil
}
+// NewExpectShell creates a new instance of Shell as per NewShell, but with
+// the default StartOpts set to include the specified expect.Testing parameter.
+func NewExpectShell(ctx *context.T, p security.Principal, t expect.Testing, verbosity bool) (*Shell, error) {
+ sh, err := NewShell(ctx, p)
+ if err != nil {
+ return nil, err
+ }
+ sh.sessionVerbosity = verbosity
+ sh.SetDefaultStartOpts(DefaultStartOpts().WithSessions(t, time.Minute))
+ return sh, nil
+}
+
+// DefaultStartOpts returns the current StartOpts stored with the Shell.
+func (sh *Shell) DefaultStartOpts() StartOpts {
+ return sh.defaultStartOpts
+}
+
+// SetDefaultStartOpts sets the default StartOpts stored with the Shell.
+func (sh *Shell) SetDefaultStartOpts(opts StartOpts) {
+ sh.defaultStartOpts = opts
+}
+
// CustomCredentials encapsulates a Principal which can be shared with
// one or more processes run by a Shell.
type CustomCredentials struct {
@@ -147,7 +277,7 @@
return fd, nil
}
-// NewCustomCredentials creates a new Principal for StartWithCredentials.
+// NewCustomCredentials creates a new Principal for StartWithOpts.
// Returns nil if the shell is not managing principals.
func (sh *Shell) NewCustomCredentials() (cred *CustomCredentials, err error) {
// Create child principal.
@@ -216,8 +346,110 @@
return registry.help(command)
}
-// Start starts the specified command, it returns a Handle which can be
-// used for interacting with that command.
+// Start is shorthand for StartWithOpts(sh.DefaultStartOpts(), ...)
+func (sh *Shell) Start(name string, env []string, args ...string) (Handle, error) {
+ return sh.StartWithOpts(sh.DefaultStartOpts(), env, name, args...)
+}
+
+// StartOpts represents the options that can be passed to the
+// StartWithOpts method.
+type StartOpts struct {
+ // Error is set when creating/intializing instances of StartOpts
+ // via one of the factory methods and returned when StartWithOpts
+ // is called. This allows usage of of the form:
+ //
+ // err := sh.StartWithOpts(sh.DefaultStartOpts()...)
+ //
+ // as opposed to:
+ //
+ // opts, err := sh.DefaultStartOpts(....)
+ // if err != nil {
+ // panic(...)
+ // }
+ // sh.StartWithOpts(opts, ....)
+ Error error
+
+ // Stdin, if non-nil, will be used as the stdin for the child process.
+ // If this option is set, then the Stdin() method on the returned Handle
+ // will return nil. The client of this API maintains ownership of stdin
+ // and must close it, i.e. the shell will not do so.
+ Stdin io.Reader
+ // Credentials, if non-nil, will be used as the credentials for the
+ // child process. If the creds are nil or the shell is not managing
+ // principals, the credentials are ignored.
+ Credentials *CustomCredentials
+ // ExecProtocol indicates whether the child process is expected to
+ // implement the v.io/x.ref/lib/exec parent/child protocol.
+ // It should be set to false when running non-vanadium commands
+ // (e.g. /bin/cp).
+ ExecProtocol bool
+ // External indicates if the command is an external process rather than
+ // a Shell.Main function.
+ External bool
+ // StartTimeout specifies the amount of time to wait for the
+ // child process to signal its correct intialization for Vanadium
+ // processes that implement the exec parent/child protocol. It has no
+ // effect if External is set to true.
+ StartTimeout time.Duration
+ // ShutdownTimeout specifics the amount of time to wait for the child
+ // process to exit when the Shutdown method is called on that
+ // child's handle.
+ ShutdownTimeout time.Duration
+ // ExpectTesting is used when creating an instance of expect.Session
+ // to embed in Handle.
+ ExpectTesting expect.Testing
+ // ExpectTimeout is the timeout to use with expect.Session.
+ ExpectTimeout time.Duration
+}
+
+// DefaultStartOpts returns an instance of Startops with the current default
+// values. The defaults have values for timeouts, no credentials
+// (StartWithOpts will then create credentials each time it is called),
+// and with ExecProtocol set to true.
+// This is expected to be the common use case.
+func DefaultStartOpts() StartOpts {
+ return defaultStartOpts
+}
+
+// WithCustomCredentials returns an instance of StartOpts with the specified
+// credentials. All other options are set to the current defaults.
+func (opts StartOpts) WithCustomCredentials(creds *CustomCredentials) StartOpts {
+ opts.Credentials = creds
+ return opts
+}
+
+// WithSessions returns a copy of opts with the specified expect.Testing and
+// associated timeout.
+func (opts StartOpts) WithSessions(t expect.Testing, timeout time.Duration) StartOpts {
+ opts.ExpectTesting = t
+ opts.ExpectTimeout = timeout
+ return opts
+}
+
+// WithStdin returns a copy of opts with the specified Stdin io.Reader.
+func (opts StartOpts) WithStdin(stdin io.Reader) StartOpts {
+ opts.Stdin = stdin
+ return opts
+}
+
+// NoExecCommand returns a copy of opts with the External option
+// enabled and ExecProtocol disabled.
+func (opts StartOpts) NoExecCommand() StartOpts {
+ opts.External = true
+ opts.ExecProtocol = false
+ return opts
+}
+
+// ExternalCommand returns a copy of StartOpts with the
+// External option enabled.
+func (opts StartOpts) ExternalCommand() StartOpts {
+ opts.External = true
+ return opts
+}
+
+// StartWithOpts starts the specified command according to the supplied
+// StartOpts and returns a Handle which can be used for interacting with
+// that command.
//
// The environment variables for the command are set by merging variables
// from the OS environment, those in this Shell and those provided as a
@@ -226,8 +458,8 @@
// and agent FdEnvVar variables will never use the value from the Shell or OS.
//
// If the shell is managing principals, the command is configured to
-// connect to the shell's agent.
-// To override this, or if the shell is not managing principals, set
+// connect to the shell's agent. Custom credentials may be specified
+// via StartOpts. If the shell is not managing principals, set
// the VeyronCredentials environment variable in the 'env' parameter.
//
// The Shell tracks all of the Handles that it creates so that it can shut
@@ -235,100 +467,62 @@
// error is returned, in which case it may be used to retrieve any output
// from the failed command.
//
-// Commands must have already been registered using RegisterFunction
-// or RegisterChild.
-func (sh *Shell) Start(name string, env []string, args ...string) (Handle, error) {
- creds, err := sh.NewChildCredentials()
- if err != nil {
- return nil, err
- }
- return sh.StartWithCredentials(name, env, creds, args...)
-}
-
-// StartWithCredentials starts a command with the specified credentials, it returns a Handle which can be
-// used for interacting with that command. The specified Principal is used directly without modification.
+// StartWithOpts will return a valid handle for errors that occur during the
+// child processes startup process. It is thus possible to call Shutdown
+// to obtain the error output. Handle will be nil if the error is due to
+// some other reason, such as failure to create pipes/files before starting
+// the child process. A common use will therefore be:
//
-// If the creds is nil or the shell is not managing principals, the credentials are ignored.
-// See 'Start' for the meaning of the other parameters and return values.
-func (sh *Shell) StartWithCredentials(name string, env []string, creds *CustomCredentials, args ...string) (Handle, error) {
- cmd := registry.getCommand(name)
- if cmd == nil {
+// h, err := sh.Start(env, "/bin/echo", "hello")
+// if err != nil {
+// if h != nil {
+// h.Shutdown(nil,os.Stderr)
+// }
+// t.Fatal(err)
+// }
+func (sh *Shell) StartWithOpts(opts StartOpts, env []string, name string, args ...string) (Handle, error) {
+ if opts.Error != nil {
+ return nil, opts.Error
+ }
+ var desc *commandDesc
+ if opts.External {
+ desc = registry.getExternalCommand(name)
+ } else if desc = registry.getCommand(name); desc == nil {
return nil, fmt.Errorf("%s: not registered", name)
}
+ if opts.Credentials == nil {
+ var err error
+ opts.Credentials, err = sh.NewChildCredentials()
+ if err != nil {
+ return nil, err
+ }
+ }
+ cmd := desc.factory()
expanded := append([]string{name}, sh.expand(args...)...)
- c := cmd.factory()
- h, err := sh.startCommand(c, env, creds, expanded...)
- if err != nil {
- // If the error is a timeout, then h can be used to recover
- // any output from the process.
- return h, err
- }
-
- if err := h.WaitForReady(sh.waitTimeout); err != nil {
- return h, err
- }
- return h, nil
-}
-
-// StartExternalCommand starts the specified external, non-Vanadium, command;
-// it returns a Handle which can be used for interacting with that command.
-// A non-Vanadium command does not implement the parent-child protocol
-// implemented by the veyron/lib/exec library, thus this method can be used
-// to start any command (e.g. /bin/cp).
-//
-// StartExternalCommand takes an io.Reader which, if non-nil, will be used as
-// the stdin for the child process. If this parameter is supplied, then the
-// Stdin() method on the returned Handle will return nil. The client of this
-// API maintains ownership of stdin and must close it, i.e. the shell
-// will not do so.
-// The env and args parameters are handled in the same way as Start.
-func (sh *Shell) StartExternalCommand(stdin io.Reader, env []string, args ...string) (Handle, error) {
- if len(args) == 0 {
- return nil, errors.New("no arguments specified to StartExternalCommand")
- }
- c := newExecHandleForExternalCommand(args[0], stdin)
- expanded := sh.expand(args...)
- creds, err := sh.NewChildCredentials()
- if err != nil {
- return nil, err
- }
- return sh.startCommand(c, env, creds, expanded...)
-}
-
-func (sh *Shell) startCommand(c command, env []string, creds *CustomCredentials, args ...string) (Handle, error) {
cenv, err := sh.setupCommandEnv(env)
if err != nil {
return nil, err
}
var p *os.File
- if creds != nil {
- p, err = creds.File()
+ if opts.Credentials != nil {
+ p, err = opts.Credentials.File()
if err != nil {
return nil, err
}
}
- h, err := c.start(sh, p, cenv, args...)
+ h, err := cmd.start(sh, p, &opts, cenv, expanded...)
if err != nil {
- return nil, err
+ return h, err
}
sh.mu.Lock()
sh.handles[h] = struct{}{}
+ sh.lifoHandles = append(sh.lifoHandles, h)
sh.mu.Unlock()
return h, nil
}
-// SetStartTimeout sets the timeout for starting subcommands.
-func (sh *Shell) SetStartTimeout(d time.Duration) {
- sh.startTimeout = d
-}
-
-// SetWaitTimeout sets the timeout for waiting on subcommands to complete.
-func (sh *Shell) SetWaitTimeout(d time.Duration) {
- sh.waitTimeout = d
-}
-
// CommandEnvelope returns the command line and environment that would be
// used for running the subprocess or function if it were started with the
// specifed arguments.
@@ -429,18 +623,57 @@
// Shutdown routines are executed is not defined.
func (sh *Shell) Cleanup(stdout, stderr io.Writer) error {
sh.mu.Lock()
- handles := make(map[Handle]struct{})
- for k, v := range sh.handles {
- handles[k] = v
+ verbose := sh.sessionVerbosity
+ sh.mu.Unlock()
+
+ writeMsg := func(format string, args ...interface{}) {
+ if !verbose {
+ return
+ }
+ if stderr != nil {
+ fmt.Fprintf(stderr, format, args...)
+ }
+ }
+
+ if verbose {
+ writeMsg("---- Shell Cleanup ----\n")
+ }
+ sh.mu.Lock()
+ handles := make([]Handle, 0, len(sh.lifoHandles))
+ for _, h := range sh.lifoHandles {
+ if _, present := sh.handles[h]; present {
+ handles = append(handles, h)
+ }
}
sh.handles = make(map[Handle]struct{})
+ sh.lifoHandles = nil
sh.mu.Unlock()
var err error
- for k, _ := range handles {
- cerr := k.Shutdown(stdout, stderr)
+ for i := len(handles); i > 0; i-- {
+ h := handles[i-1]
+ switch v := h.(type) {
+ case *functionHandle:
+ writeMsg("---- Cleanup calling Shutdown on function %q\n", v.name)
+ case *execHandle:
+ writeMsg("---- Cleanup calling Shutdown on command %q\n", v.name)
+ }
+ cerr := h.Shutdown(stdout, stderr)
if cerr != nil {
err = cerr
}
+ fn := func() string {
+ if cerr == nil {
+ return ": done"
+ } else {
+ return ": error: " + err.Error()
+ }
+ }
+ switch v := h.(type) {
+ case *functionHandle:
+ writeMsg("---- Shutdown on function %q%s\n", v.name, fn())
+ case *execHandle:
+ writeMsg("---- Shutdown on command %q%s\n", v.name, fn())
+ }
}
if sh.cancelCtx != nil {
@@ -475,8 +708,27 @@
return r, nil
}
+// ExpectSession is a subset of v.io/x/ref/lib/testutil/expect.Session's methods
+// that are embedded in Handle.
+type ExpectSession interface {
+ Expect(expected string)
+ ExpectEOF() error
+ ExpectRE(pattern string, n int) [][]string
+ ExpectSetEventuallyRE(expected ...string) [][]string
+ ExpectSetRE(expected ...string) [][]string
+ ExpectVar(name string) string
+ Expectf(format string, args ...interface{})
+ ReadAll() (string, error)
+ ReadLine() string
+ SetVerbosity(bool)
+ Failed() bool
+ Error() error
+}
+
// Handle represents a running command.
type Handle interface {
+ ExpectSession
+
// Stdout returns a reader to the running command's stdout stream.
Stdout() io.Reader
@@ -503,16 +755,11 @@
// Pid returns the pid of the process running the command
Pid() int
-
- // WaitForReady waits until the child process signals to us that it is
- // ready. If this does not occur within the given timeout duration, a
- // timeout error is returned.
- WaitForReady(timeout time.Duration) error
}
// command is used to abstract the implementations of inprocess and subprocess
// commands.
type command interface {
envelope(sh *Shell, env []string, args ...string) ([]string, []string)
- start(sh *Shell, agent *os.File, env []string, args ...string) (Handle, error)
+ start(sh *Shell, agent *os.File, opts *StartOpts, env []string, args ...string) (Handle, error)
}
diff --git a/lib/signals/signals_test.go b/lib/signals/signals_test.go
index b04b54f..b74bbc0 100644
--- a/lib/signals/signals_test.go
+++ b/lib/signals/signals_test.go
@@ -44,7 +44,6 @@
func program(stdin io.Reader, stdout io.Writer, signals ...os.Signal) {
ctx, shutdown := testutil.InitForTest()
-
closeStopLoop := make(chan struct{})
go stopLoop(v23.GetAppCycle(ctx).Stop, stdin, closeStopLoop)
wait := ShutdownOnSignals(ctx, signals...)
diff --git a/lib/signals/v23_internal_test.go b/lib/signals/v23_internal_test.go
index 51d914f..3fbd22d 100644
--- a/lib/signals/v23_internal_test.go
+++ b/lib/signals/v23_internal_test.go
@@ -22,7 +22,7 @@
func TestMain(m *testing.M) {
testutil.Init()
- if modules.IsModulesProcess() {
+ if modules.IsModulesChildProcess() {
if err := modules.Dispatch(); err != nil {
fmt.Fprintf(os.Stderr, "modules.Dispatch failed: %v\n", err)
os.Exit(1)
diff --git a/lib/testutil/v23tests/binary.go b/lib/testutil/v23tests/binary.go
new file mode 100644
index 0000000..2997735
--- /dev/null
+++ b/lib/testutil/v23tests/binary.go
@@ -0,0 +1,140 @@
+package v23tests
+
+import (
+ "bytes"
+ "io"
+ "os"
+ "path"
+ "strings"
+
+ "v.io/x/lib/vlog"
+
+ "v.io/x/ref/lib/modules"
+)
+
+// Binary represents an executable program that will be executed during a
+// test. A binary may be invoked multiple times by calling Start, each call
+// will return a new Invocation.
+//
+// Binary instances are typically obtained from a T by calling BuldV23Pkg,
+// BuildGoPkg (for Vanadium and other Go binaries) or BinaryFromPath (to
+// start binaries that are already present on the system).
+type Binary struct {
+ // The environment to which this binary belongs.
+ env *T
+
+ // The path to the binary.
+ path string
+
+ // StartOpts
+ opts modules.StartOpts
+
+ // Environment variables that will be used when creating invocations
+ // via Start.
+ envVars []string
+}
+
+func (b *Binary) cleanup() {
+ binaryDir := path.Dir(b.path)
+ vlog.Infof("cleaning up %s", binaryDir)
+ if err := os.RemoveAll(binaryDir); err != nil {
+ vlog.Infof("WARNING: RemoveAll(%s) failed (%v)", binaryDir, err)
+ }
+}
+
+// StartOpts returns the current the StartOpts
+func (b *Binary) StartOpts() modules.StartOpts {
+ return b.opts
+}
+
+// Path returns the path to the binary.
+func (b *Binary) Path() string {
+ return b.path
+}
+
+// Start starts the given binary with the given arguments.
+func (b *Binary) Start(args ...string) *Invocation {
+ return b.start(1, args...)
+}
+
+func (b *Binary) start(skip int, args ...string) *Invocation {
+ vlog.Infof("%s: starting %s %s", Caller(skip+1), b.Path(), strings.Join(args, " "))
+ opts := b.opts
+ if opts.Credentials == nil {
+ opts.Credentials, opts.Error = b.env.shell.NewChildCredentials()
+ }
+ handle, err := b.env.shell.StartWithOpts(opts, b.envVars, b.Path(), args...)
+ if err != nil {
+ if handle != nil {
+ vlog.Infof("%s: start failed", Caller(skip+1))
+ handle.Shutdown(nil, os.Stderr)
+ }
+ // TODO(cnicolaou): calling Fatalf etc from a goroutine often leads
+ // to deadlock. Need to make sure that we handle this here. Maybe
+ // it's best to just return an error? Or provide a StartWithError
+ // call for use from goroutines.
+ b.env.Fatalf("%s: StartWithOpts(%v, %v) failed: %v", Caller(skip+1), b.Path(), strings.Join(args, ", "), err)
+ }
+ vlog.Infof("started PID %d\n", handle.Pid())
+ inv := &Invocation{
+ env: b.env,
+ path: b.path,
+ args: args,
+ shutdownErr: errNotShutdown,
+ Handle: handle,
+ }
+ b.env.appendInvocation(inv)
+ return inv
+}
+
+func (b *Binary) run(args ...string) string {
+ inv := b.start(2, args...)
+ var stdout, stderr bytes.Buffer
+ err := inv.Wait(&stdout, &stderr)
+ if err != nil {
+ a := strings.Join(args, ", ")
+ b.env.Fatalf("%s: Run(%s): failed: %v: \n%s\n", Caller(2), a, err, stderr.String())
+ }
+ return strings.TrimRight(stdout.String(), "\n")
+}
+
+// Run runs the binary with the specified arguments to completion. On
+// success it returns the contents of stdout, on failure it terminates the
+// test with an error message containing the error and the contents of
+// stderr.
+func (b *Binary) Run(args ...string) string {
+ return b.run(args...)
+}
+
+// WithStdin returns a copy of this binary that, when Start is called,
+// will read its input from the given reader. Once the reader returns
+// EOF, the returned invocation's standard input will be closed (see
+// Invocation.CloseStdin).
+func (b *Binary) WithStdin(r io.Reader) *Binary {
+ opts := b.opts
+ opts.Stdin = r
+ return b.WithStartOpts(opts)
+}
+
+// WithEnv returns a copy of this binary that, when Start is called, will use
+// the given environment variables. Each environment variable should be
+// in "key=value" form. For example:
+//
+// bin.WithEnv("EXAMPLE_ENV=/tmp/something").Start(...)
+func (b *Binary) WithEnv(env ...string) *Binary {
+ newBin := *b
+ newBin.envVars = env
+ return &newBin
+}
+
+// WithStartOpts eturns a copy of this binary that, when Start is called, will
+// use the given StartOpts.
+//
+// bin.WithStartOpts(opts).Start(...)
+// or
+// bin.WithStartOpts().Run(...)
+func (b *Binary) WithStartOpts(opts modules.StartOpts) *Binary {
+ newBin := *b
+ newBin.opts = opts
+ return &newBin
+}
diff --git a/lib/testutil/v23tests/invocation.go b/lib/testutil/v23tests/invocation.go
new file mode 100644
index 0000000..8879aa3
--- /dev/null
+++ b/lib/testutil/v23tests/invocation.go
@@ -0,0 +1,108 @@
+package v23tests
+
+import (
+ "bytes"
+ "container/list"
+ "io"
+ "syscall"
+
+ "v.io/x/lib/vlog"
+
+ "v.io/x/ref/lib/modules"
+)
+
+// Invocation represents a single invocation of a Binary.
+//
+// Any bytes written by the invocation to its standard error may be recovered
+// using the Wait or WaitOrDie functions.
+//
+// For example:
+// bin := env.BinaryFromPath("/bin/bash")
+// inv := bin.Start("-c", "echo hello world 1>&2")
+// var stderr bytes.Buffer
+// inv.WaitOrDie(nil, &stderr)
+// // stderr.Bytes() now contains 'hello world\n'
+type Invocation struct {
+ // The handle to the process that was run when this invocation was started.
+ modules.Handle
+
+ // The environment to which this invocation belongs.
+ env *T
+
+ // The element representing this invocation in the list of
+ // invocations stored in the environment
+ el *list.Element
+
+ // The path of the binary used for this invocation.
+ path string
+
+ // The args the binary was started with
+ args []string
+
+ // True if the process has been shutdown
+ hasShutdown bool
+
+ // The error, if any, as determined when the invocation was
+ // shutdown. It must be set to a default initial value of
+ // errNotShutdown rather than nil to allow us to distinguish between
+ // a successful shutdown or an error.
+ shutdownErr error
+}
+
+// Path returns the path to the binary that was used for this invocation.
+func (i *Invocation) Path() string {
+ return i.path
+}
+
+// Exists returns true if the invocation still exists.
+func (i *Invocation) Exists() bool {
+ return syscall.Kill(i.Handle.Pid(), 0) == nil
+}
+
+// Kill sends the given signal to this invocation. It is up to the test
+// author to decide whether failure to deliver the signal is fatal to
+// the test.
+func (i *Invocation) Kill(sig syscall.Signal) error {
+ pid := i.Handle.Pid()
+ vlog.VI(1).Infof("sending signal %v to PID %d", sig, pid)
+ return syscall.Kill(pid, sig)
+}
+
+// Output reads the invocation's stdout until EOF and then returns what
+// was read as a string.
+func (i *Invocation) Output() string {
+ buf := bytes.Buffer{}
+ _, err := buf.ReadFrom(i.Stdout())
+ if err != nil {
+ i.env.Fatalf("%s: ReadFrom() failed: %v", Caller(1), err)
+ }
+ return buf.String()
+}
+
+// Wait waits for this invocation to finish. If either stdout or stderr
+// is non-nil, any remaining unread output from those sources will be
+// written to the corresponding writer. The returned error represents
+// the exit status of the underlying command.
+func (i *Invocation) Wait(stdout, stderr io.Writer) error {
+ err := i.Handle.Shutdown(stdout, stderr)
+ i.hasShutdown = true
+ i.shutdownErr = err
+ return err
+}
+
+// WaitOrDie waits for this invocation to finish. If either stdout or stderr
+// is non-nil, any remaining unread output from those sources will be
+// written to the corresponding writer. If the underlying command
+// exited with anything but success (exit status 0), this function will
+// cause the current test to fail.
+func (i *Invocation) WaitOrDie(stdout, stderr io.Writer) {
+ if err := i.Wait(stdout, stderr); err != nil {
+ i.env.Fatalf("%s: FATAL: Wait() for pid %d failed: %v", Caller(1), i.Handle.Pid(), err)
+ }
+}
+
+// Environment returns the instance of the test environment that this
+// invocation was from.
+func (i *Invocation) Environment() *T {
+ return i.env
+}
diff --git a/lib/testutil/v23tests/v23tests.go b/lib/testutil/v23tests/v23tests.go
index 292eb60..6fa05b7 100644
--- a/lib/testutil/v23tests/v23tests.go
+++ b/lib/testutil/v23tests/v23tests.go
@@ -71,11 +71,8 @@
package v23tests
import (
- "bytes"
- "container/list"
"errors"
"fmt"
- "io"
"io/ioutil"
"os"
"os/exec"
@@ -93,7 +90,6 @@
"v.io/x/ref/lib/modules"
"v.io/x/ref/lib/testutil"
- "v.io/x/ref/lib/testutil/expect"
tsecurity "v.io/x/ref/lib/testutil/security"
"v.io/x/ref/security/agent"
)
@@ -142,105 +138,8 @@
invocations []*Invocation
}
-// Binary represents an executable program that will be executed during a
-// test. A binary may be invoked multiple times by calling Start, each call
-// will return a new Invocation.
-//
-// Binary instances are typically obtained from a T by calling BuildGoPkg
-// (for Vanadium and other Go binaries) or BinaryFromPath (to start binaries
-// that are already present on the system).
-type Binary struct {
- // The environment to which this binary belongs.
- env *T
-
- // The path to the binary.
- path string
-
- // Environment variables that will be used when creating invocations
- // via Start.
- envVars []string
-
- // The reader who is supplying the bytes we're going to send to our stdin.
- inputReader io.Reader
-}
-
-// Invocation represents a single invocation of a Binary.
-//
-// Any bytes written by the invocation to its standard error may be recovered
-// using the Wait or WaitOrDie functions.
-//
-// For example:
-// bin := env.BinaryFromPath("/bin/bash")
-// inv := bin.Start("-c", "echo hello world 1>&2")
-// var stderr bytes.Buffer
-// inv.WaitOrDie(nil, &stderr)
-// // stderr.Bytes() now contains 'hello world\n'
-type Invocation struct {
- *expect.Session
-
- // The environment to which this invocation belongs.
- env *T
-
- // The handle to the process that was run when this invocation was started.
- handle modules.Handle
-
- // The element representing this invocation in the list of
- // invocations stored in the environment
- el *list.Element
-
- // The path of the binary used for this invocation.
- path string
-
- // The args the binary was started with
- args []string
-
- // True if the process has been shutdown
- hasShutdown bool
-
- // The error, if any, as determined when the invocation was
- // shutdown. It must be set to a default initial value of
- // errNotShutdown rather than nil to allow us to distinguish between
- // a successful shutdown or an error.
- shutdownErr error
-}
-
var errNotShutdown = errors.New("has not been shutdown")
-// Stdin returns this invocations Stdin stream.
-func (i *Invocation) Stdin() io.Writer {
- return i.handle.Stdin()
-}
-
-// CloseStdin closes the write-side of the pipe to the invocation's
-// standard input.
-func (i *Invocation) CloseStdin() {
- i.handle.CloseStdin()
-}
-
-// Stdout returns this invocations Stdout stream.
-func (i *Invocation) Stdout() io.Reader {
- return i.handle.Stdout()
-}
-
-// Path returns the path to the binary that was used for this invocation.
-func (i *Invocation) Path() string {
- return i.path
-}
-
-// Exists returns true if the invocation still exists.
-func (i *Invocation) Exists() bool {
- return syscall.Kill(i.handle.Pid(), 0) == nil
-}
-
-// Sends the given signal to this invocation. It is up to the test
-// author to decide whether failure to deliver the signal is fatal to
-// the test.
-func (i *Invocation) Kill(sig syscall.Signal) error {
- pid := i.handle.Pid()
- vlog.VI(1).Infof("sending signal %v to PID %d", sig, pid)
- return syscall.Kill(pid, sig)
-}
-
// Caller returns a string of the form <filename>:<lineno> for the
// caller specified by skip, where skip is as per runtime.Caller.
func Caller(skip int) string {
@@ -248,108 +147,16 @@
return fmt.Sprintf("%s:%d", filepath.Base(file), line)
}
-// Output reads the invocation's stdout until EOF and then returns what
-// was read as a string.
-func (i *Invocation) Output() string {
- buf := bytes.Buffer{}
- _, err := buf.ReadFrom(i.Stdout())
- if err != nil {
- i.env.Fatalf("%s: ReadFrom() failed: %v", Caller(1), err)
- }
- return buf.String()
-}
-
-// Wait waits for this invocation to finish. If either stdout or stderr
-// is non-nil, any remaining unread output from those sources will be
-// written to the corresponding writer. The returned error represents
-// the exit status of the underlying command.
-func (i *Invocation) Wait(stdout, stderr io.Writer) error {
- err := i.handle.Shutdown(stdout, stderr)
- i.hasShutdown = true
- i.shutdownErr = err
- return err
-}
-
-// Wait waits for this invocation to finish. If either stdout or stderr
-// is non-nil, any remaining unread output from those sources will be
-// written to the corresponding writer. If the underlying command
-// exited with anything but success (exit status 0), this function will
-// cause the current test to fail.
-func (i *Invocation) WaitOrDie(stdout, stderr io.Writer) {
- if err := i.Wait(stdout, stderr); err != nil {
- i.env.Fatalf("%s: FATAL: Wait() for pid %d failed: %v", Caller(1), i.handle.Pid(), err)
- }
-}
-
-// Environment returns the instance of the test environment that this
-// invocation was from.
-func (i *Invocation) Environment() *T {
- return i.env
-}
-
-func (b *Binary) cleanup() {
- binaryDir := path.Dir(b.path)
- vlog.Infof("cleaning up %s", binaryDir)
- if err := os.RemoveAll(binaryDir); err != nil {
- vlog.Infof("WARNING: RemoveAll(%s) failed (%v)", binaryDir, err)
- }
-}
-
-// Path returns the path to the binary.
-func (b *Binary) Path() string {
- return b.path
-}
-
-// Start starts the given binary with the given arguments.
-func (b *Binary) Start(args ...string) *Invocation {
- return b.start(1, args...)
-}
-
-func (b *Binary) start(skip int, args ...string) *Invocation {
- vlog.Infof("%s: starting %s %s", Caller(skip+1), b.Path(), strings.Join(args, " "))
- handle, err := b.env.shell.StartExternalCommand(b.inputReader, b.envVars, append([]string{b.Path()}, args...)...)
- if err != nil {
- // TODO(cnicolaou): calling Fatalf etc from a goroutine often leads
- // to deadlock. Need to make sure that we handle this here. Maybe
- // it's best to just return an error? Or provide a StartWithError
- // call for use from goroutines.
- b.env.Fatalf("%s: StartExternalCommand(%v, %v) failed: %v", Caller(skip+1), b.Path(), strings.Join(args, ", "), err)
- }
- vlog.Infof("started PID %d\n", handle.Pid())
- inv := &Invocation{
- env: b.env,
- handle: handle,
- path: b.path,
- args: args,
- shutdownErr: errNotShutdown,
- Session: expect.NewSession(b.env, handle.Stdout(), 5*time.Minute),
- }
- b.env.appendInvocation(inv)
- return inv
-}
-
-func (b *Binary) run(args ...string) string {
- inv := b.start(2, args...)
- var stdout, stderr bytes.Buffer
- err := inv.Wait(&stdout, &stderr)
- if err != nil {
- a := strings.Join(args, ", ")
- b.env.Fatalf("%s: Run(%s): failed: %v: \n%s\n", Caller(2), a, err, stderr.String())
- }
- return strings.TrimRight(stdout.String(), "\n")
-}
-
-// Run runs the binary with the specified arguments to completion. On
-// success it returns the contents of stdout, on failure it terminates the
-// test with an error message containing the error and the contents of
-// stderr.
-func (b *Binary) Run(args ...string) string {
- return b.run(args...)
-}
-
// Run constructs a Binary for path and invokes Run on it.
-func (e *T) Run(path string, args ...string) string {
- return e.BinaryFromPath(path).run(args...)
+func (t *T) Run(path string, args ...string) string {
+ return t.BinaryFromPath(path).run(args...)
+}
+
+// Run constructs a Binary for path and invokes Run on it using
+// the specified StartOpts
+func (t *T) RunWithOpts(opts modules.StartOpts, path string, args ...string) string {
+ b := t.BinaryFromPath(path)
+ return b.WithStartOpts(opts).run(args...)
}
// WaitFunc is the type of the functions to be used in conjunction
@@ -370,7 +177,7 @@
// WaitFor will always run fn at least once to completion and hence it will
// hang if that first iteration of fn hangs. If this behaviour is not
// appropriate, then WaitForAsync should be used.
-func (e *T) WaitFor(fn WaitFunc, delay, timeout time.Duration) interface{} {
+func (t *T) WaitFor(fn WaitFunc, delay, timeout time.Duration) interface{} {
deadline := time.Now().Add(timeout)
for {
val, err := fn()
@@ -378,10 +185,10 @@
return val
}
if err != nil {
- e.Fatalf("%s: the WaitFunc returned an error: %v", Caller(1), err)
+ t.Fatalf("%s: the WaitFunc returned an error: %v", Caller(1), err)
}
if time.Now().After(deadline) {
- e.Fatalf("%s: timed out after %s", Caller(1), timeout)
+ t.Fatalf("%s: timed out after %s", Caller(1), timeout)
}
time.Sleep(delay)
}
@@ -389,7 +196,7 @@
// WaitForAsync is like WaitFor except that it calls fn in a goroutine
// and can timeout during the execution of fn.
-func (e *T) WaitForAsync(fn WaitFunc, delay, timeout time.Duration) interface{} {
+func (t *T) WaitForAsync(fn WaitFunc, delay, timeout time.Duration) interface{} {
resultCh := make(chan interface{})
errCh := make(chan interface{})
go func() {
@@ -408,11 +215,11 @@
}()
select {
case err := <-errCh:
- e.Fatalf("%s: the WaitFunc returned error: %v", Caller(1), err)
+ t.Fatalf("%s: the WaitFunc returned error: %v", Caller(1), err)
case result := <-resultCh:
return result
case <-time.After(timeout):
- e.Fatalf("%s: timed out after %s", Caller(1), timeout)
+ t.Fatalf("%s: timed out after %s", Caller(1), timeout)
}
return nil
}
@@ -420,30 +227,30 @@
// Pushd pushes the current working directory to the stack of
// directories, returning it as its result, and changes the working
// directory to dir.
-func (e *T) Pushd(dir string) string {
+func (t *T) Pushd(dir string) string {
cwd, err := os.Getwd()
if err != nil {
- e.Fatalf("%s: Getwd failed: %s", Caller(1), err)
+ t.Fatalf("%s: Getwd failed: %s", Caller(1), err)
}
if err := os.Chdir(dir); err != nil {
- e.Fatalf("%s: Chdir failed: %s", Caller(1), err)
+ t.Fatalf("%s: Chdir failed: %s", Caller(1), err)
}
vlog.VI(1).Infof("Pushd: %s -> %s", cwd, dir)
- e.dirStack = append(e.dirStack, cwd)
+ t.dirStack = append(t.dirStack, cwd)
return cwd
}
// Popd pops the most recent entry from the directory stack and changes
// the working directory to that directory. It returns the new working
// directory as its result.
-func (e *T) Popd() string {
- if len(e.dirStack) == 0 {
- e.Fatalf("%s: directory stack empty", Caller(1))
+func (t *T) Popd() string {
+ if len(t.dirStack) == 0 {
+ t.Fatalf("%s: directory stack empty", Caller(1))
}
- dir := e.dirStack[len(e.dirStack)-1]
- e.dirStack = e.dirStack[:len(e.dirStack)-1]
+ dir := t.dirStack[len(t.dirStack)-1]
+ t.dirStack = t.dirStack[:len(t.dirStack)-1]
if err := os.Chdir(dir); err != nil {
- e.Fatalf("%s: Chdir failed: %s", Caller(1), err)
+ t.Fatalf("%s: Chdir failed: %s", Caller(1), err)
}
vlog.VI(1).Infof("Popd: -> %s", dir)
return dir
@@ -451,34 +258,13 @@
// Caller returns a string of the form <filename>:<lineno> for the
// caller specified by skip, where skip is as per runtime.Caller.
-func (e *T) Caller(skip int) string {
+func (t *T) Caller(skip int) string {
return Caller(skip + 1)
}
-// WithStdin returns a copy of this binary that, when Start is called,
-// will read its input from the given reader. Once the reader returns
-// EOF, the returned invocation's standard input will be closed (see
-// Invocation.CloseStdin).
-func (b *Binary) WithStdin(r io.Reader) *Binary {
- newBin := *b
- newBin.inputReader = r
- return &newBin
-}
-
-// Returns a copy of this binary that, when Start is called, will use
-// the given environment variables. Each environment variable should be
-// in "key=value" form. For example:
-//
-// bin.WithEnv("EXAMPLE_ENV=/tmp/something").Start(...)
-func (b *Binary) WithEnv(env ...string) *Binary {
- newBin := *b
- newBin.envVars = env
- return &newBin
-}
-
// Principal returns the security principal of this environment.
-func (e *T) Principal() security.Principal {
- return e.principal
+func (t *T) Principal() security.Principal {
+ return t.principal
}
// Cleanup cleans up the environment, deletes all its artifacts and
@@ -487,23 +273,23 @@
// as to the state of the processes it was asked to invoke up to that
// point and optionally, if the --v23.tests.shell-on-fail flag is set
// then it will run a debug shell before cleaning up its state.
-func (e *T) Cleanup() {
- if e.Failed() {
+func (t *T) Cleanup() {
+ if t.Failed() {
if testutil.IntegrationTestsDebugShellOnError {
- e.DebugShell()
+ t.DebugSystemShell()
}
// Print out a summary of the invocations and their status.
- for i, inv := range e.invocations {
+ for i, inv := range t.invocations {
if inv.hasShutdown && inv.Exists() {
m := fmt.Sprintf("%d: %s has been shutdown but still exists: %v", i, inv.path, inv.shutdownErr)
- e.Log(m)
+ t.Log(m)
vlog.VI(1).Info(m)
vlog.VI(2).Infof("%d: %s %v", i, inv.path, inv.args)
continue
}
if inv.shutdownErr != nil {
m := fmt.Sprintf("%d: %s: shutdown status: %v", i, inv.path, inv.shutdownErr)
- e.Log(m)
+ t.Log(m)
vlog.VI(1).Info(m)
vlog.VI(2).Infof("%d: %s %v", i, inv.path, inv.args)
}
@@ -514,8 +300,8 @@
// Shut down all processes in LIFO order before attempting to delete any
// files/directories to avoid potential 'file system busy' problems
// on non-unix systems.
- for i := len(e.invocations); i > 0; i-- {
- inv := e.invocations[i-1]
+ for i := len(t.invocations); i > 0; i-- {
+ inv := t.invocations[i-1]
if inv.hasShutdown {
vlog.VI(1).Infof("V23Test.Cleanup: %q has been shutdown", inv.Path())
continue
@@ -527,13 +313,13 @@
}
vlog.VI(1).Infof("V23Test.Cleanup: all invocations taken care of.")
- if err := e.shell.Cleanup(os.Stdout, os.Stderr); err != nil {
- e.Fatalf("WARNING: could not clean up shell (%v)", err)
+ if err := t.shell.Cleanup(os.Stdout, os.Stderr); err != nil {
+ t.Fatalf("WARNING: could not clean up shell (%v)", err)
}
vlog.VI(1).Infof("V23Test.Cleanup: cleaning up binaries & files")
- for _, tempFile := range e.tempFiles {
+ for _, tempFile := range t.tempFiles {
vlog.VI(1).Infof("V23Test.Cleanup: cleaning up %s", tempFile.Name())
if err := tempFile.Close(); err != nil {
vlog.Errorf("WARNING: Close(%q) failed: %v", tempFile.Name(), err)
@@ -543,7 +329,7 @@
}
}
- for _, tempDir := range e.tempDirs {
+ for _, tempDir := range t.tempDirs {
vlog.VI(1).Infof("V23Test.Cleanup: cleaning up %s", tempDir)
if err := os.RemoveAll(tempDir); err != nil {
vlog.Errorf("WARNING: RemoveAll(%q) failed: %v", tempDir, err)
@@ -551,23 +337,23 @@
}
// shutdown the runtime
- e.shutdown()
+ t.shutdown()
}
// GetVar returns the variable associated with the specified key
// and an indication of whether it is defined or not.
-func (e *T) GetVar(key string) (string, bool) {
- return e.shell.GetVar(key)
+func (t *T) GetVar(key string) (string, bool) {
+ return t.shell.GetVar(key)
}
// SetVar sets the value to be associated with key.
-func (e *T) SetVar(key, value string) {
- e.shell.SetVar(key, value)
+func (t *T) SetVar(key, value string) {
+ t.shell.SetVar(key, value)
}
// ClearVar removes the speficied variable from the Shell's environment
-func (e *T) ClearVar(key string) {
- e.shell.ClearVar(key)
+func (t *T) ClearVar(key string) {
+ t.shell.ClearVar(key)
}
func writeStringOrDie(t *T, f *os.File, s string) {
@@ -576,18 +362,19 @@
}
}
-// DebugShell drops the user into a debug shell with any environment
-// variables specified in env... (in VAR=VAL format) available to it.
-// If there is no controlling TTY, DebugShell will emit a warning message
-// and take no futher action. The DebugShell also sets some environment
+// DebugSystemShell drops the user into a debug system shell (e.g. bash)
+// with any environment variables specified in env... (in VAR=VAL format)
+// available to it.
+// If there is no controlling TTY, DebugSystemShell will emit a warning message
+// and take no futher action. The DebugSystemShell also sets some environment
// variables that relate to the running test:
// - V23_TMP_DIR<#> contains the name of each temp directory created.
// - V23_BIN_DIR contains the name of the directory containing binaries.
-func (e *T) DebugShell(env ...string) {
+func (t *T) DebugSystemShell(env ...string) {
// Get the current working directory.
cwd, err := os.Getwd()
if err != nil {
- e.Fatalf("Getwd() failed: %v", err)
+ t.Fatalf("Getwd() failed: %v", err)
}
// Transfer stdin, stdout, and stderr to the new process
@@ -600,7 +387,7 @@
}
var agentFile *os.File
- if creds, err := e.shell.NewChildCredentials(); err == nil {
+ if creds, err := t.shell.NewChildCredentials(); err == nil {
if agentFile, err = creds.File(); err != nil {
vlog.Errorf("WARNING: failed to obtain credentials for the debug shell: %v", err)
}
@@ -618,32 +405,32 @@
attr.Env = append(attr.Env, fmt.Sprintf("%s=%d", agent.FdVarName, len(attr.Files)-1))
// Set up environment for Child.
- for _, v := range e.shell.Env() {
+ for _, v := range t.shell.Env() {
attr.Env = append(attr.Env, v)
}
- for i, td := range e.tempDirs {
+ for i, td := range t.tempDirs {
attr.Env = append(attr.Env, fmt.Sprintf("V23_TMP_DIR%d=%s", i, td))
}
- if len(e.cachedBinDir) > 0 {
- attr.Env = append(attr.Env, "V23_BIN_DIR="+e.BinDir())
+ if len(t.cachedBinDir) > 0 {
+ attr.Env = append(attr.Env, "V23_BIN_DIR="+t.BinDir())
}
attr.Env = append(attr.Env, env...)
// Start up a new shell.
- writeStringOrDie(e, file, ">> Starting a new interactive shell\n")
- writeStringOrDie(e, file, "Hit CTRL-D to resume the test\n")
- if len(e.builtBinaries) > 0 {
- writeStringOrDie(e, file, "Built binaries:\n")
- for _, value := range e.builtBinaries {
- writeStringOrDie(e, file, "\t"+value.Path()+"\n")
+ writeStringOrDie(t, file, ">> Starting a new interactive shell\n")
+ writeStringOrDie(t, file, "Hit CTRL-D to resume the test\n")
+ if len(t.builtBinaries) > 0 {
+ writeStringOrDie(t, file, "Built binaries:\n")
+ for _, value := range t.builtBinaries {
+ writeStringOrDie(t, file, "\t"+value.Path()+"\n")
}
}
- if len(e.cachedBinDir) > 0 {
- writeStringOrDie(e, file, fmt.Sprintf("Binaries are cached in %q\n", e.cachedBinDir))
+ if len(t.cachedBinDir) > 0 {
+ writeStringOrDie(t, file, fmt.Sprintf("Binaries are cached in %q\n", t.cachedBinDir))
} else {
- writeStringOrDie(e, file, fmt.Sprintf("Caching of binaries was not enabled, being written to %q\n", e.binDir))
+ writeStringOrDie(t, file, fmt.Sprintf("Caching of binaries was not enabled, being written to %q\n", t.binDir))
}
shellPath := "/bin/sh"
@@ -652,44 +439,59 @@
}
proc, err := os.StartProcess(shellPath, []string{}, &attr)
if err != nil {
- e.Fatalf("StartProcess(%q) failed: %v", shellPath, err)
+ t.Fatalf("StartProcess(%q) failed: %v", shellPath, err)
}
// Wait until user exits the shell
state, err := proc.Wait()
if err != nil {
- e.Fatalf("Wait(%v) failed: %v", shellPath, err)
+ t.Fatalf("Wait(%v) failed: %v", shellPath, err)
}
- writeStringOrDie(e, file, fmt.Sprintf("<< Exited shell: %s\n", state.String()))
+ writeStringOrDie(t, file, fmt.Sprintf("<< Exited shell: %s\n", state.String()))
}
// BinaryFromPath returns a new Binary that, when started, will
// execute the executable or script at the given path.
//
// E.g. env.BinaryFromPath("/bin/bash").Start("-c", "echo hello world").Output() -> "hello world"
-func (e *T) BinaryFromPath(path string) *Binary {
+func (t *T) BinaryFromPath(path string) *Binary {
return &Binary{
- env: e,
+ env: t,
envVars: nil,
path: path,
+ opts: t.shell.DefaultStartOpts().NoExecCommand(),
}
}
// BuildGoPkg expects a Go package path that identifies a "main"
// package and returns a Binary representing the newly built
-// binary.
-func (e *T) BuildGoPkg(pkg string) *Binary {
+// binary. This binary does not use the exec protocol defined
+// in v.io/x/ref/lib/exec. Use this for command line tools and non
+// Vanadium servers.
+func (t *T) BuildGoPkg(pkg string) *Binary {
+ return t.buildPkg(pkg)
+}
+
+// BuildV23 is like BuildGoPkg, but instead assumes that the resulting
+// binary is a Vanadium application and does implement the exec protocol
+// defined in v.io/x/ref/lib/exec. Use this for Vanadium servers.
+func (t *T) BuildV23Pkg(pkg string) *Binary {
+ b := t.buildPkg(pkg)
+ b.opts = t.shell.DefaultStartOpts().ExternalCommand()
+ return b
+}
+
+func (t *T) buildPkg(pkg string) *Binary {
then := time.Now()
loc := Caller(1)
- cached, built_path, err := buildPkg(e.BinDir(), pkg)
+ cached, built_path, err := buildPkg(t.BinDir(), pkg)
if err != nil {
- e.Fatalf("%s: buildPkg(%s) failed: %v", loc, pkg, err)
+ t.Fatalf("%s: buildPkg(%s) failed: %v", loc, pkg, err)
return nil
}
-
if _, err := os.Stat(built_path); err != nil {
- e.Fatalf("%s: buildPkg(%s) failed to stat %q", loc, pkg, built_path)
+ t.Fatalf("%s: buildPkg(%s) failed to stat %q", loc, pkg, built_path)
}
taken := time.Now().Sub(then)
if cached {
@@ -697,44 +499,44 @@
} else {
vlog.Infof("%s: built %s, written to %s in %s.", loc, pkg, built_path, taken)
}
-
binary := &Binary{
- env: e,
+ env: t,
envVars: nil,
path: built_path,
+ opts: t.shell.DefaultStartOpts().NoExecCommand(),
}
- e.builtBinaries[pkg] = binary
+ t.builtBinaries[pkg] = binary
return binary
}
// NewTempFile creates a temporary file. Temporary files will be deleted
// by Cleanup.
-func (e *T) NewTempFile() *os.File {
+func (t *T) NewTempFile() *os.File {
loc := Caller(1)
f, err := ioutil.TempFile("", "")
if err != nil {
- e.Fatalf("%s: TempFile() failed: %v", loc, err)
+ t.Fatalf("%s: TempFile() failed: %v", loc, err)
}
vlog.Infof("%s: created temporary file at %s", loc, f.Name())
- e.tempFiles = append(e.tempFiles, f)
+ t.tempFiles = append(t.tempFiles, f)
return f
}
// NewTempDir creates a temporary directory. Temporary directories and
// their contents will be deleted by Cleanup.
-func (e *T) NewTempDir() string {
+func (t *T) NewTempDir() string {
loc := Caller(1)
f, err := ioutil.TempDir("", "")
if err != nil {
- e.Fatalf("%s: TempDir() failed: %v", loc, err)
+ t.Fatalf("%s: TempDir() failed: %v", loc, err)
}
vlog.Infof("%s: created temporary directory at %s", loc, f)
- e.tempDirs = append(e.tempDirs, f)
+ t.tempDirs = append(t.tempDirs, f)
return f
}
-func (e *T) appendInvocation(inv *Invocation) {
- e.invocations = append(e.invocations, inv)
+func (t *T) appendInvocation(inv *Invocation) {
+ t.invocations = append(t.invocations, inv)
}
// Creates a new local testing environment. A local testing environment has a
@@ -763,8 +565,10 @@
if err != nil {
t.Fatalf("NewShell() failed: %v", err)
}
- shell.SetStartTimeout(1 * time.Minute)
- shell.SetWaitTimeout(5 * time.Minute)
+ opts := modules.DefaultStartOpts()
+ opts.StartTimeout = time.Minute
+ opts.ShutdownTimeout = 5 * time.Minute
+ shell.SetDefaultStartOpts(opts)
// The V23_BIN_DIR environment variable can be
// used to identify a directory that multiple integration
@@ -788,12 +592,16 @@
return e
}
+func (t *T) Shell() *modules.Shell {
+ return t.shell
+}
+
// BinDir returns the directory that binarie files are stored in.
-func (e *T) BinDir() string {
- if len(e.cachedBinDir) > 0 {
- return e.cachedBinDir
+func (t *T) BinDir() string {
+ if len(t.cachedBinDir) > 0 {
+ return t.cachedBinDir
}
- return e.binDir
+ return t.binDir
}
// BuildPkg returns a path to a directory that contains the built binary for
@@ -833,7 +641,7 @@
// the NAMESPACE_ROOT variable in the test environment so that all subsequent
// invocations will access this root mount table.
func RunRootMT(i *T, args ...string) (*Binary, *Invocation) {
- b := i.BuildGoPkg("v.io/x/ref/services/mounttable/mounttabled")
+ b := i.BuildV23Pkg("v.io/x/ref/services/mounttable/mounttabled")
inv := b.start(1, args...)
name := inv.ExpectVar("NAME")
inv.Environment().SetVar("NAMESPACE_ROOT", name)
diff --git a/lib/testutil/v23tests/v23tests_test.go b/lib/testutil/v23tests/v23tests_test.go
index 4937d26..b253ad9 100644
--- a/lib/testutil/v23tests/v23tests_test.go
+++ b/lib/testutil/v23tests/v23tests_test.go
@@ -18,14 +18,10 @@
"v.io/x/ref/lib/modules"
"v.io/x/ref/lib/testutil"
- "v.io/x/ref/lib/testutil/expect"
"v.io/x/ref/lib/testutil/v23tests"
_ "v.io/x/ref/profiles"
)
-// TODO(sjr): add more unit tests, especially for errors cases.
-// TODO(sjr): need to make sure processes don't get left lying around.
-
func TestBinaryFromPath(t *testing.T) {
env := v23tests.New(t)
defer env.Cleanup()
@@ -48,7 +44,7 @@
defer env.Cleanup()
v23tests.RunRootMT(env, "--veyron.tcp.address=127.0.0.1:0")
- proxyBin := env.BuildGoPkg("v.io/x/ref/services/proxy/proxyd")
+ proxyBin := env.BuildV23Pkg("v.io/x/ref/services/proxy/proxyd")
nsBin := env.BuildGoPkg("v.io/x/ref/cmd/namespace")
mt, ok := env.GetVar("NAMESPACE_ROOT")
@@ -84,8 +80,6 @@
// can examine their output, but not in the parent process. We use the
// modules framework to do so, with the added twist that we need access
// to an instance of testing.T which we obtain via a global variable.
-// TODO(cnicolaou): this will need to change once we switch to using
-// TestMain.
func IntegrationTestInChild(i *v23tests.T) {
fmt.Println("Hello")
sleep := i.BinaryFromPath("/bin/sleep")
@@ -102,7 +96,7 @@
func TestHelperProcess(t *testing.T) {
globalT = t
- modules.DispatchInTest()
+ modules.Dispatch()
}
func RunIntegrationTestInChild(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
@@ -117,19 +111,18 @@
func TestDeferHandling(t *testing.T) {
sh, _ := modules.NewShell(nil, nil)
- child, err := sh.Start("RunIntegrationTestInChild", nil, "--v23.tests")
+ child, err := sh.Start("RunIntegrationTestInChild", nil, "--test.run=TestHelperProcess", "--v23.tests")
if err != nil {
t.Fatal(err)
}
- s := expect.NewSession(t, child.Stdout(), time.Minute)
- s.Expect("Hello")
- s.ExpectRE("--- FAIL: TestHelperProcess", -1)
+ child.Expect("Hello")
+ child.ExpectRE("--- FAIL: TestHelperProcess", -1)
for _, e := range []string{
".* 0: /bin/sleep: shutdown status: has not been shutdown",
".* 1: /bin/sleep: shutdown status: signal: terminated",
".* 2: /bin/sleep: shutdown status: has not been shutdown",
} {
- s.ExpectRE(e, -1)
+ child.ExpectRE(e, -1)
}
var stderr bytes.Buffer
if err := child.Shutdown(nil, &stderr); err != nil {
@@ -285,7 +278,7 @@
msg := recover().(string)
// this, and the tests below are intended to ensure that line #s
// are captured and reported correctly.
- if got, want := msg, "v23tests_test.go:295"; !strings.Contains(got, want) {
+ if got, want := msg, "v23tests_test.go:288"; !strings.Contains(got, want) {
t.Fatalf("%q does not contain %q", got, want)
}
if got, want := msg, "fork/exec /bin/echox: no such file or directory"; !strings.Contains(got, want) {
@@ -300,17 +293,21 @@
env := v23tests.New(mock)
defer env.Cleanup()
- _, inv := v23tests.RunRootMT(env, "--xxveyron.tcp.address=127.0.0.1:0")
+ // Fail fast.
+ sh := env.Shell()
+ opts := sh.DefaultStartOpts()
+ opts.StartTimeout = 100 * time.Millisecond
+ sh.SetDefaultStartOpts(opts)
defer func() {
msg := recover().(string)
- if got, want := msg, "v23tests_test.go:313"; !strings.Contains(got, want) {
+ if got, want := msg, "v23tests_test.go:310"; !strings.Contains(got, want) {
t.Fatalf("%q does not contain %q", got, want)
}
- if got, want := msg, "exit status 2"; !strings.Contains(got, want) {
+ if got, want := msg, "StartWithOpts"; !strings.Contains(got, want) {
t.Fatalf("%q does not contain %q", got, want)
}
}()
- inv.WaitOrDie(nil, nil)
+ v23tests.RunRootMT(env, "--xxveyron.tcp.address=127.0.0.1:0")
}
func TestWaitTimeout(t *testing.T) {
@@ -327,13 +324,11 @@
if iterations == 0 {
t.Fatalf("our sleeper didn't get to run")
}
- if got, want := recover().(string), "v23tests_test.go:335: timed out"; !strings.Contains(got, want) {
+ if got, want := recover().(string), "v23tests_test.go:331: timed out"; !strings.Contains(got, want) {
t.Fatalf("%q does not contain %q", got, want)
}
}()
-
env.WaitFor(sleeper, time.Millisecond, 50*time.Millisecond)
-
}
func TestWaitAsyncTimeout(t *testing.T) {
@@ -351,11 +346,10 @@
if iterations != 0 {
t.Fatalf("our sleeper got to run")
}
- if got, want := recover().(string), "v23tests_test.go:359: timed out"; !strings.Contains(got, want) {
+ if got, want := recover().(string), "v23tests_test.go:353: timed out"; !strings.Contains(got, want) {
t.Fatalf("%q does not contain %q", got, want)
}
}()
-
env.WaitForAsync(sleeper, time.Millisecond, 50*time.Millisecond)
}
diff --git a/profiles/internal/ipc/cancel_test.go b/profiles/internal/ipc/cancel_test.go
index 6a510ab..4154aaa 100644
--- a/profiles/internal/ipc/cancel_test.go
+++ b/profiles/internal/ipc/cancel_test.go
@@ -3,6 +3,8 @@
import (
"testing"
+ tsecurity "v.io/x/ref/lib/testutil/security"
+ "v.io/x/ref/profiles/internal/ipc/stream"
"v.io/x/ref/profiles/internal/ipc/stream/manager"
tnaming "v.io/x/ref/profiles/internal/testing/mocks/naming"
@@ -12,7 +14,6 @@
"v.io/v23/naming/ns"
"v.io/v23/security"
"v.io/x/lib/vlog"
- "v.io/x/ref/profiles/internal/ipc/stream"
)
type fakeAuthorizer int
@@ -57,7 +58,7 @@
func makeCanceld(ns ns.Namespace, name, child string) (*canceld, error) {
sm := manager.InternalNew(naming.FixedRoutingID(0x111111111))
ctx := testContext()
- s, err := testInternalNewServer(ctx, sm, ns)
+ s, err := testInternalNewServer(ctx, sm, ns, tsecurity.NewPrincipal("test"))
if err != nil {
return nil, err
}
diff --git a/profiles/internal/ipc/client_test.go b/profiles/internal/ipc/client_test.go
index b16c1d9..01807eb 100644
--- a/profiles/internal/ipc/client_test.go
+++ b/profiles/internal/ipc/client_test.go
@@ -496,7 +496,6 @@
t.Errorf("Got (%q, %v) want (%q, nil)", result, err, expected)
}
// Kill the server, verify client can't talk to it anymore.
- sh.SetWaitTimeout(time.Minute)
if err := server.Shutdown(os.Stderr, os.Stderr); err != nil {
t.Fatalf("unexpected error: %s", err)
}
diff --git a/profiles/internal/ipc/debug_test.go b/profiles/internal/ipc/debug_test.go
index 71f21b7..502995d 100644
--- a/profiles/internal/ipc/debug_test.go
+++ b/profiles/internal/ipc/debug_test.go
@@ -36,7 +36,7 @@
defer sm.Shutdown()
ns := tnaming.NewSimpleNamespace()
ctx := testContext()
- server, err := testInternalNewServer(ctx, sm, ns, ReservedNameDispatcher{debugDisp}, vc.LocalPrincipal{pserver})
+ server, err := testInternalNewServer(ctx, sm, ns, pserver, ReservedNameDispatcher{debugDisp})
if err != nil {
t.Fatalf("InternalNewServer failed: %v", err)
}
diff --git a/profiles/internal/ipc/discharges.go b/profiles/internal/ipc/discharges.go
index b45912a..49a0996 100644
--- a/profiles/internal/ipc/discharges.go
+++ b/profiles/internal/ipc/discharges.go
@@ -1,6 +1,9 @@
package ipc
import (
+ "reflect"
+ "sort"
+ "strings"
"sync"
"time"
@@ -42,7 +45,10 @@
return &dischargeClient{
c: client,
defaultCtx: defaultCtx,
- cache: dischargeCache{cache: make(map[string]security.Discharge)},
+ cache: dischargeCache{
+ cache: make(map[dischargeCacheKey]security.Discharge),
+ idToKeys: make(map[string][]dischargeCacheKey),
+ },
dischargeExpiryBuffer: dischargeExpiryBuffer,
}
}
@@ -61,18 +67,20 @@
}
// Make a copy since this copy will be mutated.
var caveats []security.Caveat
+ var filteredImpetuses []security.DischargeImpetus
for _, cav := range forcaveats {
// It shouldn't happen, but in case there are non-third-party
// caveats, drop them.
if tp := cav.ThirdPartyDetails(); tp != nil {
caveats = append(caveats, cav)
+ filteredImpetuses = append(filteredImpetuses, filteredImpetus(tp.Requirements(), impetus))
}
}
// Gather discharges from cache.
// (Collect a set of pointers, where nil implies a missing discharge)
discharges := make([]*security.Discharge, len(caveats))
- if d.cache.Discharges(caveats, discharges) > 0 {
+ if d.cache.Discharges(caveats, filteredImpetuses, discharges) > 0 {
// Fetch discharges for caveats for which no discharges were
// found in the cache.
if ctx == nil {
@@ -83,7 +91,7 @@
ctx, span = vtrace.SetNewSpan(ctx, "Fetching Discharges")
defer span.Finish()
}
- d.fetchDischarges(ctx, caveats, impetus, discharges)
+ d.fetchDischarges(ctx, caveats, filteredImpetuses, discharges)
}
for _, d := range discharges {
if d != nil {
@@ -102,12 +110,13 @@
// fetched or no new discharges are fetched.
// REQUIRES: len(caveats) == len(out)
// REQUIRES: caveats[i].ThirdPartyDetails() != nil for 0 <= i < len(caveats)
-func (d *dischargeClient) fetchDischarges(ctx *context.T, caveats []security.Caveat, impetus security.DischargeImpetus, out []*security.Discharge) {
+func (d *dischargeClient) fetchDischarges(ctx *context.T, caveats []security.Caveat, impetuses []security.DischargeImpetus, out []*security.Discharge) {
var wg sync.WaitGroup
for {
type fetched struct {
idx int
discharge *security.Discharge
+ impetus security.DischargeImpetus
}
discharges := make(chan fetched, len(caveats))
want := 0
@@ -121,7 +130,7 @@
defer wg.Done()
tp := cav.ThirdPartyDetails()
vlog.VI(3).Infof("Fetching discharge for %v", tp)
- call, err := d.c.StartCall(ctx, tp.Location(), "Discharge", []interface{}{cav, filteredImpetus(tp.Requirements(), impetus)}, NoDischarges{})
+ call, err := d.c.StartCall(ctx, tp.Location(), "Discharge", []interface{}{cav, impetuses[i]}, NoDischarges{})
if err != nil {
vlog.VI(3).Infof("Discharge fetch for %v failed: %v", tp, err)
return
@@ -131,14 +140,14 @@
vlog.VI(3).Infof("Discharge fetch for %v failed: (%v)", cav, err)
return
}
- discharges <- fetched{i, &d}
+ discharges <- fetched{i, &d, impetuses[i]}
}(i, ctx, caveats[i])
}
wg.Wait()
close(discharges)
var got int
for fetched := range discharges {
- d.cache.Add(*fetched.discharge)
+ d.cache.Add(*fetched.discharge, fetched.impetus)
out[fetched.idx] = fetched.discharge
got++
}
@@ -165,38 +174,68 @@
// dischargeCache is a concurrency-safe cache for third party caveat discharges.
// TODO(suharshs,ataly,ashankar): This should be keyed by filtered impetus as well.
type dischargeCache struct {
- mu sync.RWMutex
- cache map[string]security.Discharge // GUARDED_BY(mu)
+ mu sync.RWMutex
+ cache map[dischargeCacheKey]security.Discharge // GUARDED_BY(mu)
+ idToKeys map[string][]dischargeCacheKey // GUARDED_BY(mu)
}
-// Add inserts the argument to the cache, possibly overwriting previous
-// discharges for the same caveat.
-func (dcc *dischargeCache) Add(discharges ...security.Discharge) {
- dcc.mu.Lock()
- for _, d := range discharges {
- dcc.cache[d.ID()] = d
+type dischargeCacheKey struct {
+ id, method, serverPatterns string
+}
+
+func (dcc *dischargeCache) cacheKey(id string, impetus security.DischargeImpetus) dischargeCacheKey {
+ // We currently do not cache on impetus.Arguments because there it seems there is no
+ // universal way to generate a key from them.
+ // Add sorted BlessingPatterns to the key.
+ var bps []string
+ for _, bp := range impetus.Server {
+ bps = append(bps, string(bp))
}
+ sort.Strings(bps)
+ return dischargeCacheKey{
+ id: id,
+ method: impetus.Method,
+ serverPatterns: strings.Join(bps, ","), // "," is restricted in blessingPatterns.
+ }
+}
+
+// Add inserts the argument to the cache, the previous discharge for the same caveat.
+func (dcc *dischargeCache) Add(d security.Discharge, filteredImpetus security.DischargeImpetus) {
+ // Only add to the cache if the caveat did not require arguments.
+ if len(filteredImpetus.Arguments) > 0 {
+ return
+ }
+ id := d.ID()
+ dcc.mu.Lock()
+ dcc.cache[dcc.cacheKey(id, filteredImpetus)] = d
+ if _, ok := dcc.idToKeys[id]; !ok {
+ dcc.idToKeys[id] = []dischargeCacheKey{}
+ }
+ dcc.idToKeys[id] = append(dcc.idToKeys[id], dcc.cacheKey(id, filteredImpetus))
dcc.mu.Unlock()
}
-// Discharges takes a slice of caveats and a slice of discharges of the same
-// length and fills in nil entries in the discharges slice with pointers to
-// cached discharges (if there are any).
+// Discharges takes a slice of caveats, a slice of filtered Discharge impetuses
+// corresponding to the caveats, and a slice of discharges of the same length and
+// fills in nil entries in the discharges slice with pointers to cached discharges
+// (if there are any).
//
-// REQUIRES: len(caveats) == len(out)
+// REQUIRES: len(caveats) == len(impetuses) == len(out)
// REQUIRES: caveats[i].ThirdPartyDetails() != nil, for all 0 <= i < len(caveats)
-func (dcc *dischargeCache) Discharges(caveats []security.Caveat, out []*security.Discharge) (remaining int) {
+func (dcc *dischargeCache) Discharges(caveats []security.Caveat, impetuses []security.DischargeImpetus, out []*security.Discharge) (remaining int) {
dcc.mu.Lock()
for i, d := range out {
if d != nil {
continue
}
- if cached, exists := dcc.cache[caveats[i].ThirdPartyDetails().ID()]; exists {
+ id := caveats[i].ThirdPartyDetails().ID()
+ key := dcc.cacheKey(id, impetuses[i])
+ if cached, exists := dcc.cache[key]; exists {
out[i] = &cached
// If the discharge has expired, purge it from the cache.
if hasDischargeExpired(out[i]) {
out[i] = nil
- delete(dcc.cache, cached.ID())
+ delete(dcc.cache, key)
remaining++
}
} else {
@@ -218,15 +257,19 @@
func (dcc *dischargeCache) invalidate(discharges ...security.Discharge) {
dcc.mu.Lock()
for _, d := range discharges {
- // TODO(ashankar,ataly): The cached discharge might have been
- // replaced by the time invalidate is called.
- // Should we have an "Equals" function defined on "Discharge"
- // and use that? (Could use reflect.DeepEqual as well, but
- // that will likely be expensive)
- // if cached := dcc.cache[d.ID()]; cached.Equals(d) {
- // delete(dcc.cache, d.ID())
- // }
- delete(dcc.cache, d.ID())
+ if keys, ok := dcc.idToKeys[d.ID()]; ok {
+ var newKeys []dischargeCacheKey
+ for _, k := range keys {
+ // TODO(suharshs,ataly,ashankar): Should we have an "Equals" function
+ // defined on "Discharge" and use that instead of reflect.DeepEqual?
+ if cached := dcc.cache[k]; reflect.DeepEqual(cached, d) {
+ delete(dcc.cache, k)
+ } else {
+ newKeys = append(newKeys, k)
+ }
+ }
+ dcc.idToKeys[d.ID()] = newKeys
+ }
}
dcc.mu.Unlock()
}
diff --git a/profiles/internal/ipc/discharges_test.go b/profiles/internal/ipc/discharges_test.go
new file mode 100644
index 0000000..230b8bf
--- /dev/null
+++ b/profiles/internal/ipc/discharges_test.go
@@ -0,0 +1,102 @@
+package ipc
+
+import (
+ "testing"
+ "time"
+
+ tsecurity "v.io/x/ref/lib/testutil/security"
+
+ "v.io/v23/security"
+ "v.io/v23/vdl"
+)
+
+func TestDischargeClientCache(t *testing.T) {
+ dcc := &dischargeCache{
+ cache: make(map[dischargeCacheKey]security.Discharge),
+ idToKeys: make(map[string][]dischargeCacheKey),
+ }
+
+ var (
+ discharger = tsecurity.NewPrincipal("discharger")
+ expiredCav = mkCaveat(security.NewPublicKeyCaveat(discharger.PublicKey(), "moline", security.ThirdPartyRequirements{}, security.UnconstrainedUse()))
+ argsCav = mkCaveat(security.NewPublicKeyCaveat(discharger.PublicKey(), "moline", security.ThirdPartyRequirements{}, security.UnconstrainedUse()))
+ methodCav = mkCaveat(security.NewPublicKeyCaveat(discharger.PublicKey(), "moline", security.ThirdPartyRequirements{}, security.UnconstrainedUse()))
+ serverCav = mkCaveat(security.NewPublicKeyCaveat(discharger.PublicKey(), "moline", security.ThirdPartyRequirements{}, security.UnconstrainedUse()))
+
+ dExpired = mkDischarge(discharger.MintDischarge(expiredCav, mkCaveat(security.ExpiryCaveat(time.Now().Add(-1*time.Minute)))))
+ dArgs = mkDischarge(discharger.MintDischarge(argsCav, security.UnconstrainedUse()))
+ dMethod = mkDischarge(discharger.MintDischarge(methodCav, security.UnconstrainedUse()))
+ dServer = mkDischarge(discharger.MintDischarge(serverCav, security.UnconstrainedUse()))
+
+ emptyImp = security.DischargeImpetus{}
+ argsImp = security.DischargeImpetus{Arguments: []*vdl.Value{&vdl.Value{}}}
+ methodImp = security.DischargeImpetus{Method: "foo"}
+ otherMethodImp = security.DischargeImpetus{Method: "bar"}
+ serverImp = security.DischargeImpetus{Server: []security.BlessingPattern{security.BlessingPattern("fooserver")}}
+ otherServerImp = security.DischargeImpetus{Server: []security.BlessingPattern{security.BlessingPattern("barserver")}}
+ )
+
+ // Discharges for different cavs should not be cached.
+ d := mkDischarge(discharger.MintDischarge(argsCav, security.UnconstrainedUse()))
+ dcc.Add(d, emptyImp)
+ outdis := make([]*security.Discharge, 1)
+ if remaining := dcc.Discharges([]security.Caveat{methodCav}, []security.DischargeImpetus{emptyImp}, outdis); remaining == 0 {
+ t.Errorf("Discharge for different caveat should not have been in cache")
+ }
+ dcc.invalidate(d)
+
+ // Add some discharges into the cache.
+ dcc.Add(dArgs, argsImp)
+ dcc.Add(dMethod, methodImp)
+ dcc.Add(dServer, serverImp)
+ dcc.Add(dExpired, emptyImp)
+
+ testCases := []struct {
+ caveat security.Caveat // caveat that we are fetching discharges for.
+ queryImpetus security.DischargeImpetus // Impetus used to query the cache.
+ cachedDischarge *security.Discharge // Discharge that we expect to be returned from the cache, nil if the discharge should not be cached.
+ }{
+ // Expired discharges should not be returned by the cache.
+ {expiredCav, emptyImp, nil},
+
+ // Discharges with Impetuses that have Arguments should not be cached.
+ {argsCav, argsImp, nil},
+
+ {methodCav, methodImp, &dMethod},
+ {methodCav, otherMethodImp, nil},
+ {methodCav, emptyImp, nil},
+
+ {serverCav, serverImp, &dServer},
+ {serverCav, otherServerImp, nil},
+ {serverCav, emptyImp, nil},
+ }
+
+ for i, test := range testCases {
+ out := make([]*security.Discharge, 1)
+ remaining := dcc.Discharges([]security.Caveat{test.caveat}, []security.DischargeImpetus{test.queryImpetus}, out)
+ if test.cachedDischarge != nil {
+ got := "nil"
+ if remaining == 0 {
+ got = out[0].ID()
+ }
+ if got != test.cachedDischarge.ID() {
+ t.Errorf("#%d: got discharge %v, want %v, queried with %v", i, got, test.cachedDischarge.ID(), test.queryImpetus)
+ }
+ } else if remaining == 0 {
+ t.Errorf("#%d: discharge %v should not have been in cache, queried with %v", i, out[0].ID(), test.queryImpetus)
+ }
+ }
+ if t.Failed() {
+ t.Logf("dArgs.ID(): %v", dArgs.ID())
+ t.Logf("dMethod.ID(): %v", dMethod.ID())
+ t.Logf("dServer.ID(): %v", dServer.ID())
+ t.Logf("dExpired.ID(): %v", dExpired.ID())
+ }
+}
+
+func mkDischarge(d security.Discharge, err error) security.Discharge {
+ if err != nil {
+ panic(err)
+ }
+ return d
+}
diff --git a/profiles/internal/ipc/full_test.go b/profiles/internal/ipc/full_test.go
index 8adb4bf..a8e0077 100644
--- a/profiles/internal/ipc/full_test.go
+++ b/profiles/internal/ipc/full_test.go
@@ -81,12 +81,12 @@
return ctx
}
-func testInternalNewServer(ctx *context.T, streamMgr stream.Manager, ns ns.Namespace, opts ...ipc.ServerOpt) (ipc.Server, error) {
+func testInternalNewServer(ctx *context.T, streamMgr stream.Manager, ns ns.Namespace, principal security.Principal, opts ...ipc.ServerOpt) (ipc.Server, error) {
client, err := InternalNewClient(streamMgr, ns)
if err != nil {
return nil, err
}
- return InternalNewServer(ctx, streamMgr, ns, client, opts...)
+ return InternalNewServer(ctx, streamMgr, ns, client, principal, opts...)
}
type userType string
@@ -254,9 +254,8 @@
func startServerWS(t *testing.T, principal security.Principal, sm stream.Manager, ns ns.Namespace, name string, disp ipc.Dispatcher, shouldUseWebsocket websocketMode, opts ...ipc.ServerOpt) (naming.Endpoint, ipc.Server) {
vlog.VI(1).Info("InternalNewServer")
- opts = append(opts, vc.LocalPrincipal{principal})
ctx := testContext()
- server, err := testInternalNewServer(ctx, sm, ns, opts...)
+ server, err := testInternalNewServer(ctx, sm, ns, principal, opts...)
if err != nil {
t.Errorf("InternalNewServer failed: %v", err)
}
@@ -399,13 +398,13 @@
return verror.Is(err, id.ID)
}
-func runServer(t *testing.T, ns ns.Namespace, name string, obj interface{}, opts ...ipc.ServerOpt) stream.Manager {
+func runServer(t *testing.T, ns ns.Namespace, principal security.Principal, name string, obj interface{}, opts ...ipc.ServerOpt) stream.Manager {
rid, err := naming.NewRoutingID()
if err != nil {
t.Fatal(err)
}
sm := imanager.InternalNew(rid)
- server, err := testInternalNewServer(testContext(), sm, ns, opts...)
+ server, err := testInternalNewServer(testContext(), sm, ns, principal, opts...)
if err != nil {
t.Fatal(err)
}
@@ -422,7 +421,7 @@
sm := imanager.InternalNew(naming.FixedRoutingID(0x555555555))
ns := tnaming.NewSimpleNamespace()
ctx := testContext()
- server, err := testInternalNewServer(ctx, sm, ns, vc.LocalPrincipal{tsecurity.NewPrincipal("server")})
+ server, err := testInternalNewServer(ctx, sm, ns, tsecurity.NewPrincipal("server"))
if err != nil {
t.Errorf("InternalNewServer failed: %v", err)
}
@@ -618,7 +617,7 @@
// namespace that the client will use, provide it with a
// different namespace).
tnaming.NewSimpleNamespace(),
- vc.LocalPrincipal{tsecurity.NewPrincipal("attacker")})
+ tsecurity.NewPrincipal("attacker"))
if err != nil {
t.Fatal(err)
}
@@ -922,7 +921,7 @@
// Setup the discharge server.
var tester dischargeTestServer
ctx := testContext()
- dischargeServer, err := testInternalNewServer(ctx, sm, ns, vc.LocalPrincipal{pdischarger})
+ dischargeServer, err := testInternalNewServer(ctx, sm, ns, pdischarger)
if err != nil {
t.Fatal(err)
}
@@ -935,7 +934,7 @@
}
// Setup the application server.
- appServer, err := testInternalNewServer(ctx, sm, ns, vc.LocalPrincipal{pserver})
+ appServer, err := testInternalNewServer(ctx, sm, ns, pserver)
if err != nil {
t.Fatal(err)
}
@@ -1527,7 +1526,7 @@
return []ipc.Address{&netstate.AddrIfc{Addr: a}}, nil
}
ctx := testContext()
- server, err := testInternalNewServer(ctx, sm, ns, vc.LocalPrincipal{tsecurity.NewPrincipal("server")})
+ server, err := testInternalNewServer(ctx, sm, ns, tsecurity.NewPrincipal("server"))
if err != nil {
t.Errorf("InternalNewServer failed: %v", err)
}
@@ -1569,7 +1568,7 @@
return nil, fmt.Errorf("oops")
}
ctx := testContext()
- server, err := testInternalNewServer(ctx, sm, ns, vc.LocalPrincipal{tsecurity.NewPrincipal("server")})
+ server, err := testInternalNewServer(ctx, sm, ns, tsecurity.NewPrincipal("server"))
if err != nil {
t.Errorf("InternalNewServer failed: %v", err)
}
@@ -1598,7 +1597,7 @@
defer sm.Shutdown()
ns := tnaming.NewSimpleNamespace()
ctx := testContext()
- server, err := testInternalNewServer(ctx, sm, ns, options.VCSecurityNone)
+ server, err := testInternalNewServer(ctx, sm, ns, nil, options.VCSecurityNone)
if err != nil {
t.Fatalf("InternalNewServer failed: %v", err)
}
@@ -1664,9 +1663,8 @@
// to act as batman (as opposed to using the default blessing).
ns := tnaming.NewSimpleNamespace()
- popt := vc.LocalPrincipal{pserver}
- defer runServer(t, ns, "mountpoint/batman", &testServer{}, popt, options.ServerBlessings{batman}).Shutdown()
- defer runServer(t, ns, "mountpoint/default", &testServer{}, popt).Shutdown()
+ defer runServer(t, ns, pserver, "mountpoint/batman", &testServer{}, options.ServerBlessings{batman}).Shutdown()
+ defer runServer(t, ns, pserver, "mountpoint/default", &testServer{}).Shutdown()
// And finally, make an RPC and see that the client sees "batman"
runClient := func(server string) ([]string, error) {
@@ -1726,8 +1724,8 @@
// Setup the disharger and test server.
discharger := &dischargeServer{}
- defer runServer(t, ns, "mountpoint/discharger", discharger, vc.LocalPrincipal{pdischarger}).Shutdown()
- defer runServer(t, ns, "mountpoint/testServer", &testServer{}, vc.LocalPrincipal{pserver}).Shutdown()
+ defer runServer(t, ns, pdischarger, "mountpoint/discharger", discharger).Shutdown()
+ defer runServer(t, ns, pserver, "mountpoint/testServer", &testServer{}).Shutdown()
runClient := func(noDischarges bool) {
rid, err := naming.NewRoutingID()
@@ -1784,8 +1782,8 @@
// Setup the disharger and test server.
discharger1 := &dischargeServer{}
discharger2 := &dischargeServer{}
- defer runServer(t, ns, "mountpoint/discharger1", discharger1, vc.LocalPrincipal{pdischarger1}).Shutdown()
- defer runServer(t, ns, "mountpoint/discharger2", discharger2, vc.LocalPrincipal{pdischarger2}).Shutdown()
+ defer runServer(t, ns, pdischarger1, "mountpoint/discharger1", discharger1).Shutdown()
+ defer runServer(t, ns, pdischarger2, "mountpoint/discharger2", discharger2).Shutdown()
rid, err := naming.NewRoutingID()
if err != nil {
@@ -1827,7 +1825,7 @@
ns := tnaming.NewSimpleNamespace()
- serverSM := runServer(t, ns, "mountpoint/testServer", &testServer{}, vc.LocalPrincipal{pserver})
+ serverSM := runServer(t, ns, pserver, "mountpoint/testServer", &testServer{})
defer serverSM.Shutdown()
rid := serverSM.RoutingID()
@@ -1912,7 +1910,7 @@
mountName := "mountpoint/default"
// Start a server with pserver.
- defer runServer(t, ns, mountName, &testServer{}, vc.LocalPrincipal{pserver}).Shutdown()
+ defer runServer(t, ns, pserver, mountName, &testServer{}).Shutdown()
smc := imanager.InternalNew(naming.FixedRoutingID(0xc))
client, err := InternalNewClient(
@@ -1976,7 +1974,7 @@
// Setup the disharge server.
discharger := &expiryDischarger{}
- defer runServer(t, ns, "mountpoint/discharger", discharger, vc.LocalPrincipal{pdischarger}).Shutdown()
+ defer runServer(t, ns, pdischarger, "mountpoint/discharger", discharger).Shutdown()
// Create a discharge client.
rid, err := naming.NewRoutingID()
diff --git a/profiles/internal/ipc/proxy_test.go b/profiles/internal/ipc/proxy_test.go
index 58259d9..6eed70a 100644
--- a/profiles/internal/ipc/proxy_test.go
+++ b/profiles/internal/ipc/proxy_test.go
@@ -185,7 +185,7 @@
}
defer client.Close()
serverCtx, _ := v23.SetPrincipal(ctx, pserver)
- server, err := iipc.InternalNewServer(serverCtx, smserver, ns, nil, vc.LocalPrincipal{pserver})
+ server, err := iipc.InternalNewServer(serverCtx, smserver, ns, nil, pserver)
if err != nil {
t.Fatal(err)
}
diff --git a/profiles/internal/ipc/server.go b/profiles/internal/ipc/server.go
index 6ee0b8f..c289c3d 100644
--- a/profiles/internal/ipc/server.go
+++ b/profiles/internal/ipc/server.go
@@ -74,6 +74,7 @@
publisher publisher.Publisher // publisher to publish mounttable mounts.
listenerOpts []stream.ListenerOpt // listener opts for Listen.
dhcpState *dhcpState // dhcpState, nil if not using dhcp
+ principal security.Principal
// maps that contain state on listeners.
listenState map[*listenState]struct{}
@@ -150,15 +151,15 @@
var _ ipc.Server = (*server)(nil)
-func InternalNewServer(ctx *context.T, streamMgr stream.Manager, ns ns.Namespace, client ipc.Client, opts ...ipc.ServerOpt) (ipc.Server, error) {
+func InternalNewServer(ctx *context.T, streamMgr stream.Manager, ns ns.Namespace, client ipc.Client, principal security.Principal, opts ...ipc.ServerOpt) (ipc.Server, error) {
ctx, cancel := context.WithRootCancel(ctx)
ctx, _ = vtrace.SetNewSpan(ctx, "NewServer")
statsPrefix := naming.Join("ipc", "server", "routing-id", streamMgr.RoutingID().String())
s := &server{
-
ctx: ctx,
cancel: cancel,
streamMgr: streamMgr,
+ principal: principal,
publisher: publisher.New(ctx, ns, publishPeriod),
listenState: make(map[*listenState]struct{}),
listeners: make(map[stream.Listener]struct{}),
@@ -169,7 +170,6 @@
stats: newIPCStats(statsPrefix),
}
var (
- principal security.Principal
blessings security.Blessings
dischargeExpiryBuffer = vc.DefaultServerDischargeExpiryBuffer
)
@@ -179,8 +179,6 @@
// Collect all ServerOpts that are also ListenerOpts.
s.listenerOpts = append(s.listenerOpts, opt)
switch opt := opt.(type) {
- case vc.LocalPrincipal:
- principal = opt.Principal
case options.ServerBlessings:
blessings = opt.Blessings
case vc.DischargeExpiryBuffer:
@@ -391,7 +389,7 @@
protocol: addr.Protocol,
address: addr.Address,
}
- ls.ln, ls.lep, ls.lnerr = s.streamMgr.Listen(addr.Protocol, addr.Address, s.listenerOpts...)
+ ls.ln, ls.lep, ls.lnerr = s.streamMgr.Listen(addr.Protocol, addr.Address, s.principal, s.listenerOpts...)
lnState = append(lnState, ls)
if ls.lnerr != nil {
vlog.VI(2).Infof("Listen(%q, %q, ...) failed: %v", addr.Protocol, addr.Address, ls.lnerr)
@@ -463,7 +461,7 @@
if err != nil {
return nil, nil, fmt.Errorf("Failed to resolve proxy %q (%v)", proxy, err)
}
- ln, ep, err := s.streamMgr.Listen(inaming.Network, resolved, s.listenerOpts...)
+ ln, ep, err := s.streamMgr.Listen(inaming.Network, resolved, s.principal, s.listenerOpts...)
if err != nil {
return nil, nil, fmt.Errorf("failed to listen on %q: %s", resolved, err)
}
diff --git a/profiles/internal/ipc/server_test.go b/profiles/internal/ipc/server_test.go
index 469cb59..2670900 100644
--- a/profiles/internal/ipc/server_test.go
+++ b/profiles/internal/ipc/server_test.go
@@ -45,7 +45,7 @@
defer sm.Shutdown()
ns := tnaming.NewSimpleNamespace()
ctx := testContext()
- server, err := testInternalNewServer(ctx, sm, ns)
+ server, err := testInternalNewServer(ctx, sm, ns, tsecurity.NewPrincipal("test"))
if err != nil {
t.Fatal(err)
}
@@ -87,7 +87,7 @@
sm := imanager.InternalNew(naming.FixedRoutingID(0x555555555))
defer sm.Shutdown()
ns := tnaming.NewSimpleNamespace()
- server, err := InternalNewServer(testContext(), sm, ns, nil)
+ server, err := testInternalNewServer(testContext(), sm, ns, tsecurity.NewPrincipal("test"))
if err != nil {
t.Fatal(err)
}
@@ -134,7 +134,7 @@
sm := imanager.InternalNew(naming.FixedRoutingID(0x555555555))
defer sm.Shutdown()
ns := tnaming.NewSimpleNamespace()
- principal := vc.LocalPrincipal{tsecurity.NewPrincipal("testServerStatus")}
+ principal := tsecurity.NewPrincipal("testServerStatus")
server, err := testInternalNewServer(ctx, sm, ns, principal)
if err != nil {
t.Fatal(err)
@@ -162,7 +162,7 @@
progress := make(chan error)
- client, err := InternalNewClient(sm, ns, principal)
+ client, err := InternalNewClient(sm, ns, vc.LocalPrincipal{principal})
makeCall := func(ctx *context.T) {
call, err := client.StartCall(ctx, "test", "Hang", nil)
progress <- err
@@ -236,7 +236,7 @@
}
}
- server, err := testInternalNewServer(ctx, sm, ns)
+ server, err := testInternalNewServer(ctx, sm, ns, tsecurity.NewPrincipal("test"))
expectNoError(err)
defer server.Stop()
@@ -289,7 +289,7 @@
sm := imanager.InternalNew(naming.FixedRoutingID(0x555555555))
defer sm.Shutdown()
ns := tnaming.NewSimpleNamespace()
- server, err := testInternalNewServer(testContext(), sm, ns)
+ server, err := testInternalNewServer(testContext(), sm, ns, tsecurity.NewPrincipal("test"))
if err != nil {
t.Fatal(err)
}
@@ -411,7 +411,7 @@
sm := imanager.InternalNew(naming.FixedRoutingID(0x555555555))
defer sm.Shutdown()
ns := tnaming.NewSimpleNamespace()
- server, err := testInternalNewServer(testContext(), sm, ns)
+ server, err := testInternalNewServer(testContext(), sm, ns, tsecurity.NewPrincipal("test"))
defer server.Stop()
if err != nil {
@@ -560,7 +560,7 @@
sm := imanager.InternalNew(naming.FixedRoutingID(0x555555555))
defer sm.Shutdown()
ns := tnaming.NewSimpleNamespace()
- server, err := testInternalNewServer(testContext(), sm, ns)
+ server, err := testInternalNewServer(testContext(), sm, ns, tsecurity.NewPrincipal("test"))
defer server.Stop()
if err != nil {
diff --git a/profiles/internal/ipc/stream/benchmark/dial_vc.go b/profiles/internal/ipc/stream/benchmark/dial_vc.go
index 68cfdd5..ca94f11 100644
--- a/profiles/internal/ipc/stream/benchmark/dial_vc.go
+++ b/profiles/internal/ipc/stream/benchmark/dial_vc.go
@@ -5,6 +5,7 @@
"time"
"v.io/x/ref/lib/testutil/benchmark"
+ tsecurity "v.io/x/ref/lib/testutil/security"
"v.io/x/ref/profiles/internal/ipc/stream/manager"
_ "v.io/x/ref/profiles/static"
@@ -19,7 +20,7 @@
server := manager.InternalNew(naming.FixedRoutingID(0x5))
client := manager.InternalNew(naming.FixedRoutingID(0xc))
- _, ep, err := server.Listen("tcp", "127.0.0.1:0", mode)
+ _, ep, err := server.Listen("tcp", "127.0.0.1:0", tsecurity.NewPrincipal("test"), mode)
if err != nil {
b.Fatal(err)
}
diff --git a/profiles/internal/ipc/stream/benchmark/dial_vif.go b/profiles/internal/ipc/stream/benchmark/dial_vif.go
index 6bd7d7d..fd1d47e 100644
--- a/profiles/internal/ipc/stream/benchmark/dial_vif.go
+++ b/profiles/internal/ipc/stream/benchmark/dial_vif.go
@@ -6,6 +6,7 @@
"time"
"v.io/x/ref/lib/testutil/benchmark"
+ tsecurity "v.io/x/ref/lib/testutil/security"
"v.io/x/ref/profiles/internal/ipc/stream/vif"
"v.io/v23/naming"
@@ -15,6 +16,7 @@
// benchmarkDialVIF measures VIF creation time over the underlying net connection.
func benchmarkDialVIF(b *testing.B, mode options.VCSecurityLevel) {
stats := benchmark.AddStats(b, 16)
+ principal := tsecurity.NewPrincipal("test")
b.ResetTimer() // Exclude setup time from measurement.
@@ -22,7 +24,7 @@
b.StopTimer()
nc, ns := net.Pipe()
- server, err := vif.InternalNewAcceptedVIF(ns, naming.FixedRoutingID(0x5), nil, mode)
+ server, err := vif.InternalNewAcceptedVIF(ns, naming.FixedRoutingID(0x5), principal, nil, mode)
if err != nil {
b.Fatal(err)
}
diff --git a/profiles/internal/ipc/stream/benchmark/throughput_flow.go b/profiles/internal/ipc/stream/benchmark/throughput_flow.go
index e0191d0..82e4b9c 100644
--- a/profiles/internal/ipc/stream/benchmark/throughput_flow.go
+++ b/profiles/internal/ipc/stream/benchmark/throughput_flow.go
@@ -8,6 +8,7 @@
"v.io/v23/naming"
"v.io/v23/options"
+ tsecurity "v.io/x/ref/lib/testutil/security"
"v.io/x/ref/profiles/internal/ipc/stream"
)
@@ -28,7 +29,7 @@
func createListeners(mode options.VCSecurityLevel, m stream.Manager, N int) (servers []listener, err error) {
for i := 0; i < N; i++ {
var l listener
- if l.ln, l.ep, err = m.Listen("tcp", "127.0.0.1:0", mode); err != nil {
+ if l.ln, l.ep, err = m.Listen("tcp", "127.0.0.1:0", tsecurity.NewPrincipal("test"), mode); err != nil {
return
}
servers = append(servers, l)
diff --git a/profiles/internal/ipc/stream/manager/listener.go b/profiles/internal/ipc/stream/manager/listener.go
index 6c23880..07fde42 100644
--- a/profiles/internal/ipc/stream/manager/listener.go
+++ b/profiles/internal/ipc/stream/manager/listener.go
@@ -8,11 +8,13 @@
"sync"
"v.io/x/ref/profiles/internal/ipc/stream/proxy"
+ "v.io/x/ref/profiles/internal/ipc/stream/vc"
"v.io/x/ref/profiles/internal/ipc/stream/vif"
"v.io/x/ref/profiles/internal/lib/upcqueue"
inaming "v.io/x/ref/profiles/internal/naming"
"v.io/v23/naming"
+ "v.io/v23/security"
"v.io/v23/verror"
"v.io/v23/vom"
"v.io/x/lib/vlog"
@@ -44,15 +46,16 @@
// proxyListener implements the listener interface by connecting to a remote
// proxy (typically used to "listen" across network domains).
type proxyListener struct {
- q *upcqueue.T
- proxyEP naming.Endpoint
- manager *manager
- opts []stream.ListenerOpt
+ q *upcqueue.T
+ proxyEP naming.Endpoint
+ manager *manager
+ principal security.Principal
+ opts []stream.ListenerOpt
}
var _ stream.Listener = (*proxyListener)(nil)
-func newNetListener(m *manager, netLn net.Listener, opts []stream.ListenerOpt) listener {
+func newNetListener(m *manager, netLn net.Listener, principal security.Principal, opts []stream.ListenerOpt) listener {
ln := &netListener{
q: upcqueue.New(),
manager: m,
@@ -60,11 +63,11 @@
vifs: vif.NewSet(),
}
ln.netLoop.Add(1)
- go ln.netAcceptLoop(opts)
+ go ln.netAcceptLoop(principal, opts)
return ln
}
-func (ln *netListener) netAcceptLoop(listenerOpts []stream.ListenerOpt) {
+func (ln *netListener) netAcceptLoop(principal security.Principal, listenerOpts []stream.ListenerOpt) {
defer ln.netLoop.Done()
for {
conn, err := ln.netLn.Accept()
@@ -73,7 +76,7 @@
return
}
vlog.VI(1).Infof("New net.Conn accepted from %s (local address: %s)", conn.RemoteAddr(), conn.LocalAddr())
- vf, err := vif.InternalNewAcceptedVIF(conn, ln.manager.rid, nil, listenerOpts...)
+ vf, err := vif.InternalNewAcceptedVIF(conn, ln.manager.rid, principal, nil, listenerOpts...)
if err != nil {
vlog.Infof("Shutting down conn from %s (local address: %s) as a VIF could not be created: %v", conn.RemoteAddr(), conn.LocalAddr(), err)
conn.Close()
@@ -137,12 +140,13 @@
return strings.Join(ret, "\n")
}
-func newProxyListener(m *manager, proxyEP naming.Endpoint, opts []stream.ListenerOpt) (listener, *inaming.Endpoint, error) {
+func newProxyListener(m *manager, proxyEP naming.Endpoint, principal security.Principal, opts []stream.ListenerOpt) (listener, *inaming.Endpoint, error) {
ln := &proxyListener{
- q: upcqueue.New(),
- proxyEP: proxyEP,
- manager: m,
- opts: opts,
+ q: upcqueue.New(),
+ proxyEP: proxyEP,
+ manager: m,
+ opts: opts,
+ principal: principal,
}
vf, ep, err := ln.connect()
if err != nil {
@@ -163,6 +167,8 @@
}
}
// TODO(cnicolaou, ashankar): probably want to set a timeout here. (is this covered by opts?)
+ // TODO(suharshs): Pass the principal explicitly to FindOrDialVIF and remove vc.LocalPrincipal.
+ dialOpts = append(dialOpts, vc.LocalPrincipal{ln.principal})
vf, err := ln.manager.FindOrDialVIF(ln.proxyEP, dialOpts...)
if err != nil {
return nil, nil, err
diff --git a/profiles/internal/ipc/stream/manager/manager.go b/profiles/internal/ipc/stream/manager/manager.go
index b8e657a..e7c42e1 100644
--- a/profiles/internal/ipc/stream/manager/manager.go
+++ b/profiles/internal/ipc/stream/manager/manager.go
@@ -19,7 +19,6 @@
"v.io/x/ref/lib/stats"
"v.io/x/ref/profiles/internal/ipc/stream"
"v.io/x/ref/profiles/internal/ipc/stream/crypto"
- "v.io/x/ref/profiles/internal/ipc/stream/vc"
"v.io/x/ref/profiles/internal/ipc/stream/vif"
"v.io/x/ref/profiles/internal/ipc/version"
inaming "v.io/x/ref/profiles/internal/naming"
@@ -160,12 +159,12 @@
return nil, fmt.Errorf("unknown network %s", protocol)
}
-func (m *manager) Listen(protocol, address string, opts ...stream.ListenerOpt) (stream.Listener, naming.Endpoint, error) {
- blessings, err := extractBlessings(opts)
+func (m *manager) Listen(protocol, address string, principal security.Principal, opts ...stream.ListenerOpt) (stream.Listener, naming.Endpoint, error) {
+ blessings, err := extractBlessings(principal, opts)
if err != nil {
return nil, nil, err
}
- ln, ep, err := m.internalListen(protocol, address, opts...)
+ ln, ep, err := m.internalListen(protocol, address, principal, opts...)
if err != nil {
return nil, nil, err
}
@@ -173,7 +172,7 @@
return ln, ep, nil
}
-func (m *manager) internalListen(protocol, address string, opts ...stream.ListenerOpt) (stream.Listener, *inaming.Endpoint, error) {
+func (m *manager) internalListen(protocol, address string, principal security.Principal, opts ...stream.ListenerOpt) (stream.Listener, *inaming.Endpoint, error) {
m.muListeners.Lock()
if m.shutdown {
m.muListeners.Unlock()
@@ -187,7 +186,7 @@
if err != nil {
return nil, nil, fmt.Errorf("failed to parse endpoint %q: %v", address, err)
}
- return m.remoteListen(ep, opts)
+ return m.remoteListen(ep, principal, opts)
}
netln, err := listen(protocol, address)
if err != nil {
@@ -201,14 +200,14 @@
return nil, nil, errShutDown
}
- ln := newNetListener(m, netln, opts)
+ ln := newNetListener(m, netln, principal, opts)
m.listeners[ln] = true
m.muListeners.Unlock()
return ln, version.Endpoint(protocol, netln.Addr().String(), m.rid), nil
}
-func (m *manager) remoteListen(proxy naming.Endpoint, listenerOpts []stream.ListenerOpt) (stream.Listener, *inaming.Endpoint, error) {
- ln, ep, err := newProxyListener(m, proxy, listenerOpts)
+func (m *manager) remoteListen(proxy naming.Endpoint, principal security.Principal, listenerOpts []stream.ListenerOpt) (stream.Listener, *inaming.Endpoint, error) {
+ ln, ep, err := newProxyListener(m, proxy, principal, listenerOpts)
if err != nil {
return nil, nil, err
}
@@ -298,15 +297,10 @@
return strings.Join(l, "\n")
}
-func extractBlessings(opts []stream.ListenerOpt) ([]string, error) {
- var (
- p security.Principal
- b security.Blessings
- )
+func extractBlessings(p security.Principal, opts []stream.ListenerOpt) ([]string, error) {
+ var b security.Blessings
for _, o := range opts {
switch v := o.(type) {
- case vc.LocalPrincipal:
- p = v.Principal
case options.ServerBlessings:
b = v.Blessings
}
diff --git a/profiles/internal/ipc/stream/manager/manager_test.go b/profiles/internal/ipc/stream/manager/manager_test.go
index dbff17d..3d7a1a5 100644
--- a/profiles/internal/ipc/stream/manager/manager_test.go
+++ b/profiles/internal/ipc/stream/manager/manager_test.go
@@ -31,10 +31,6 @@
inaming "v.io/x/ref/profiles/internal/naming"
)
-func newPrincipal(defaultBlessing string) vc.LocalPrincipal {
- return vc.LocalPrincipal{tsecurity.NewPrincipal(defaultBlessing)}
-}
-
func init() {
modules.RegisterChild("runServer", "", runServer)
}
@@ -48,7 +44,7 @@
// condition that occurs when closing the server; also, using 1 cpu
// introduces less variance in the behavior of the test.
runtime.GOMAXPROCS(1)
- if modules.IsModulesProcess() {
+ if modules.IsModulesChildProcess() {
if err := modules.Dispatch(); err != nil {
fmt.Fprintf(os.Stderr, "modules.Dispatch failed: %v\n", err)
os.Exit(1)
@@ -62,7 +58,7 @@
server := InternalNew(naming.FixedRoutingID(0x55555555))
client := InternalNew(naming.FixedRoutingID(0xcccccccc))
- ln, ep, err := server.Listen(protocol, "127.0.0.1:0")
+ ln, ep, err := server.Listen(protocol, "127.0.0.1:0", tsecurity.NewPrincipal("test"))
if err != nil {
t.Fatal(err)
}
@@ -176,10 +172,10 @@
server = InternalNew(naming.FixedRoutingID(0x55555555))
client = InternalNew(naming.FixedRoutingID(0xcccccccc))
- clientPrincipal = newPrincipal("client")
- serverPrincipal = newPrincipal("server")
- clientKey = clientPrincipal.Principal.PublicKey()
- serverBlessings = serverPrincipal.Principal.BlessingStore().Default()
+ clientPrincipal = tsecurity.NewPrincipal("client")
+ serverPrincipal = tsecurity.NewPrincipal("server")
+ clientKey = clientPrincipal.PublicKey()
+ serverBlessings = serverPrincipal.BlessingStore().Default()
)
// VCSecurityLevel is intentionally not provided to Listen - to test
// default behavior.
@@ -223,7 +219,7 @@
go func() {
// VCSecurityLevel is intentionally not provided to Dial - to
// test default behavior.
- vc, err := client.Dial(ep, clientPrincipal)
+ vc, err := client.Dial(ep, vc.LocalPrincipal{clientPrincipal})
if err != nil {
errs <- err
return
@@ -259,8 +255,8 @@
func TestListenEndpoints(t *testing.T) {
server := InternalNew(naming.FixedRoutingID(0xcafe))
- ln1, ep1, err1 := server.Listen("tcp", "127.0.0.1:0")
- ln2, ep2, err2 := server.Listen("tcp", "127.0.0.1:0")
+ ln1, ep1, err1 := server.Listen("tcp", "127.0.0.1:0", tsecurity.NewPrincipal("test"))
+ ln2, ep2, err2 := server.Listen("tcp", "127.0.0.1:0", tsecurity.NewPrincipal("test"))
// Since "127.0.0.1:0" was used as the network address, a random port will be
// assigned in each case. The endpoint should include that random port.
if err1 != nil {
@@ -306,7 +302,7 @@
func testCloseListener(t *testing.T, protocol string) {
server := InternalNew(naming.FixedRoutingID(0x5e97e9))
- ln, ep, err := server.Listen(protocol, "127.0.0.1:0")
+ ln, ep, err := server.Listen(protocol, "127.0.0.1:0", tsecurity.NewPrincipal("test"))
if err != nil {
t.Fatal(err)
}
@@ -325,7 +321,7 @@
func TestShutdown(t *testing.T) {
server := InternalNew(naming.FixedRoutingID(0x5e97e9))
- ln, _, err := server.Listen("tcp", "127.0.0.1:0")
+ ln, _, err := server.Listen("tcp", "127.0.0.1:0", tsecurity.NewPrincipal("test"))
if err != nil {
t.Fatal(err)
}
@@ -335,7 +331,7 @@
t.Errorf("expecting %d listeners, got %d for %s", n, expect, debugString(server))
}
server.Shutdown()
- if _, _, err := server.Listen("tcp", "127.0.0.1:0"); err == nil {
+ if _, _, err := server.Listen("tcp", "127.0.0.1:0", tsecurity.NewPrincipal("test")); err == nil {
t.Error("server should have shut down")
}
if n, expect := numListeners(server), 0; n != expect {
@@ -355,7 +351,7 @@
server := InternalNew(naming.FixedRoutingID(0x55555555))
client := InternalNew(naming.FixedRoutingID(0xcccccccc))
- ln, ep, err := server.Listen(protocol, "127.0.0.1:0")
+ ln, ep, err := server.Listen(protocol, "127.0.0.1:0", tsecurity.NewPrincipal("test"))
if err != nil {
t.Fatal(err)
}
@@ -379,13 +375,13 @@
/* TLS + resumption + channel bindings is broken: <https://secure-resumption.com/#channelbindings>.
func TestSessionTicketCache(t *testing.T) {
server := InternalNew(naming.FixedRoutingID(0x55555555))
- _, ep, err := server.Listen("tcp", "127.0.0.1:0", newPrincipal("server"))
+ _, ep, err := server.Listen("tcp", "127.0.0.1:0", tsecurity.NewPrincipal("server"))
if err != nil {
t.Fatal(err)
}
client := InternalNew(naming.FixedRoutingID(0xcccccccc))
- if _, err = client.Dial(ep, newPrincipal("TestSessionTicketCacheClient")); err != nil {
+ if _, err = client.Dial(ep, tsecurity.NewPrincipal("TestSessionTicketCacheClient")); err != nil {
t.Fatalf("Dial(%q) failed: %v", ep, err)
}
@@ -404,7 +400,7 @@
// Have the server read from each flow and write to rchan.
rchan := make(chan string)
- ln, ep, err := server.Listen(protocol, "127.0.0.1:0")
+ ln, ep, err := server.Listen(protocol, "127.0.0.1:0", tsecurity.NewPrincipal("test"))
if err != nil {
t.Fatal(err)
}
@@ -492,7 +488,7 @@
// resolve IPv6 addresses.
// As of April 2014, https://developers.google.com/compute/docs/networking
// said that IPv6 is not yet supported.
- ln, ep, err := server.Listen("tcp4", "127.0.0.1:0")
+ ln, ep, err := server.Listen("tcp4", "127.0.0.1:0", tsecurity.NewPrincipal("test"))
if err != nil {
t.Fatal(err)
}
@@ -576,7 +572,7 @@
func runServer(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
server := InternalNew(naming.FixedRoutingID(0x55555555))
- _, ep, err := server.Listen(args[0], args[1])
+ _, ep, err := server.Listen(args[0], args[1], tsecurity.NewPrincipal("test"))
if err != nil {
fmt.Fprintln(stderr, err)
return err
@@ -623,12 +619,12 @@
}
ipc.RegisterProtocol("tn", dialer, listener)
- _, _, err := server.Listen("tnx", "127.0.0.1:0")
+ _, _, err := server.Listen("tnx", "127.0.0.1:0", tsecurity.NewPrincipal("test"))
if err == nil || !strings.Contains(err.Error(), "unknown network tnx") {
t.Fatal("expected error is missing (%v)", err)
}
- _, _, err = server.Listen("tn", "127.0.0.1:0")
+ _, _, err = server.Listen("tn", "127.0.0.1:0", tsecurity.NewPrincipal("test"))
if err == nil || !strings.Contains(err.Error(), "tn.Listen") {
t.Fatal("expected error is missing (%v)", err)
}
@@ -642,7 +638,7 @@
t.Errorf("got %t, want %t", got, want)
}
- _, ep, err := server.Listen("tn", "127.0.0.1:0")
+ _, ep, err := server.Listen("tn", "127.0.0.1:0", tsecurity.NewPrincipal("test"))
if err != nil {
t.Errorf("unexpected error %s", err)
}
@@ -655,45 +651,49 @@
func TestBlessingNamesInEndpoint(t *testing.T) {
var (
- p = newPrincipal("default")
- b1, _ = p.Principal.BlessSelf("dev.v.io/users/foo@bar.com/devices/desktop/app/myapp")
- b2, _ = p.Principal.BlessSelf("otherblessing")
+ p = tsecurity.NewPrincipal("default")
+ b1, _ = p.BlessSelf("dev.v.io/users/foo@bar.com/devices/desktop/app/myapp")
+ b2, _ = p.BlessSelf("otherblessing")
b, _ = security.UnionOfBlessings(b1, b2)
bopt = options.ServerBlessings{b}
server = InternalNew(naming.FixedRoutingID(0x1))
tests = []struct {
+ principal security.Principal
opts []stream.ListenerOpt
blessings []string
err bool
}{
{
// Use the default blessings when only a principal is provided
- opts: []stream.ListenerOpt{p},
+ principal: p,
blessings: []string{"default"},
},
{
// Respect options.ServerBlessings if provided
- opts: []stream.ListenerOpt{p, bopt},
+ principal: p,
+ opts: []stream.ListenerOpt{bopt},
blessings: []string{"dev.v.io/users/foo@bar.com/devices/desktop/app/myapp", "otherblessing"},
},
{
- // It is an error to provide options.ServerBlessings without vc.LocalPrincipal
- opts: []stream.ListenerOpt{bopt},
- err: true,
+ // It is an error to provide options.ServerBlessings without passing a principal
+ principal: nil,
+ opts: []stream.ListenerOpt{bopt},
+ err: true,
},
{
- // It is an error to provide inconsistent options.ServerBlessings and vc.LocalPrincipal
- opts: []stream.ListenerOpt{newPrincipal("random"), bopt},
- err: true,
+ // It is an error to provide inconsistent options.ServerBlessings and principal
+ principal: tsecurity.NewPrincipal("random"),
+ opts: []stream.ListenerOpt{bopt},
+ err: true,
},
}
)
// p must recognize its own blessings!
p.AddToRoots(bopt.Blessings)
for idx, test := range tests {
- ln, ep, err := server.Listen("tcp", "127.0.0.1:0", test.opts...)
+ ln, ep, err := server.Listen("tcp", "127.0.0.1:0", test.principal, test.opts...)
if (err != nil) != test.err {
t.Errorf("test #%d: Got error %v, wanted error: %v", idx, err, test.err)
}
diff --git a/profiles/internal/ipc/stream/model.go b/profiles/internal/ipc/stream/model.go
index 630fe1e..ddbc8e5 100644
--- a/profiles/internal/ipc/stream/model.go
+++ b/profiles/internal/ipc/stream/model.go
@@ -111,14 +111,20 @@
// with the provided network address.
//
// For example:
- // ln, ep, err := Listen("tcp", ":0")
+ // ln, ep, err := Listen("tcp", ":0", principal)
// for {
// flow, err := ln.Accept()
// // process flow
// }
// can be used to accept Flows initiated by remote processes to the endpoint
// identified by the returned Endpoint.
- Listen(protocol, address string, opts ...ListenerOpt) (Listener, naming.Endpoint, error)
+ //
+ // principal is used during authentication. If principal is nil, then the Listener
+ // expects to be used for unauthenticated, unencrypted communication.
+ // If no Blessings are provided via v23.options.ServerBlessings, the principal's
+ // default Blessings will be presented to the Client.
+ // TODO(suharshs): Pass Blessings in explicitly instead of relying on ServerBlessings opt.
+ Listen(protocol, address string, principal security.Principal, opts ...ListenerOpt) (Listener, naming.Endpoint, error)
// Dial creates a VC to the provided remote endpoint.
Dial(remote naming.Endpoint, opts ...VCOpt) (VC, error)
diff --git a/profiles/internal/ipc/stream/proxy/proxy.go b/profiles/internal/ipc/stream/proxy/proxy.go
index f3c50ac..8189083 100644
--- a/profiles/internal/ipc/stream/proxy/proxy.go
+++ b/profiles/internal/ipc/stream/proxy/proxy.go
@@ -563,7 +563,7 @@
p.proxy.routeCounters(p, m.Counters)
if vcObj != nil {
server := &server{Process: p, VC: vcObj}
- go p.proxy.runServer(server, vcObj.HandshakeAcceptedVC(vc.LocalPrincipal{p.proxy.principal}))
+ go p.proxy.runServer(server, vcObj.HandshakeAcceptedVC(p.proxy.principal))
}
break
}
diff --git a/profiles/internal/ipc/stream/proxy/proxy_test.go b/profiles/internal/ipc/stream/proxy/proxy_test.go
index 7cb86e0..cc6d906 100644
--- a/profiles/internal/ipc/stream/proxy/proxy_test.go
+++ b/profiles/internal/ipc/stream/proxy/proxy_test.go
@@ -15,13 +15,13 @@
_ "v.io/x/ref/profiles"
"v.io/x/ref/profiles/internal/ipc/stream/manager"
"v.io/x/ref/profiles/internal/ipc/stream/proxy"
- "v.io/x/ref/profiles/internal/ipc/stream/vc"
)
//go:generate v23 test generate
func TestProxy(t *testing.T) {
- shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), nil, "tcp", "127.0.0.1:0", "")
+ pproxy := tsecurity.NewPrincipal("proxy")
+ shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), pproxy, "tcp", "127.0.0.1:0", "")
if err != nil {
t.Fatal(err)
}
@@ -32,7 +32,7 @@
defer server1.Shutdown()
// Setup a stream.Listener that will accept VCs and Flows routed
// through the proxy.
- ln1, ep1, err := server1.Listen(proxyEp.Network(), proxyEp.String())
+ ln1, ep1, err := server1.Listen(proxyEp.Network(), proxyEp.String(), tsecurity.NewPrincipal("test"))
if err != nil {
t.Fatal(err)
}
@@ -43,7 +43,7 @@
defer server2.Shutdown()
// Setup a stream.Listener that will accept VCs and Flows routed
// through the proxy.
- ln2, ep2, err := server2.Listen(proxyEp.Network(), proxyEp.String())
+ ln2, ep2, err := server2.Listen(proxyEp.Network(), proxyEp.String(), tsecurity.NewPrincipal("test"))
if err != nil {
t.Fatal(err)
}
@@ -81,7 +81,8 @@
}
func TestDuplicateRoutingID(t *testing.T) {
- shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), nil, "tcp", "127.0.0.1:0", "")
+ pproxy := tsecurity.NewPrincipal("proxy")
+ shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), pproxy, "tcp", "127.0.0.1:0", "")
if err != nil {
t.Fatal(err)
}
@@ -95,13 +96,13 @@
defer server2.Shutdown()
// First server to claim serverRID should win.
- ln1, ep1, err := server1.Listen(proxyEp.Network(), proxyEp.String())
+ ln1, ep1, err := server1.Listen(proxyEp.Network(), proxyEp.String(), tsecurity.NewPrincipal("test"))
if err != nil {
t.Fatal(err)
}
defer ln1.Close()
- ln2, ep2, err := server2.Listen(proxyEp.Network(), proxyEp.String())
+ ln2, ep2, err := server2.Listen(proxyEp.Network(), proxyEp.String(), tsecurity.NewPrincipal("test"))
if pattern := "routing id 00000000000000005555555555555555 is already being proxied"; err == nil || !strings.Contains(err.Error(), pattern) {
t.Errorf("Got (%v, %v, %v) want error \"...%v\" (ep1:%v)", ln2, ep2, err, pattern, ep1)
}
@@ -151,7 +152,7 @@
server := manager.InternalNew(naming.FixedRoutingID(0x5555555555555555))
defer server.Shutdown()
- ln, ep, err := server.Listen(proxyEp.Network(), proxyEp.String(), vc.LocalPrincipal{pserver})
+ ln, ep, err := server.Listen(proxyEp.Network(), proxyEp.String(), pserver)
if err != nil {
t.Fatal(err)
}
@@ -183,7 +184,8 @@
}
func TestHostPort(t *testing.T) {
- shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), nil, "tcp", "127.0.0.1:0", "")
+ pproxy := tsecurity.NewPrincipal("proxy")
+ shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), pproxy, "tcp", "127.0.0.1:0", "")
if err != nil {
t.Fatal(err)
}
@@ -192,7 +194,7 @@
defer server.Shutdown()
addr := proxyEp.Addr().String()
port := addr[strings.LastIndex(addr, ":"):]
- ln, _, err := server.Listen("veyron", "127.0.0.1"+port)
+ ln, _, err := server.Listen("veyron", "127.0.0.1"+port, tsecurity.NewPrincipal("test"))
if err != nil {
t.Fatal(err)
}
@@ -200,7 +202,8 @@
}
func TestClientBecomesServer(t *testing.T) {
- shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), nil, "tcp", "127.0.0.1:0", "")
+ pproxy := tsecurity.NewPrincipal("proxy")
+ shutdown, proxyEp, err := proxy.InternalNew(naming.FixedRoutingID(0xbbbbbbbbbbbbbbbb), pproxy, "tcp", "127.0.0.1:0", "")
if err != nil {
t.Fatal(err)
}
@@ -212,7 +215,7 @@
defer client1.Shutdown()
defer client2.Shutdown()
- lnS, epS, err := server.Listen(proxyEp.Network(), proxyEp.String())
+ lnS, epS, err := server.Listen(proxyEp.Network(), proxyEp.String(), tsecurity.NewPrincipal("test"))
if err != nil {
t.Fatal(err)
}
@@ -233,7 +236,7 @@
}
// Now client1 becomes a server
- lnC, epC, err := client1.Listen(proxyEp.Network(), proxyEp.String())
+ lnC, epC, err := client1.Listen(proxyEp.Network(), proxyEp.String(), tsecurity.NewPrincipal("test"))
if err != nil {
t.Fatal(err)
}
diff --git a/profiles/internal/ipc/stream/vc/vc.go b/profiles/internal/ipc/stream/vc/vc.go
index c994b3a..bbad982 100644
--- a/profiles/internal/ipc/stream/vc/vc.go
+++ b/profiles/internal/ipc/stream/vc/vc.go
@@ -142,10 +142,8 @@
// when establishing virtual circuits.
type LocalPrincipal struct{ security.Principal }
-func (LocalPrincipal) IPCStreamListenerOpt() {}
-func (LocalPrincipal) IPCStreamVCOpt() {}
-func (LocalPrincipal) IPCClientOpt() {}
-func (LocalPrincipal) IPCServerOpt() {}
+func (LocalPrincipal) IPCStreamVCOpt() {}
+func (LocalPrincipal) IPCClientOpt() {}
// DischargeClient is an interface for obtaining discharges for a set of third-party
// caveats.
@@ -504,14 +502,18 @@
// Since the handshaking process might involve several round trips, a bulk of the work
// is done asynchronously and the result of the handshake is written to the
// channel returned by this method.
-func (vc *VC) HandshakeAcceptedVC(opts ...stream.ListenerOpt) <-chan HandshakeResult {
+//
+// principal is server's used during authentication. If principal is nil, then the VC
+// expects to be used for unauthenticated, unencrypted communication.
+// If no Blessings are provided via v23.options.ServerBlessings, the principal's
+// default Blessings will be presented to the Client.
+func (vc *VC) HandshakeAcceptedVC(principal security.Principal, opts ...stream.ListenerOpt) <-chan HandshakeResult {
result := make(chan HandshakeResult, 1)
finish := func(ln stream.Listener, err error) chan HandshakeResult {
result <- HandshakeResult{ln, err}
return result
}
var (
- principal security.Principal
securityLevel options.VCSecurityLevel
dischargeClient DischargeClient
lBlessings security.Blessings
@@ -522,8 +524,6 @@
switch v := o.(type) {
case DischargeClient:
dischargeClient = v
- case LocalPrincipal:
- principal = v.Principal
case options.VCSecurityLevel:
securityLevel = v
case options.ServerBlessings:
@@ -543,7 +543,7 @@
switch securityLevel {
case options.VCSecurityConfidential:
if principal == nil {
- principal = AnonymousPrincipal
+ return finish(nil, fmt.Errorf("principal required for VCSecurityConfidential"))
}
if lBlessings.IsZero() {
lBlessings = principal.BlessingStore().Default()
diff --git a/profiles/internal/ipc/stream/vc/vc_test.go b/profiles/internal/ipc/stream/vc/vc_test.go
index 9a9ff00..40d549d 100644
--- a/profiles/internal/ipc/stream/vc/vc_test.go
+++ b/profiles/internal/ipc/stream/vc/vc_test.go
@@ -472,7 +472,7 @@
go clientH.pipeLoop(serverH.VC)
go serverH.pipeLoop(clientH.VC)
- lopts := []stream.ListenerOpt{vc.LocalPrincipal{server}, security}
+ lopts := []stream.ListenerOpt{security}
vcopts := []stream.VCOpt{vc.LocalPrincipal{client}, security}
if dischargeClient != nil {
@@ -482,7 +482,7 @@
vcopts = append(vcopts, auth)
}
- c := serverH.VC.HandshakeAcceptedVC(lopts...)
+ c := serverH.VC.HandshakeAcceptedVC(server, lopts...)
if err := clientH.VC.HandshakeDialedVC(vcopts...); err != nil {
go func() { <-c }()
return nil, nil, err
diff --git a/profiles/internal/ipc/stream/vif/auth.go b/profiles/internal/ipc/stream/vif/auth.go
index 62914ab..25a5414 100644
--- a/profiles/internal/ipc/stream/vif/auth.go
+++ b/profiles/internal/ipc/stream/vif/auth.go
@@ -187,15 +187,17 @@
return c, nil
}
-// serverAuthOptions extracts the Principal from the options list.
-func serverAuthOptions(lopts []stream.ListenerOpt) (principal security.Principal, lBlessings security.Blessings, dischargeClient vc.DischargeClient, err error) {
- var securityLevel options.VCSecurityLevel
+// serverAuthOptions returns credentials for VIF authentication, based on the provided principal and options list.
+func serverAuthOptions(principal security.Principal, lopts []stream.ListenerOpt) (security.Principal, security.Blessings, vc.DischargeClient, error) {
+ var (
+ securityLevel options.VCSecurityLevel
+ dischargeClient vc.DischargeClient
+ lBlessings security.Blessings
+ )
for _, o := range lopts {
switch v := o.(type) {
case vc.DischargeClient:
dischargeClient = v
- case vc.LocalPrincipal:
- principal = v.Principal
case options.VCSecurityLevel:
securityLevel = v
case options.ServerBlessings:
@@ -204,18 +206,15 @@
}
switch securityLevel {
case options.VCSecurityConfidential:
- if principal == nil {
- principal = vc.AnonymousPrincipal
- }
if lBlessings.IsZero() {
lBlessings = principal.BlessingStore().Default()
}
+ return principal, lBlessings, dischargeClient, nil
case options.VCSecurityNone:
- principal = nil
+ return nil, security.Blessings{}, nil, nil
default:
- err = fmt.Errorf("unrecognized VC security level: %v", securityLevel)
+ return nil, security.Blessings{}, nil, fmt.Errorf("unrecognized VC security level: %v", securityLevel)
}
- return
}
// makeHopSetup constructs the options that this process can support.
diff --git a/profiles/internal/ipc/stream/vif/set_test.go b/profiles/internal/ipc/stream/vif/set_test.go
index 0a45f12..e00a51b 100644
--- a/profiles/internal/ipc/stream/vif/set_test.go
+++ b/profiles/internal/ipc/stream/vif/set_test.go
@@ -12,6 +12,7 @@
"v.io/v23/ipc"
"v.io/v23/naming"
+ tsecurity "v.io/x/ref/lib/testutil/security"
_ "v.io/x/ref/profiles"
"v.io/x/ref/profiles/internal/ipc/stream/vif"
)
@@ -59,7 +60,7 @@
func newVIF(c, s net.Conn) (*vif.VIF, *vif.VIF, error) {
done := make(chan *vif.VIF)
go func() {
- vf, err := vif.InternalNewAcceptedVIF(s, naming.FixedRoutingID(0x5), nil)
+ vf, err := vif.InternalNewAcceptedVIF(s, naming.FixedRoutingID(0x5), tsecurity.NewPrincipal("test"), nil)
if err != nil {
panic(err)
}
diff --git a/profiles/internal/ipc/stream/vif/vif.go b/profiles/internal/ipc/stream/vif/vif.go
index dd2c703..97bbe65 100644
--- a/profiles/internal/ipc/stream/vif/vif.go
+++ b/profiles/internal/ipc/stream/vif/vif.go
@@ -65,6 +65,7 @@
muListen sync.Mutex
acceptor *upcqueue.T // GUARDED_BY(muListen)
listenerOpts []stream.ListenerOpt // GUARDED_BY(muListen)
+ principal security.Principal
muNextVCI sync.Mutex
nextVCI id.VC
@@ -153,7 +154,7 @@
if err != nil {
return nil, err
}
- return internalNew(conn, pool, reader, rid, id.VC(vc.NumReservedVCs), versions, nil, nil, c)
+ return internalNew(conn, pool, reader, rid, id.VC(vc.NumReservedVCs), versions, principal, nil, nil, c)
}
// InternalNewAcceptedVIF creates a new virtual interface over the provided
@@ -166,13 +167,13 @@
// As the name suggests, this method is intended for use only within packages
// placed inside veyron/profiles/internal. Code outside the
// veyron/profiles/internal/* packages should never call this method.
-func InternalNewAcceptedVIF(conn net.Conn, rid naming.RoutingID, versions *version.Range, lopts ...stream.ListenerOpt) (*VIF, error) {
+func InternalNewAcceptedVIF(conn net.Conn, rid naming.RoutingID, principal security.Principal, versions *version.Range, lopts ...stream.ListenerOpt) (*VIF, error) {
pool := iobuf.NewPool(0)
reader := iobuf.NewReader(pool, conn)
- return internalNew(conn, pool, reader, rid, id.VC(vc.NumReservedVCs)+1, versions, upcqueue.New(), lopts, &crypto.NullControlCipher{})
+ return internalNew(conn, pool, reader, rid, id.VC(vc.NumReservedVCs)+1, versions, principal, upcqueue.New(), lopts, &crypto.NullControlCipher{})
}
-func internalNew(conn net.Conn, pool *iobuf.Pool, reader *iobuf.Reader, rid naming.RoutingID, initialVCI id.VC, versions *version.Range, acceptor *upcqueue.T, listenerOpts []stream.ListenerOpt, c crypto.ControlCipher) (*VIF, error) {
+func internalNew(conn net.Conn, pool *iobuf.Pool, reader *iobuf.Reader, rid naming.RoutingID, initialVCI id.VC, versions *version.Range, principal security.Principal, acceptor *upcqueue.T, listenerOpts []stream.ListenerOpt, c crypto.ControlCipher) (*VIF, error) {
var (
// Choose IDs that will not conflict with any other (VC, Flow)
// pairs. VCI 0 is never used by the application (it is
@@ -210,6 +211,7 @@
vcMap: newVCMap(),
acceptor: acceptor,
listenerOpts: listenerOpts,
+ principal: principal,
localEP: localEP(conn, rid, versions),
nextVCI: initialVCI,
outgoing: outgoing,
@@ -448,7 +450,7 @@
})
return nil
}
- go vif.acceptFlowsLoop(vc, vc.HandshakeAcceptedVC(lopts...))
+ go vif.acceptFlowsLoop(vc, vc.HandshakeAcceptedVC(vif.principal, lopts...))
case *message.SetupVC:
// TODO(ashankar,mattr): Handle this! See comment about SetupVC
// in vif.Dial
@@ -491,7 +493,7 @@
return errAlreadySetup
}
vif.muListen.Lock()
- principal, lBlessings, dischargeClient, err := serverAuthOptions(vif.listenerOpts)
+ principal, lBlessings, dischargeClient, err := serverAuthOptions(vif.principal, vif.listenerOpts)
vif.muListen.Unlock()
if err != nil {
return errVersionNegotiationFailed
diff --git a/profiles/internal/ipc/stream/vif/vif_test.go b/profiles/internal/ipc/stream/vif/vif_test.go
index 132aa11..959ae0b 100644
--- a/profiles/internal/ipc/stream/vif/vif_test.go
+++ b/profiles/internal/ipc/stream/vif/vif_test.go
@@ -389,7 +389,7 @@
}
result <- client
}()
- server, err := vif.InternalNewAcceptedVIF(c2, naming.FixedRoutingID(0x5), nil)
+ server, err := vif.InternalNewAcceptedVIF(c2, naming.FixedRoutingID(0x5), tsecurity.NewPrincipal("test"), nil)
if err != nil {
t.Fatal(err)
}
@@ -500,7 +500,7 @@
cl <- c
}
}()
- s, err := vif.InternalNewAcceptedVIF(c2, naming.FixedRoutingID(0x5), serverVersions, newPrincipal("server"))
+ s, err := vif.InternalNewAcceptedVIF(c2, naming.FixedRoutingID(0x5), tsecurity.NewPrincipal("server"), serverVersions)
c, ok := <-cl
if err != nil {
verr = err
diff --git a/profiles/internal/ipc/v23_test.go b/profiles/internal/ipc/v23_test.go
index e416039..a5def16 100644
--- a/profiles/internal/ipc/v23_test.go
+++ b/profiles/internal/ipc/v23_test.go
@@ -20,7 +20,7 @@
func TestMain(m *testing.M) {
testutil.Init()
- if modules.IsModulesProcess() {
+ if modules.IsModulesChildProcess() {
if err := modules.Dispatch(); err != nil {
fmt.Fprintf(os.Stderr, "modules.Dispatch failed: %v\n", err)
os.Exit(1)
diff --git a/profiles/internal/rt/ipc_test.go b/profiles/internal/rt/ipc_test.go
index eaa9615..36ec91f 100644
--- a/profiles/internal/rt/ipc_test.go
+++ b/profiles/internal/rt/ipc_test.go
@@ -239,7 +239,9 @@
// no proxies were started. Anyway, just to express the
// intent...
for _, ep := range status.Endpoints {
- if got := ep.BlessingNames(); !reflect.DeepEqual(got, want) {
+ got := ep.BlessingNames()
+ sort.Strings(got)
+ if !reflect.DeepEqual(got, want) {
t.Errorf("test #%d: endpoint=%q: Got blessings %v, want %v", idx, ep, got, want)
}
}
diff --git a/profiles/internal/rt/mgmt.go b/profiles/internal/rt/mgmt.go
index 34f1b4d..935120c 100644
--- a/profiles/internal/rt/mgmt.go
+++ b/profiles/internal/rt/mgmt.go
@@ -24,10 +24,14 @@
} else if err != nil {
return err
}
-
parentName, err := handle.Config.Get(mgmt.ParentNameConfigKey)
if err != nil {
- return nil
+ // If the ParentNameConfigKey is not set, then this process has
+ // not been started by the device manager, but the parent process
+ // is still a Vanadium process using the exec library so we
+ // call SetReady to let it know that this child process has
+ // successfully started.
+ return handle.SetReady()
}
listenSpec, err := getListenSpec(handle)
if err != nil {
@@ -58,7 +62,6 @@
server.Stop()
return err
}
-
return handle.SetReady()
}
diff --git a/profiles/internal/rt/runtime.go b/profiles/internal/rt/runtime.go
index d065cb0..8eba35a 100644
--- a/profiles/internal/rt/runtime.go
+++ b/profiles/internal/rt/runtime.go
@@ -232,7 +232,6 @@
client, _ := ctx.Value(clientKey).(ipc.Client)
otherOpts := append([]ipc.ServerOpt{}, opts...)
- otherOpts = append(otherOpts, vc.LocalPrincipal{principal})
if reserved, ok := ctx.Value(reservedNameKey).(*reservedNameDispatcher); ok {
otherOpts = append(otherOpts, iipc.ReservedNameDispatcher{reserved.dispatcher})
otherOpts = append(otherOpts, reserved.opts...)
@@ -244,7 +243,7 @@
if !hasServerBlessingsOpt(opts) && principal != nil {
otherOpts = append(otherOpts, options.ServerBlessings{principal.BlessingStore().Default()})
}
- server, err := iipc.InternalNewServer(ctx, sm, ns, r.GetClient(ctx), otherOpts...)
+ server, err := iipc.InternalNewServer(ctx, sm, ns, r.GetClient(ctx), principal, otherOpts...)
if err != nil {
return nil, err
}
diff --git a/profiles/internal/rt/v23_test.go b/profiles/internal/rt/v23_test.go
index 4ca1965..f1d3bd1 100644
--- a/profiles/internal/rt/v23_test.go
+++ b/profiles/internal/rt/v23_test.go
@@ -35,7 +35,7 @@
func TestMain(m *testing.M) {
testutil.Init()
- if modules.IsModulesProcess() {
+ if modules.IsModulesChildProcess() {
if err := modules.Dispatch(); err != nil {
fmt.Fprintf(os.Stderr, "modules.Dispatch failed: %v\n", err)
os.Exit(1)
diff --git a/profiles/internal/vtrace/vtrace_test.go b/profiles/internal/vtrace/vtrace_test.go
index 14afa92..834af71 100644
--- a/profiles/internal/vtrace/vtrace_test.go
+++ b/profiles/internal/vtrace/vtrace_test.go
@@ -15,6 +15,7 @@
"v.io/x/lib/vlog"
"v.io/x/ref/lib/testutil"
+ tsecurity "v.io/x/ref/lib/testutil/security"
_ "v.io/x/ref/profiles"
iipc "v.io/x/ref/profiles/internal/ipc"
"v.io/x/ref/profiles/internal/ipc/stream"
@@ -90,7 +91,7 @@
if err != nil {
return nil, err
}
- s, err := iipc.InternalNewServer(ctx, sm, ns, client)
+ s, err := iipc.InternalNewServer(ctx, sm, ns, client, tsecurity.NewPrincipal("test"))
if err != nil {
return nil, err
}
diff --git a/security/agent/v23_test.go b/security/agent/v23_test.go
index bdcc989..68dcf3c 100644
--- a/security/agent/v23_test.go
+++ b/security/agent/v23_test.go
@@ -20,7 +20,7 @@
func TestMain(m *testing.M) {
testutil.Init()
- if modules.IsModulesProcess() {
+ if modules.IsModulesChildProcess() {
if err := modules.Dispatch(); err != nil {
fmt.Fprintf(os.Stderr, "modules.Dispatch failed: %v\n", err)
os.Exit(1)
diff --git a/security/flag/v23_internal_test.go b/security/flag/v23_internal_test.go
index 9a2e9f0..1f17192 100644
--- a/security/flag/v23_internal_test.go
+++ b/security/flag/v23_internal_test.go
@@ -19,7 +19,7 @@
func TestMain(m *testing.M) {
testutil.Init()
- if modules.IsModulesProcess() {
+ if modules.IsModulesChildProcess() {
if err := modules.Dispatch(); err != nil {
fmt.Fprintf(os.Stderr, "modules.Dispatch failed: %v\n", err)
os.Exit(1)
diff --git a/services/identity/oauth/googleoauth.go b/services/identity/oauth/googleoauth.go
index 5feb74e..a543d5e 100644
--- a/services/identity/oauth/googleoauth.go
+++ b/services/identity/oauth/googleoauth.go
@@ -79,7 +79,9 @@
if err := json.NewDecoder(tinfo.Body).Decode(>oken); err != nil {
return "", fmt.Errorf("invalid JSON response from Google's tokeninfo API: %v", err)
}
- if !gtoken.VerifiedEmail {
+ // We check both "verified_email" and "email_verified" here because the token response sometimes
+ // contains one and sometimes contains the other.
+ if !gtoken.VerifiedEmail && !gtoken.EmailVerified {
return "", fmt.Errorf("email not verified: %#v", gtoken)
}
if gtoken.Issuer != "accounts.google.com" {
@@ -116,6 +118,7 @@
ExpiresIn int64 `json:"expires_in"`
Email string `json:"email"`
VerifiedEmail bool `json:"verified_email"`
+ EmailVerified bool `json:"email_verified"`
AccessType string `json:"access_type"`
}
if err := json.NewDecoder(tokeninfo.Body).Decode(&token); err != nil {
@@ -134,7 +137,9 @@
vlog.Infof("Got access token [%+v], wanted one of client ids %v", token, accessTokenClients)
return "", "", fmt.Errorf("token not meant for this purpose, confused deputy? https://developers.google.com/accounts/docs/OAuth2UserAgent#validatetoken")
}
- if !token.VerifiedEmail {
+ // We check both "verified_email" and "email_verified" here because the token response sometimes
+ // contains one and sometimes contains the other.
+ if !token.VerifiedEmail && !token.EmailVerified {
return "", "", fmt.Errorf("email not verified")
}
return token.Email, client.Name, nil
@@ -184,4 +189,5 @@
IssuedAt int64 `json:"issued_at"`
Email string `json:"email"`
VerifiedEmail bool `json:"verified_email"`
+ EmailVerified bool `json:"email_verified"`
}
diff --git a/services/mgmt/application/impl/acl_test.go b/services/mgmt/application/impl/acl_test.go
index 9715785..ad9c964 100644
--- a/services/mgmt/application/impl/acl_test.go
+++ b/services/mgmt/application/impl/acl_test.go
@@ -85,8 +85,8 @@
storedir, cleanup := mgmttest.SetupRootDir(t, "application")
defer cleanup()
- _, nms := mgmttest.RunShellCommand(t, sh, nil, repoCmd, "repo", storedir)
- pid := mgmttest.ReadPID(t, nms)
+ nmh := mgmttest.RunCommand(t, sh, nil, repoCmd, "repo", storedir)
+ pid := mgmttest.ReadPID(t, nmh)
defer syscall.Kill(pid, syscall.SIGINT)
otherCtx, err := v23.SetPrincipal(ctx, tsecurity.NewPrincipal())
@@ -222,8 +222,8 @@
t.Fatal(err)
}
- _, nms := mgmttest.RunShellCommand(t, sh, nil, repoCmd, "repo", storedir)
- pid := mgmttest.ReadPID(t, nms)
+ nmh := mgmttest.RunCommand(t, sh, nil, repoCmd, "repo", storedir)
+ pid := mgmttest.ReadPID(t, nmh)
defer syscall.Kill(pid, syscall.SIGINT)
// Create example envelope.
diff --git a/services/mgmt/application/impl/v23_test.go b/services/mgmt/application/impl/v23_test.go
index ad4161e..125eb9e 100644
--- a/services/mgmt/application/impl/v23_test.go
+++ b/services/mgmt/application/impl/v23_test.go
@@ -19,7 +19,7 @@
func TestMain(m *testing.M) {
testutil.Init()
- if modules.IsModulesProcess() {
+ if modules.IsModulesChildProcess() {
if err := modules.Dispatch(); err != nil {
fmt.Fprintf(os.Stderr, "modules.Dispatch failed: %v\n", err)
os.Exit(1)
diff --git a/services/mgmt/binary/impl/acl_test.go b/services/mgmt/binary/impl/acl_test.go
index 281a594..62393b5 100644
--- a/services/mgmt/binary/impl/acl_test.go
+++ b/services/mgmt/binary/impl/acl_test.go
@@ -97,8 +97,8 @@
defer cleanup()
prepDirectory(t, storedir)
- _, nms := mgmttest.RunShellCommand(t, sh, nil, binaryCmd, "bini", storedir)
- pid := mgmttest.ReadPID(t, nms)
+ nmh := mgmttest.RunCommand(t, sh, nil, binaryCmd, "bini", storedir)
+ pid := mgmttest.ReadPID(t, nmh)
defer syscall.Kill(pid, syscall.SIGINT)
vlog.VI(2).Infof("Self uploads a shared and private binary.")
@@ -154,8 +154,8 @@
t.Fatalf("SetPrincipal() failed: %v", err)
}
- _, nms := mgmttest.RunShellCommand(t, sh, nil, binaryCmd, "bini", storedir)
- pid := mgmttest.ReadPID(t, nms)
+ nmh := mgmttest.RunCommand(t, sh, nil, binaryCmd, "bini", storedir)
+ pid := mgmttest.ReadPID(t, nmh)
defer syscall.Kill(pid, syscall.SIGINT)
vlog.VI(2).Infof("Self uploads a shared and private binary.")
@@ -434,8 +434,8 @@
t.Fatalf("otherPrincipal.AddToRoots() failed: %v", err)
}
- _, nms := mgmttest.RunShellCommand(t, sh, nil, binaryCmd, "bini", storedir)
- pid := mgmttest.ReadPID(t, nms)
+ nmh := mgmttest.RunCommand(t, sh, nil, binaryCmd, "bini", storedir)
+ pid := mgmttest.ReadPID(t, nmh)
defer syscall.Kill(pid, syscall.SIGINT)
acl, tag, err := b("bini").GetACL(selfCtx)
diff --git a/services/mgmt/binary/impl/v23_test.go b/services/mgmt/binary/impl/v23_test.go
index 9c0642e..a55f54f 100644
--- a/services/mgmt/binary/impl/v23_test.go
+++ b/services/mgmt/binary/impl/v23_test.go
@@ -19,7 +19,7 @@
func TestMain(m *testing.M) {
testutil.Init()
- if modules.IsModulesProcess() {
+ if modules.IsModulesChildProcess() {
if err := modules.Dispatch(); err != nil {
fmt.Fprintf(os.Stderr, "modules.Dispatch failed: %v\n", err)
os.Exit(1)
diff --git a/services/mgmt/device/impl/debug_acls_test.go b/services/mgmt/device/impl/debug_acls_test.go
new file mode 100644
index 0000000..f6811c2
--- /dev/null
+++ b/services/mgmt/device/impl/debug_acls_test.go
@@ -0,0 +1,112 @@
+package impl_test
+
+import (
+ "syscall"
+ "testing"
+
+ "v.io/v23/context"
+ "v.io/v23/security"
+ "v.io/v23/services/security/access"
+ "v.io/v23/vdl"
+
+ mgmttest "v.io/x/ref/services/mgmt/lib/testutil"
+)
+
+func updateACL(t *testing.T, ctx *context.T, blessing, right string, name ...string) {
+ acl, etag, err := appStub(name...).GetACL(ctx)
+ if err != nil {
+ t.Fatalf("GetACL(%v) failed %v", name, err)
+ }
+ acl.Add(security.BlessingPattern(blessing), right)
+ if err = appStub(name...).SetACL(ctx, acl, etag); err != nil {
+ t.Fatalf("SetACL(%v, %v, %v) failed: %v", name, blessing, right, err)
+ }
+}
+
+func TestDebugACLPropagation(t *testing.T) {
+ cleanup, ctx, sh, envelope, root, helperPath, idp := startupHelper(t)
+ defer cleanup()
+
+ // Set up the device manager.
+ dmh := mgmttest.RunCommand(t, sh, nil, deviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+ mgmttest.ReadPID(t, dmh)
+ claimDevice(t, ctx, "dm", "mydevice", noPairingToken)
+
+ // Create the local server that the app uses to let us know it's ready.
+ pingCh, cleanup := setupPingServer(t, ctx)
+ defer cleanup()
+ resolve(t, ctx, "pingserver", 1)
+
+ // Make some users.
+ selfCtx := ctx
+ bobCtx := ctxWithNewPrincipal(t, selfCtx, idp, "bob")
+ hjCtx := ctxWithNewPrincipal(t, selfCtx, idp, "hackerjoe")
+ aliceCtx := ctxWithNewPrincipal(t, selfCtx, idp, "alice")
+
+ // TODO(rjkroege): Set ACLs here that conflict with the one provided by the device
+ // manager and show that the one set here is overridden.
+ // Create the envelope for the first version of the app.
+ *envelope = envelopeFromShell(sh, nil, appCmd, "google naps", "appV1")
+
+ // Install the app.
+ appID := installApp(t, ctx)
+
+ // Give bob rights to start an app.
+ updateACL(t, selfCtx, "root/bob/$", string(access.Read), appID)
+
+ // Bob starts an instance of the app.
+ bobApp := startApp(t, bobCtx, appID)
+ verifyPingArgs(t, pingCh, userName(t), "default", "")
+
+ // Bob permits Alice to read from his app.
+ updateACL(t, bobCtx, "root/alice/$", string(access.Read), appID, bobApp)
+
+ // Confirm that self can access stats name (i.e. apps proxied from
+ // the __debug space of the app.
+ // TODO(rjkroege): validate each of the services under __debug.
+ v, err := statsStub(appID, bobApp, "stats/system/pid").Value(ctx)
+ if err != nil {
+ t.Fatalf("Value() failed: %v\n", err)
+ }
+ var pid int
+ if err := vdl.Convert(&pid, v); err != nil {
+ t.Fatalf("pid returned from stats interface is not an int: %v", err)
+ }
+
+ // Bob has an issue with his app and tries to use the debug output to figure it out.
+ // TODO(rjkroege): Invert this test when ACLs are correctly propagated.
+ if _, err = statsStub(appID, bobApp, "stats/system/pid").Value(bobCtx); err == nil {
+ t.Fatalf("This ought not to work yet! Something is wrong.")
+ }
+
+ // But Bob can't figure it out and hopes that hackerjoe can debug it.
+ updateACL(t, bobCtx, "root/hackerjoe/$", string(access.Debug), appID, bobApp)
+
+ // But the device manager doesn't support this so hackerjoe can't help.
+ // TODO(rjkroege): Invert this test when ACLs are propagated..
+ if _, err = statsStub(appID, bobApp, "stats/system/pid").Value(hjCtx); err == nil {
+ t.Fatalf("This ought not to work yet! Something is wrong.")
+ }
+
+ // Alice might be able to help but Bob didn't give Alice access to the debug ACLs.
+ if _, err = statsStub(appID, bobApp, "stats/system/pid").Value(hjCtx); err == nil {
+ t.Fatalf("Alice could wrongly access the stats without perms.")
+ }
+
+ // Bob changes the permissions so that Alice can help debug too.
+ updateACL(t, bobCtx, "root/alice/$", string(access.Debug), appID, bobApp)
+
+ // But the device manager doesn't support this so Alice can't help either.
+ // TODO(rjkroege): Invert this test when ACLs are propagated..
+ if _, err = statsStub(appID, bobApp, "stats/system/pid").Value(aliceCtx); err == nil {
+ t.Fatalf("This ought not to work yet! Something is wrong.")
+ }
+
+ // Bob is glum so stops his app.
+ stopApp(t, bobCtx, appID, bobApp)
+
+ // Cleanly shut down the device manager.
+ syscall.Kill(dmh.Pid(), syscall.SIGINT)
+ dmh.Expect("dm terminated")
+ dmh.ExpectEOF()
+}
diff --git a/services/mgmt/device/impl/impl_test.go b/services/mgmt/device/impl/impl_test.go
index 4302dec..0aa8c45 100644
--- a/services/mgmt/device/impl/impl_test.go
+++ b/services/mgmt/device/impl/impl_test.go
@@ -58,9 +58,11 @@
suidhelper "v.io/x/ref/services/mgmt/suidhelper/impl"
)
+//go:generate v23 test generate .
+
const (
// Modules command names.
- execScriptCmd = "execScriptCmd"
+ execScriptCmd = "execScript"
deviceManagerCmd = "deviceManager"
deviceManagerV10Cmd = "deviceManagerV10" // deviceManager with a different major version number
appCmd = "app"
@@ -82,28 +84,21 @@
// The installer sets this flag on the installed device manager, so we
// need to ensure it's defined.
flag.String("name", "", "")
-
- modules.RegisterChild(execScriptCmd, "", execScript)
- modules.RegisterChild(deviceManagerCmd, "", deviceManager)
- modules.RegisterChild(deviceManagerV10Cmd, "", deviceManagerV10)
- modules.RegisterChild(appCmd, "", app)
-
- if modules.IsModulesProcess() {
- return
- }
}
func TestMain(m *testing.M) {
testutil.Init()
+ isSuidHelper := len(os.Getenv("VEYRON_SUIDHELPER_TEST")) > 0
+ if modules.IsModulesChildProcess() && !isSuidHelper {
+ if err := modules.Dispatch(); err != nil {
+ fmt.Fprintf(os.Stderr, "modules.Dispatch failed: %v\n", err)
+ os.Exit(1)
+ }
+ return
+ }
os.Exit(m.Run())
}
-// TestHelperProcess is the entrypoint for the modules commands in a
-// a test subprocess.
-func TestHelperProcess(t *testing.T) {
- modules.DispatchInTest()
-}
-
// TestSuidHelper is testing boilerplate for suidhelper that does not
// create a runtime because the suidhelper is not a Veyron application.
func TestSuidHelper(t *testing.T) {
@@ -111,7 +106,6 @@
return
}
vlog.VI(1).Infof("TestSuidHelper starting")
-
if err := suidhelper.Run(os.Environ()); err != nil {
vlog.Fatalf("Failed to Run() setuidhelper: %v", err)
}
@@ -401,12 +395,12 @@
// demonstrates that the initial device manager could be started by hand
// as long as the right initial configuration is passed into the device
// manager implementation.
- dmh, dms := mgmttest.RunShellCommand(t, sh, dmPauseBeforeStopEnv, deviceManagerCmd, dmArgs...)
+ dmh := mgmttest.RunCommand(t, sh, dmPauseBeforeStopEnv, deviceManagerCmd, dmArgs...)
defer func() {
syscall.Kill(dmh.Pid(), syscall.SIGINT)
}()
- mgmttest.ReadPID(t, dms)
+ mgmttest.ReadPID(t, dmh)
// Brand new device manager must be claimed first.
claimDevice(t, ctx, "factoryDM", "mydevice", noPairingToken)
@@ -439,17 +433,17 @@
dmh.CloseStdin()
- dms.Expect("restart handler")
- dms.Expect("factoryDM terminated")
+ dmh.Expect("restart handler")
+ dmh.Expect("factoryDM terminated")
dmh.Shutdown(os.Stderr, os.Stderr)
// A successful update means the device manager has stopped itself. We
// relaunch it from the current link.
resolveExpectNotFound(t, ctx, "v2DM") // Ensure a clean slate.
- dmh, dms = mgmttest.RunShellCommand(t, sh, dmEnv, execScriptCmd, currLink)
+ dmh = mgmttest.RunCommand(t, sh, dmEnv, execScriptCmd, currLink)
- mgmttest.ReadPID(t, dms)
+ mgmttest.ReadPID(t, dmh)
resolve(t, ctx, "v2DM", 1) // Current link should have been launching v2.
// Try issuing an update without changing the envelope in the
@@ -480,8 +474,8 @@
t.Fatalf("current link didn't change")
}
- dms.Expect("restart handler")
- dms.Expect("v2DM terminated")
+ dmh.Expect("restart handler")
+ dmh.Expect("v2DM terminated")
dmh.Shutdown(os.Stderr, os.Stderr)
@@ -490,17 +484,17 @@
// Re-lanuch the device manager from current link. We instruct the
// device manager to pause before stopping its server, so that we can
// verify that a second revert fails while a revert is in progress.
- dmh, dms = mgmttest.RunShellCommand(t, sh, dmPauseBeforeStopEnv, execScriptCmd, currLink)
+ dmh = mgmttest.RunCommand(t, sh, dmPauseBeforeStopEnv, execScriptCmd, currLink)
- mgmttest.ReadPID(t, dms)
+ mgmttest.ReadPID(t, dmh)
resolve(t, ctx, "v3DM", 1) // Current link should have been launching v3.
// Revert the device manager to its previous version (v2).
revertDevice(t, ctx, "v3DM")
revertDeviceExpectError(t, ctx, "v3DM", impl.ErrOperationInProgress.ID) // Revert already in progress.
dmh.CloseStdin()
- dms.Expect("restart handler")
- dms.Expect("v3DM terminated")
+ dmh.Expect("restart handler")
+ dmh.Expect("v3DM terminated")
if evalLink() != scriptPathV2 {
t.Fatalf("current link was not reverted correctly")
}
@@ -508,14 +502,14 @@
resolveExpectNotFound(t, ctx, "v2DM") // Ensure a clean slate.
- dmh, dms = mgmttest.RunShellCommand(t, sh, dmEnv, execScriptCmd, currLink)
- mgmttest.ReadPID(t, dms)
+ dmh = mgmttest.RunCommand(t, sh, dmEnv, execScriptCmd, currLink)
+ mgmttest.ReadPID(t, dmh)
resolve(t, ctx, "v2DM", 1) // Current link should have been launching v2.
// Revert the device manager to its previous version (factory).
revertDevice(t, ctx, "v2DM")
- dms.Expect("restart handler")
- dms.Expect("v2DM terminated")
+ dmh.Expect("restart handler")
+ dmh.Expect("v2DM terminated")
if evalLink() != scriptPathFactory {
t.Fatalf("current link was not reverted correctly")
}
@@ -523,22 +517,22 @@
resolveExpectNotFound(t, ctx, "factoryDM") // Ensure a clean slate.
- dmh, dms = mgmttest.RunShellCommand(t, sh, dmEnv, execScriptCmd, currLink)
- mgmttest.ReadPID(t, dms)
+ dmh = mgmttest.RunCommand(t, sh, dmEnv, execScriptCmd, currLink)
+ mgmttest.ReadPID(t, dmh)
resolve(t, ctx, "factoryDM", 1) // Current link should have been launching factory version.
stopDevice(t, ctx, "factoryDM")
- dms.Expect("factoryDM terminated")
- dms.ExpectEOF()
+ dmh.Expect("factoryDM terminated")
+ dmh.ExpectEOF()
// Re-launch the device manager, to exercise the behavior of Suspend.
resolveExpectNotFound(t, ctx, "factoryDM") // Ensure a clean slate.
- dmh, dms = mgmttest.RunShellCommand(t, sh, dmEnv, execScriptCmd, currLink)
- mgmttest.ReadPID(t, dms)
+ dmh = mgmttest.RunCommand(t, sh, dmEnv, execScriptCmd, currLink)
+ mgmttest.ReadPID(t, dmh)
resolve(t, ctx, "factoryDM", 1)
suspendDevice(t, ctx, "factoryDM")
- dms.Expect("restart handler")
- dms.Expect("factoryDM terminated")
- dms.ExpectEOF()
+ dmh.Expect("restart handler")
+ dmh.Expect("factoryDM terminated")
+ dmh.ExpectEOF()
}
type pingServer chan<- pingArgs
@@ -647,8 +641,8 @@
// Set up the device manager. Since we won't do device manager updates,
// don't worry about its application envelope and current link.
- dmh, dms := mgmttest.RunShellCommand(t, sh, nil, deviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
- mgmttest.ReadPID(t, dms)
+ dmh := mgmttest.RunCommand(t, sh, nil, deviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+ mgmttest.ReadPID(t, dmh)
claimDevice(t, ctx, "dm", "mydevice", noPairingToken)
// Create the local server that the app uses to let us know it's ready.
@@ -840,8 +834,8 @@
// Cleanly shut down the device manager.
syscall.Kill(dmh.Pid(), syscall.SIGINT)
- dms.Expect("dm terminated")
- dms.ExpectEOF()
+ dmh.Expect("dm terminated")
+ dmh.ExpectEOF()
}
func startRealBinaryRepository(t *testing.T, ctx *context.T, von string) func() {
@@ -903,8 +897,8 @@
// Set up the device manager. Since we won't do device manager updates,
// don't worry about its application envelope and current link.
pairingToken := "abcxyz"
- _, dms := mgmttest.RunShellCommand(t, sh, nil, deviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link", pairingToken)
- pid := mgmttest.ReadPID(t, dms)
+ dmh := mgmttest.RunCommand(t, sh, nil, deviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link", pairingToken)
+ pid := mgmttest.ReadPID(t, dmh)
defer syscall.Kill(pid, syscall.SIGINT)
*envelope = envelopeFromShell(sh, nil, appCmd, "google naps", "trapp")
@@ -987,8 +981,8 @@
// Set up the device manager. Since we won't do device manager updates,
// don't worry about its application envelope and current link.
- _, dms := mgmttest.RunShellCommand(t, sh, nil, deviceManagerCmd, "dm", root, "unused_helper", "unused_app_repo_name", "unused_curr_link")
- pid := mgmttest.ReadPID(t, dms)
+ dmh := mgmttest.RunCommand(t, sh, nil, deviceManagerCmd, "dm", root, "unused_helper", "unused_app_repo_name", "unused_curr_link")
+ pid := mgmttest.ReadPID(t, dmh)
defer syscall.Kill(pid, syscall.SIGINT)
// Create an envelope for an app.
@@ -1135,8 +1129,8 @@
// Set up the device manager. Since we won't do device manager updates,
// don't worry about its application envelope and current link.
- _, dms := mgmttest.RunShellCommand(t, sh, nil, deviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
- pid := mgmttest.ReadPID(t, dms)
+ dmh := mgmttest.RunCommand(t, sh, nil, deviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+ pid := mgmttest.ReadPID(t, dmh)
defer syscall.Kill(pid, syscall.SIGINT)
// Create the local server that the app uses to let us know it's ready.
@@ -1341,8 +1335,8 @@
// Set up the device manager. Since we won't do device manager updates,
// don't worry about its application envelope and current link.
- _, dms := mgmttest.RunShellCommand(t, sh, nil, deviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
- pid := mgmttest.ReadPID(t, dms)
+ dmh := mgmttest.RunCommand(t, sh, nil, deviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+ pid := mgmttest.ReadPID(t, dmh)
defer syscall.Kill(pid, syscall.SIGINT)
// Create the local server that the app uses to let us know it's ready.
@@ -1455,8 +1449,8 @@
}
otherCtx := ctxWithNewPrincipal(t, selfCtx, idp, "other")
- _, dms := mgmttest.RunShellCommand(t, sh, nil, deviceManagerCmd, "dm", root, "unused_helper", "unused_app_repo_name", "unused_curr_link")
- pid := mgmttest.ReadPID(t, dms)
+ dmh := mgmttest.RunCommand(t, sh, nil, deviceManagerCmd, "dm", root, "unused_helper", "unused_app_repo_name", "unused_curr_link")
+ pid := mgmttest.ReadPID(t, dmh)
defer syscall.Kill(pid, syscall.SIGINT)
deviceStub := device.DeviceClient("dm/device")
@@ -1554,8 +1548,8 @@
// Create a script wrapping the test target that implements suidhelper.
helperPath := generateSuidHelperScript(t, root)
- _, dms := mgmttest.RunShellCommand(t, sh, nil, deviceManagerCmd, "-mocksetuid", "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
- pid := mgmttest.ReadPID(t, dms)
+ dmh := mgmttest.RunCommand(t, sh, nil, deviceManagerCmd, "-mocksetuid", "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+ pid := mgmttest.ReadPID(t, dmh)
defer syscall.Kill(pid, syscall.SIGINT)
// Claim the devicemanager with selfCtx as root/self/alice
claimDevice(t, selfCtx, "dm", "alice", noPairingToken)
@@ -1708,8 +1702,8 @@
// Set up the device manager. Since we won't do device manager updates,
// don't worry about its application envelope and current link.
- _, dms := mgmttest.RunShellCommand(t, sh, nil, deviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
- pid := mgmttest.ReadPID(t, dms)
+ dmh := mgmttest.RunCommand(t, sh, nil, deviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+ pid := mgmttest.ReadPID(t, dmh)
defer syscall.Kill(pid, syscall.SIGINT)
claimDevice(t, ctx, "dm", "mydevice", noPairingToken)
diff --git a/services/mgmt/device/impl/instance_reaping_test.go b/services/mgmt/device/impl/instance_reaping_test.go
index 0f52a0b..cc00312 100644
--- a/services/mgmt/device/impl/instance_reaping_test.go
+++ b/services/mgmt/device/impl/instance_reaping_test.go
@@ -7,55 +7,23 @@
"syscall"
"testing"
- "v.io/v23"
"v.io/v23/context"
"v.io/v23/naming"
- "v.io/v23/services/mgmt/application"
"v.io/v23/services/mgmt/stats"
"v.io/v23/vdl"
"v.io/x/ref/lib/flags/consts"
- "v.io/x/ref/lib/modules"
- "v.io/x/ref/lib/testutil"
- "v.io/x/ref/services/mgmt/device/impl"
mgmttest "v.io/x/ref/services/mgmt/lib/testutil"
)
-// TODO(rjkroege): This helper is generally useful. Move to util_test.go
-// and use it to reduce boiler plate across all tests here.
-func startupHelper(t *testing.T) (func(), *context.T, *modules.Shell, *application.Envelope, string, string) {
- ctx, shutdown := testutil.InitForTest()
- v23.GetNamespace(ctx).CacheCtl(naming.DisableCache(true))
-
- sh, deferFn := mgmttest.CreateShellAndMountTable(t, ctx, nil)
-
- // Set up mock application and binary repositories.
- envelope, envCleanup := startMockRepos(t, ctx)
-
- root, rootCleanup := mgmttest.SetupRootDir(t, "devicemanager")
- if err := impl.SaveCreatorInfo(root); err != nil {
- t.Fatal(err)
- }
-
- // Create a script wrapping the test target that implements suidhelper.
- helperPath := generateSuidHelperScript(t, root)
-
- return func() {
- rootCleanup()
- envCleanup()
- deferFn()
- shutdown()
- }, ctx, sh, envelope, root, helperPath
-}
-
func TestReaperNoticesAppDeath(t *testing.T) {
- cleanup, ctx, sh, envelope, root, helperPath := startupHelper(t)
+ cleanup, ctx, sh, envelope, root, helperPath, _ := startupHelper(t)
defer cleanup()
// Set up the device manager. Since we won't do device manager updates,
// don't worry about its application envelope and current link.
- dmh, dms := mgmttest.RunShellCommand(t, sh, nil, deviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
- mgmttest.ReadPID(t, dms)
+ dmh := mgmttest.RunCommand(t, sh, nil, deviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+ mgmttest.ReadPID(t, dmh)
claimDevice(t, ctx, "dm", "mydevice", noPairingToken)
// Create the local server that the app uses to let us know it's ready.
@@ -105,8 +73,8 @@
// Cleanly shut down the device manager.
syscall.Kill(dmh.Pid(), syscall.SIGINT)
- dms.Expect("dm terminated")
- dms.ExpectEOF()
+ dmh.Expect("dm terminated")
+ dmh.ExpectEOF()
}
func getPid(t *testing.T, ctx *context.T, appID, instanceID string) int {
@@ -120,7 +88,7 @@
}
func TestReapReconciliation(t *testing.T) {
- cleanup, ctx, sh, envelope, root, helperPath := startupHelper(t)
+ cleanup, ctx, sh, envelope, root, helperPath, _ := startupHelper(t)
defer cleanup()
// Start a device manager.
@@ -133,8 +101,8 @@
defer os.RemoveAll(dmCreds)
dmEnv := []string{fmt.Sprintf("%v=%v", consts.VeyronCredentials, dmCreds)}
- dmh, dms := mgmttest.RunShellCommand(t, sh, dmEnv, deviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
- mgmttest.ReadPID(t, dms)
+ dmh := mgmttest.RunCommand(t, sh, dmEnv, deviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+ mgmttest.ReadPID(t, dmh)
claimDevice(t, ctx, "dm", "mydevice", noPairingToken)
// Create the local server that the app uses to let us know it's ready.
@@ -160,8 +128,8 @@
// Shutdown the first device manager.
syscall.Kill(dmh.Pid(), syscall.SIGINT)
- dms.Expect("dm terminated")
- dms.ExpectEOF()
+ dmh.Expect("dm terminated")
+ dmh.ExpectEOF()
dmh.Shutdown(os.Stderr, os.Stderr)
resolveExpectNotFound(t, ctx, "dm") // Ensure a clean slate.
@@ -174,8 +142,8 @@
}
// Run another device manager to replace the dead one.
- dmh, dms = mgmttest.RunShellCommand(t, sh, dmEnv, deviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
- mgmttest.ReadPID(t, dms)
+ dmh = mgmttest.RunCommand(t, sh, dmEnv, deviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+ mgmttest.ReadPID(t, dmh)
resolve(t, ctx, "dm", 1) // Verify the device manager has published itself.
// By now, we've reconciled the state of the tree with which processes are actually
@@ -218,6 +186,6 @@
// TODO(rjkroege): Should be in a defer to ensure that the device manager
// is cleaned up even if the test fails in an exceptional way.
syscall.Kill(dmh.Pid(), syscall.SIGINT)
- dms.Expect("dm terminated")
- dms.ExpectEOF()
+ dmh.Expect("dm terminated")
+ dmh.ExpectEOF()
}
diff --git a/services/mgmt/device/impl/util_test.go b/services/mgmt/device/impl/util_test.go
index 6e8fe15..9d4b7eb 100644
--- a/services/mgmt/device/impl/util_test.go
+++ b/services/mgmt/device/impl/util_test.go
@@ -18,6 +18,7 @@
"v.io/v23/security"
"v.io/v23/services/mgmt/application"
"v.io/v23/services/mgmt/device"
+ "v.io/v23/services/mgmt/stats"
"v.io/v23/verror"
"v.io/x/lib/vlog"
tsecurity "v.io/x/ref/lib/testutil/security"
@@ -26,6 +27,7 @@
"v.io/x/ref/lib/testutil"
_ "v.io/x/ref/profiles/roaming"
"v.io/x/ref/services/mgmt/device/impl"
+ mgmttest "v.io/x/ref/services/mgmt/lib/testutil"
)
const (
@@ -181,6 +183,12 @@
return device.ApplicationClient(appName)
}
+func statsStub(nameComponents ...string) stats.StatsClientMethods {
+ baseName := "dm/apps"
+ statsName := naming.Join(append([]string{baseName}, nameComponents...)...)
+ return stats.StatsClient(statsName)
+}
+
func installApp(t *testing.T, ctx *context.T, opt ...interface{}) string {
appID, err := appStub().Install(ctx, mockApplicationRepoName, ocfg(opt), opkg(opt))
if err != nil {
@@ -383,3 +391,34 @@
}
return ret
}
+
+// TODO(rjkroege): This helper is generally useful. Use it to reduce
+// boiler plate across all device manager tests.
+func startupHelper(t *testing.T) (func(), *context.T, *modules.Shell, *application.Envelope, string, string, *tsecurity.IDProvider) {
+ ctx, shutdown := testutil.InitForTest()
+ v23.GetNamespace(ctx).CacheCtl(naming.DisableCache(true))
+
+ // Make a new identity context.
+ idp := tsecurity.NewIDProvider("root")
+ ctx = ctxWithNewPrincipal(t, ctx, idp, "self")
+
+ sh, deferFn := mgmttest.CreateShellAndMountTable(t, ctx, nil)
+
+ // Set up mock application and binary repositories.
+ envelope, envCleanup := startMockRepos(t, ctx)
+
+ root, rootCleanup := mgmttest.SetupRootDir(t, "devicemanager")
+ if err := impl.SaveCreatorInfo(root); err != nil {
+ t.Fatal(err)
+ }
+
+ // Create a script wrapping the test target that implements suidhelper.
+ helperPath := generateSuidHelperScript(t, root)
+
+ return func() {
+ rootCleanup()
+ envCleanup()
+ deferFn()
+ shutdown()
+ }, ctx, sh, envelope, root, helperPath, idp
+}
diff --git a/services/mgmt/device/impl/v23_test.go b/services/mgmt/device/impl/v23_test.go
new file mode 100644
index 0000000..aa08bfe
--- /dev/null
+++ b/services/mgmt/device/impl/v23_test.go
@@ -0,0 +1,18 @@
+// 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.
+
+// This file was auto-generated via go generate.
+// DO NOT UPDATE MANUALLY
+package impl_test
+
+import "v.io/x/ref/lib/modules"
+
+func init() {
+ modules.RegisterChild("execScript", `execScript launches the script passed as argument.`, execScript)
+ modules.RegisterChild("deviceManager", `deviceManager sets up a device manager server. It accepts the name to
+publish the server under as an argument. Additional arguments can optionally
+specify device manager config settings.`, deviceManager)
+ modules.RegisterChild("deviceManagerV10", `This is the same as deviceManager above, except that it has a different major version number`, deviceManagerV10)
+ modules.RegisterChild("app", ``, app)
+}
diff --git a/services/mgmt/device/starter/starter.go b/services/mgmt/device/starter/starter.go
index 4ad22f8..f1fc91d 100644
--- a/services/mgmt/device/starter/starter.go
+++ b/services/mgmt/device/starter/starter.go
@@ -117,6 +117,10 @@
}
func startClaimableDevice(ctx *context.T, dispatcher ipc.Dispatcher, args Args) (func(), error) {
+ ctx, err := setNamespaceRootsForUnclaimedDevice(ctx)
+ if err != nil {
+ return nil, err
+ }
// TODO(caprita,ashankar): We create a context with a new stream manager
// that we can cancel once the device has been claimed. This gets around
// the following issue: if we publish the claimable server to the local
@@ -129,8 +133,7 @@
// gets confused trying to reuse the old connection and doesn't attempt
// to create a new connection). We should get to the bottom of it.
ctx, cancel := context.WithCancel(ctx)
- ctx, err := v23.SetNewStreamManager(ctx)
- if err != nil {
+ if ctx, err = v23.SetNewStreamManager(ctx); err != nil {
cancel()
return nil, err
}
@@ -362,3 +365,44 @@
remoteBlessings, _ := call.RemoteBlessings()
return remoteBlessings, nil
}
+
+// Unclaimed devices typically have Principals that recognize no other
+// authoritative public keys than their own. As a result, they will fail to
+// authorize any other services.
+//
+// With no information to authenticate or authorize peers (including the
+// mounttable at the namespace root), this unclaimed device manager will be
+// unable to make any outgoing RPCs.
+//
+// As a workaround, reconfigure it to "authorize any root mounttable" by
+// removing references to the expected blessings of the namespace root. This
+// will allow the unclaimed device manager to mount itself.
+//
+// TODO(ashankar,caprita): The more secure fix would be to ensure that an
+// unclaimed device is configured to recognize the blessings presented by the
+// mounttable it is configured to talk to. Of course, if the root mounttable is
+// "discovered" as opposed to "configured", then this new device will have to
+// return to either not mounting itself (and being claimed via some discovery
+// protocol like mdns or bluetooth) or ignoring the blessings of the namespace
+// root.
+func setNamespaceRootsForUnclaimedDevice(ctx *context.T) (*context.T, error) {
+ origroots := v23.GetNamespace(ctx).Roots()
+ roots := make([]string, len(origroots))
+ for i, orig := range origroots {
+ addr, suffix := naming.SplitAddressName(orig)
+ origep, err := v23.NewEndpoint(addr)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create endpoint from namespace root %q: %v", orig, err)
+ }
+ ep := naming.FormatEndpoint(
+ origep.Addr().Network(),
+ origep.Addr().String(),
+ origep.RoutingID(),
+ origep.IPCVersionRange(),
+ naming.ServesMountTableOpt(origep.ServesMountTable()))
+ roots[i] = naming.JoinAddressName(ep, suffix)
+ }
+ vlog.Infof("Changing namespace roots from %v to %v", origroots, roots)
+ ctx, _, err := v23.SetNewNamespace(ctx, roots...)
+ return ctx, err
+}
diff --git a/services/mgmt/lib/testutil/modules.go b/services/mgmt/lib/testutil/modules.go
index b03dcb9..1736135 100644
--- a/services/mgmt/lib/testutil/modules.go
+++ b/services/mgmt/lib/testutil/modules.go
@@ -17,7 +17,6 @@
"v.io/x/ref/lib/modules"
"v.io/x/ref/lib/modules/core"
"v.io/x/ref/lib/testutil"
- "v.io/x/ref/lib/testutil/expect"
tsecurity "v.io/x/ref/lib/testutil/security"
)
@@ -35,11 +34,10 @@
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")
+ h.ExpectVar("PID")
+ rootName := h.ExpectVar("MT_NAME")
if t.Failed() {
- t.Fatalf("failed to read mt name: %s", s.Error())
+ t.Fatalf("failed to read mt name: %s", h.Error())
}
return rootName, h
}
@@ -61,10 +59,13 @@
// CreateShellAndMountTable builds a new modules shell and its
// associated mount table.
func CreateShellAndMountTable(t *testing.T, ctx *context.T, p security.Principal) (*modules.Shell, func()) {
- sh, err := modules.NewShell(ctx, p)
+ sh, err := modules.NewExpectShell(ctx, p, t, testing.Verbose())
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
+ opts := sh.DefaultStartOpts()
+ opts.ExpectTimeout = ExpectTimeout
+ sh.SetDefaultStartOpts(opts)
// The shell, will, by default share credentials with its children.
sh.ClearVar(consts.VeyronCredentials)
@@ -100,16 +101,25 @@
return sh, fn
}
+// RunShellCommand runs an external, non Vanadium command using the modules system.
+func RunShellCommand(t *testing.T, sh *modules.Shell, env []string, cmd string, args ...string) modules.Handle {
+ return runWithOpts(t, sh, sh.DefaultStartOpts().NoExecCommand(), env, cmd, args...)
+}
+
+// RunCommand runs a modules command.
+func RunCommand(t *testing.T, sh *modules.Shell, env []string, cmd string, args ...string) modules.Handle {
+ return runWithOpts(t, sh, sh.DefaultStartOpts(), env, cmd, args...)
+}
+
// RunShellCommand runs an external command using the modules system.
-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...)
+func runWithOpts(t *testing.T, sh *modules.Shell, opts modules.StartOpts, env []string, cmd string, args ...string) modules.Handle {
+ h, err := sh.StartWithOpts(opts, env, cmd, args...)
if err != nil {
t.Fatalf(testutil.FormatLogLine(2, "failed to start %q: %s", cmd, err))
- return nil, nil
+ return nil
}
- s := expect.NewSession(t, h.Stdout(), ExpectTimeout)
- s.SetVerbosity(testing.Verbose())
- return h, s
+ h.SetVerbosity(testing.Verbose())
+ return h
}
// NewServer creates a new server.
@@ -128,8 +138,8 @@
// ReadPID waits for the "ready:<PID>" line from the child and parses out the
// PID of the child.
-func ReadPID(t *testing.T, s *expect.Session) int {
- m := s.ExpectRE("ready:([0-9]+)", -1)
+func ReadPID(t *testing.T, h modules.ExpectSession) int {
+ m := h.ExpectRE("ready:([0-9]+)", -1)
if len(m) == 1 && len(m[0]) == 2 {
pid, err := strconv.Atoi(m[0][1])
if err != nil {
diff --git a/services/mgmt/profile/profiled/profiled_v23_test.go b/services/mgmt/profile/profiled/profiled_v23_test.go
index 5ef410e..a7152a6 100644
--- a/services/mgmt/profile/profiled/profiled_v23_test.go
+++ b/services/mgmt/profile/profiled/profiled_v23_test.go
@@ -50,9 +50,9 @@
"-name=" + profileRepoName, "-store=" + profileRepoStore,
"-veyron.tcp.address=127.0.0.1:0",
}
- i.BuildGoPkg("v.io/x/ref/services/mgmt/profile/profiled").Start(args...)
+ i.BuildV23Pkg("v.io/x/ref/services/mgmt/profile/profiled").Start(args...)
- clientBin := i.BuildGoPkg("v.io/x/ref/cmd/profile")
+ clientBin := i.BuildV23Pkg("v.io/x/ref/cmd/profile")
// Create a profile.
const profile = "test-profile"
diff --git a/services/mgmt/suidhelper/impl/args.go b/services/mgmt/suidhelper/impl/args.go
index 3703e22..04933fb 100644
--- a/services/mgmt/suidhelper/impl/args.go
+++ b/services/mgmt/suidhelper/impl/args.go
@@ -8,6 +8,7 @@
"os"
"os/user"
"strconv"
+ "strings"
sflag "v.io/x/ref/services/mgmt/suidhelper/impl/flag"
)
@@ -57,6 +58,16 @@
flagProgName = sflag.ProgName
}
+func cleanEnv(env []string) []string {
+ nenv := []string{}
+ for _, e := range env {
+ if !strings.HasPrefix(e, "VEYRON_SUIDHELPER_TEST") {
+ nenv = append(nenv, e)
+ }
+ }
+ return nenv
+}
+
// ParseArguments populates the WorkParameter object from the provided args
// and env strings.
func (wp *WorkParameters) ProcessArguments(fs *flag.FlagSet, env []string) error {
@@ -96,6 +107,7 @@
// Preserve the arguments for examination by the test harness if executed
// in the course of a test.
if os.Getenv("VEYRON_SUIDHELPER_TEST") != "" {
+ env = cleanEnv(env)
b := new(bytes.Buffer)
enc := json.NewEncoder(b)
enc.Encode(ArgsSavedForTest{