veyron/mgmt/lib/exec: move this into veyron/lib/exec and merge veyron/lib/config with it.
Change-Id: I8c5b2d1f1a4a9d3c48b6e76e1fe5e43c93416c0d
diff --git a/services/mgmt/lib/exec/child.go b/services/mgmt/lib/exec/child.go
deleted file mode 100644
index 909c6e9..0000000
--- a/services/mgmt/lib/exec/child.go
+++ /dev/null
@@ -1,119 +0,0 @@
-package exec
-
-import (
- "encoding/binary"
- "errors"
- "io"
- "os"
- "sync"
-
- "veyron.io/veyron/veyron/lib/config"
-)
-
-var (
- ErrNoVersion = errors.New(versionVariable + " environment variable missing")
- ErrUnsupportedVersion = errors.New("Unsupported version of veyron/lib/exec request by " + versionVariable + " environment variable")
-)
-
-type ChildHandle struct {
- // Config is passed down from the parent.
- Config config.Config
- // Secret is a secret passed to the child by its parent via a
- // trusted channel.
- Secret string
- // statusPipe is a pipe that is used to notify the parent that the child
- // process has started successfully. It is meant to be invoked by the
- // veyron framework to notify the parent that the child process has
- // successfully started.
- statusPipe *os.File
-}
-
-var (
- childHandle *ChildHandle
- childHandleErr error
- once sync.Once
-)
-
-// fileOffset accounts for the file descriptors that are always passed
-// to the child by the parent: stderr, stdin, stdout, data read, and
-// status write. Any extra files added by the client will follow
-// fileOffset.
-const fileOffset = 5
-
-// GetChildHandle returns a ChildHandle that can be used to signal
-// that the child is 'ready' (by calling SetReady) to its parent or to
-// retrieve data securely passed to this process by its parent. For
-// instance, a secret intended to create a secure communication
-// channels and or authentication.
-//
-// If the child is relying on exec.Cmd.ExtraFiles then its first file
-// descriptor will not be 3, but will be offset by extra files added
-// by the framework. The developer should use the NewExtraFile method
-// to robustly get their extra files with the correct offset applied.
-func GetChildHandle() (*ChildHandle, error) {
- once.Do(func() {
- childHandle, childHandleErr = createChildHandle()
- })
- return childHandle, childHandleErr
-}
-
-// SetReady writes a 'ready' status to its parent.
-func (c *ChildHandle) SetReady() error {
- _, err := c.statusPipe.Write([]byte(readyStatus))
- c.statusPipe.Close()
- return err
-}
-
-// NewExtraFile creates a new file handle for the i-th file descriptor after
-// discounting stdout, stderr, stdin and the files reserved by the framework for
-// its own purposes.
-func (c *ChildHandle) NewExtraFile(i uintptr, name string) *os.File {
- return os.NewFile(i+fileOffset, name)
-}
-
-func createChildHandle() (*ChildHandle, error) {
- switch os.Getenv(versionVariable) {
- case "":
- return nil, ErrNoVersion
- case version1:
- // TODO(cnicolaou): need to use major.minor.build format for
- // version #s.
- default:
- return nil, ErrUnsupportedVersion
- }
- dataPipe := os.NewFile(3, "data_rd")
- serializedConfig, err := decodeString(dataPipe)
- if err != nil {
- return nil, err
- }
- cfg := config.New()
- if err := cfg.MergeFrom(serializedConfig); err != nil {
- return nil, err
- }
- secret, err := decodeString(dataPipe)
- if err != nil {
- return nil, err
- }
- childHandle = &ChildHandle{
- Config: cfg,
- Secret: secret,
- statusPipe: os.NewFile(4, "status_wr"),
- }
- return childHandle, nil
-}
-
-func decodeString(r io.Reader) (string, error) {
- var l int64 = 0
- if err := binary.Read(r, binary.BigEndian, &l); err != nil {
- return "", err
- }
- var data []byte = make([]byte, l)
- if n, err := r.Read(data); err != nil || int64(n) != l {
- if err != nil {
- return "", err
- } else {
- return "", errors.New("partial read")
- }
- }
- return string(data), nil
-}
diff --git a/services/mgmt/lib/exec/doc.go b/services/mgmt/lib/exec/doc.go
deleted file mode 100644
index 953965a..0000000
--- a/services/mgmt/lib/exec/doc.go
+++ /dev/null
@@ -1,13 +0,0 @@
-// Package exec implements simple process creation and rendezvous, including
-// sharing a secret with, and passing arbitrary configuration to, the newly
-// created process.
-//
-// Once a parent starts a child process it can use WaitForReady to wait
-// for the child to reach its 'Ready' state. Operations are provided to wait
-// for the child to terminate, and to terminate the child cleaning up any state
-// associated with it.
-//
-// A child process uses the GetChildHandle function to complete the initial
-// authentication handshake. The child must call SetReady to indicate that it is
-// fully initialized and ready for whatever purpose it is intended to fulfill.
-package exec
diff --git a/services/mgmt/lib/exec/example_test.go b/services/mgmt/lib/exec/example_test.go
deleted file mode 100644
index c66adcc..0000000
--- a/services/mgmt/lib/exec/example_test.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package exec
-
-import (
- "log"
- "os/exec"
- "time"
-)
-
-func ExampleChildHandle() {
- ch, _ := GetChildHandle()
- // Initalize the app/service, access the secret shared with the
- // child by its parent
- _ = ch.Secret
- ch.SetReady()
- // Do work
-}
-
-func ExampleParentHandle() {
- cmd := exec.Command("/bin/hostname")
- ph := NewParentHandle(cmd, SecretOpt("secret"))
-
- // Start the child process.
- if err := ph.Start(); err != nil {
- log.Printf("failed to start child: %s\n", err)
- return
- }
-
- // Wait for the child to become ready.
- if err := ph.WaitForReady(time.Second); err != nil {
- log.Printf("failed to start child: %s\n", err)
- return
- }
-
- // Wait for the child to exit giving it an hour to do it's work.
- if err := ph.Wait(time.Hour); err != nil {
- log.Printf("wait or child failed %s\n", err)
- }
-}
diff --git a/services/mgmt/lib/exec/exec_test.go b/services/mgmt/lib/exec/exec_test.go
deleted file mode 100644
index 399f00a..0000000
--- a/services/mgmt/lib/exec/exec_test.go
+++ /dev/null
@@ -1,523 +0,0 @@
-package exec_test
-
-import (
- "fmt"
- "io"
- "log"
- "os"
- "os/exec"
- "sync"
- "testing"
- "time"
-
- "veyron.io/veyron/veyron/lib/config"
- // Use mock timekeeper to avoid actually sleeping during the test.
- "veyron.io/veyron/veyron/runtimes/google/testing/timekeeper"
- vexec "veyron.io/veyron/veyron/services/mgmt/lib/exec"
-)
-
-// We always expect there to be exactly three open file descriptors
-// when the test starts out: STDIN, STDOUT, and STDERR. This
-// assumption is tested in init below, and in the rare cases where it
-// is wrong, we bail out.
-const baselineOpenFiles = 3
-
-func init() {
- if os.Getenv("GO_WANT_HELPER_PROCESS_EXEC") == "1" {
- return
- }
- if want, got := baselineOpenFiles, openFiles(); want != got {
- message := `Test expected to start with %d open files, found %d instead.
-This can happen if parent process has any open file descriptors,
-e.g. pipes, that are being inherited.`
- panic(fmt.Errorf(message, want, got))
- }
-}
-
-// These tests need to run a subprocess and we reuse this same test
-// binary to do so. A fake test 'TestHelperProcess' contains the code
-// we need to run in the child and we simply run this same binary with
-// a test.run= arg that runs just that test. This idea was taken from
-// the tests for os/exec.
-func helperCommand(s ...string) *exec.Cmd {
- cs := []string{"-test.run=TestHelperProcess", "--"}
- cs = append(cs, s...)
- cmd := exec.Command(os.Args[0], cs...)
- cmd.Env = append([]string{"GO_WANT_HELPER_PROCESS_EXEC=1"}, os.Environ()...)
- return cmd
-}
-
-func openFiles() int {
- f, err := os.Open("/dev/null")
- if err != nil {
- panic("Failed to open /dev/null\n")
- }
- n := f.Fd()
- f.Close()
- return int(n)
-}
-
-func clean(t *testing.T, ph ...*vexec.ParentHandle) {
- for _, p := range ph {
- alreadyClean := !p.Exists()
- p.Clean()
- if !alreadyClean && p.Exists() {
- t.Errorf("child process left behind even after calling Clean")
- }
- }
- if want, got := baselineOpenFiles, openFiles(); want != got {
- t.Errorf("Leaking file descriptors: expect %d, got %d", want, got)
- }
-}
-
-func read(ch chan bool, r io.Reader, m string) {
- buf := make([]byte, 4096*4)
- n, err := r.Read(buf)
- if err != nil {
- log.Printf("failed to read message: error %s, expecting '%s'\n",
- err, m)
- ch <- false
- return
- }
- g := string(buf[:n])
- b := g == m
- if !b {
- log.Printf("read '%s', not '%s'\n", g, m)
- }
- ch <- b
-}
-
-func expectMessage(r io.Reader, m string) bool {
- ch := make(chan bool, 1)
- go read(ch, r, m)
- select {
- case b := <-ch:
- return b
- case <-time.After(5 * time.Second):
- log.Printf("expectMessage: timeout\n")
- return false
- }
- panic("unreachable")
-}
-
-func TestConfigExchange(t *testing.T) {
- cmd := helperCommand("testConfig")
- stderr, _ := cmd.StderrPipe()
- cfg := config.New()
- cfg.Set("foo", "bar")
- ph := vexec.NewParentHandle(cmd, vexec.ConfigOpt{cfg})
- err := ph.Start()
- if err != nil {
- t.Fatalf("testConfig: start: %v", err)
- }
- serialized, err := cfg.Serialize()
- if err != nil {
- t.Fatalf("testConfig: failed to serialize config: %v", err)
- }
- if !expectMessage(stderr, serialized) {
- t.Errorf("unexpected output from child")
- } else {
- if err = cmd.Wait(); err != nil {
- t.Errorf("testConfig: wait: %v", err)
- }
- }
- clean(t, ph)
-}
-
-func TestSecretExchange(t *testing.T) {
- cmd := helperCommand("testSecret")
- stderr, _ := cmd.StderrPipe()
- ph := vexec.NewParentHandle(cmd, vexec.SecretOpt("dummy_secret"))
- err := ph.Start()
- if err != nil {
- t.Fatalf("testSecretTest: start: %v", err)
- }
- if !expectMessage(stderr, "dummy_secret") {
- t.Errorf("unexpected output from child")
- } else {
- if err = cmd.Wait(); err != nil {
- t.Errorf("testSecretTest: wait: %v", err)
- }
- }
- clean(t, ph)
-}
-
-func TestNoVersion(t *testing.T) {
- // Make sure that Init correctly tests for the presence of VEXEC_VERSION
- _, err := vexec.GetChildHandle()
- if err != vexec.ErrNoVersion {
- t.Errorf("Should be missing Version")
- }
-}
-
-func waitForReady(t *testing.T, cmd *exec.Cmd, name string, delay int, ph *vexec.ParentHandle) error {
- err := ph.Start()
- if err != nil {
- t.Fatalf("%s: start: %v", name, err)
- return err
- }
- return ph.WaitForReady(time.Duration(delay) * time.Second)
-}
-
-func readyHelperCmd(t *testing.T, cmd *exec.Cmd, name, result string) *vexec.ParentHandle {
- stderr, err := cmd.StderrPipe()
- if err != nil {
- t.Fatalf("%s: failed to get stderr pipe\n", name)
- }
- ph := vexec.NewParentHandle(cmd)
- if err := waitForReady(t, cmd, name, 4, ph); err != nil {
- t.Errorf("%s: WaitForReady: %v (%v)", name, err, ph)
- return nil
- }
- if !expectMessage(stderr, result) {
- t.Errorf("%s: failed to read '%s' from child\n", name, result)
- }
- return ph
-}
-
-func readyHelper(t *testing.T, name, test, result string) *vexec.ParentHandle {
- cmd := helperCommand(test)
- return readyHelperCmd(t, cmd, name, result)
-}
-
-func testMany(t *testing.T, name, test, result string) []*vexec.ParentHandle {
- nprocs := 10
- ph := make([]*vexec.ParentHandle, nprocs)
- cmd := make([]*exec.Cmd, nprocs)
- stderr := make([]io.ReadCloser, nprocs)
- controlReaders := make([]io.ReadCloser, nprocs)
- var done sync.WaitGroup
- for i := 0; i < nprocs; i++ {
- cmd[i] = helperCommand(test)
- // The control pipe is used to signal the child when to wake up.
- controlRead, controlWrite, err := os.Pipe()
- if err != nil {
- t.Errorf("Failed to create control pipe: %v", err)
- return nil
- }
- controlReaders[i] = controlRead
- cmd[i].ExtraFiles = append(cmd[i].ExtraFiles, controlRead)
- stderr[i], _ = cmd[i].StderrPipe()
- tk := timekeeper.NewManualTime()
- ph[i] = vexec.NewParentHandle(cmd[i], vexec.TimeKeeperOpt{tk})
- done.Add(1)
- go func() {
- // For simulated slow children, wait until the parent
- // starts waiting, and then wake up the child.
- if test == "testReadySlow" {
- <-tk.Requests()
- tk.AdvanceTime(3 * time.Second)
- if _, err = controlWrite.Write([]byte("wake")); err != nil {
- t.Errorf("Failed to write to control pipe: %v", err)
- }
- }
- controlWrite.Close()
- done.Done()
- }()
- if err := ph[i].Start(); err != nil {
- t.Errorf("%s: Failed to start child %d: %s\n", name, i, err)
- }
- }
- for i := 0; i < nprocs; i++ {
- if err := ph[i].WaitForReady(5 * time.Second); err != nil {
- t.Errorf("%s: Failed to wait for child %d: %s\n", name, i, err)
- }
- controlReaders[i].Close()
- }
- for i := 0; i < nprocs; i++ {
- if !expectMessage(stderr[i], result) {
- t.Errorf("%s: Failed to read message from child %d\n", name, i)
- }
- }
- done.Wait()
- return ph
-}
-
-func TestToReadyMany(t *testing.T) {
- clean(t, testMany(t, "TestToReadyMany", "testReady", ".")...)
-}
-
-func TestToReadySlowMany(t *testing.T) {
- clean(t, testMany(t, "TestToReadySlowMany", "testReadySlow", "..")...)
-}
-
-func TestToReady(t *testing.T) {
- ph := readyHelper(t, "TestToReady", "testReady", ".")
- clean(t, ph)
-}
-
-func TestNeverReady(t *testing.T) {
- name := "testNeverReady"
- result := "never ready"
- cmd := helperCommand(name)
- stderr, _ := cmd.StderrPipe()
- ph := vexec.NewParentHandle(cmd)
- err := waitForReady(t, cmd, name, 1, ph)
- if err != vexec.ErrTimeout {
- t.Errorf("Failed to get timeout: got %v\n", err)
- } else {
- // block waiting for error from child
- if !expectMessage(stderr, result) {
- t.Errorf("%s: failed to read '%s' from child\n", name, result)
- }
- }
- clean(t, ph)
-}
-
-func TestTooSlowToReady(t *testing.T) {
- name := "testTooSlowToReady"
- result := "write status_wr: broken pipe"
- cmd := helperCommand(name)
- // The control pipe is used to signal the child when to wake up.
- controlRead, controlWrite, err := os.Pipe()
- if err != nil {
- t.Errorf("Failed to create control pipe: %v", err)
- return
- }
- cmd.ExtraFiles = append(cmd.ExtraFiles, controlRead)
- stderr, _ := cmd.StderrPipe()
- tk := timekeeper.NewManualTime()
- ph := vexec.NewParentHandle(cmd, vexec.TimeKeeperOpt{tk})
- defer clean(t, ph)
- defer controlWrite.Close()
- defer controlRead.Close()
- // Wait for the parent to start waiting, then simulate a timeout.
- go func() {
- toWait := <-tk.Requests()
- tk.AdvanceTime(toWait)
- }()
- err = waitForReady(t, cmd, name, 1, ph)
- if err != vexec.ErrTimeout {
- t.Errorf("Failed to get timeout: got %v\n", err)
- } else {
- // After the parent timed out, wake up the child and let it
- // proceed.
- if _, err = controlWrite.Write([]byte("wake")); err != nil {
- t.Errorf("Failed to write to control pipe: %v", err)
- } else {
- // block waiting for error from child
- if !expectMessage(stderr, result) {
- t.Errorf("%s: failed to read '%s' from child\n", name, result)
- }
- }
- }
-}
-
-func TestToReadySlow(t *testing.T) {
- name := "TestToReadySlow"
- cmd := helperCommand("testReadySlow")
- // The control pipe is used to signal the child when to wake up.
- controlRead, controlWrite, err := os.Pipe()
- if err != nil {
- t.Errorf("Failed to create control pipe: %v", err)
- return
- }
- cmd.ExtraFiles = append(cmd.ExtraFiles, controlRead)
- stderr, err := cmd.StderrPipe()
- if err != nil {
- t.Fatalf("%s: failed to get stderr pipe", name)
- }
- tk := timekeeper.NewManualTime()
- ph := vexec.NewParentHandle(cmd, vexec.TimeKeeperOpt{tk})
- defer clean(t, ph)
- defer controlWrite.Close()
- defer controlRead.Close()
- // Wait for the parent to start waiting, simulate a short wait (but not
- // enough to timeout the parent), then wake up the child.
- go func() {
- <-tk.Requests()
- tk.AdvanceTime(2 * time.Second)
- if _, err = controlWrite.Write([]byte("wake")); err != nil {
- t.Errorf("Failed to write to control pipe: %v", err)
- }
- }()
- if err := waitForReady(t, cmd, name, 4, ph); err != nil {
- t.Errorf("%s: WaitForReady: %v (%v)", name, err, ph)
- return
- }
- // After the child has replied, simulate a timeout on the server by
- // advacing the time more; at this point, however, the timeout should no
- // longer occur since the child already replied.
- tk.AdvanceTime(2 * time.Second)
- if result := ".."; !expectMessage(stderr, result) {
- t.Errorf("%s: failed to read '%s' from child\n", name, result)
- }
-}
-
-func TestToCompletion(t *testing.T) {
- ph := readyHelper(t, "TestToCompletion", "testSuccess", "...ok")
- e := ph.Wait(time.Second)
- if e != nil {
- t.Errorf("Wait failed: err %s\n", e)
- }
- clean(t, ph)
-}
-
-func TestToCompletionError(t *testing.T) {
- ph := readyHelper(t, "TestToCompletionError", "testError", "...err")
- e := ph.Wait(time.Second)
- if e == nil {
- t.Errorf("Wait failed: err %s\n", e)
- }
- clean(t, ph)
-}
-
-func TestExtraFiles(t *testing.T) {
- cmd := helperCommand("testExtraFiles")
- rd, wr, err := os.Pipe()
- if err != nil {
- t.Fatalf("Failed to create pipe: %s\n", err)
- }
- cmd.ExtraFiles = append(cmd.ExtraFiles, rd)
- msg := "hello there..."
- fmt.Fprintf(wr, msg)
- ph := readyHelperCmd(t, cmd, "TestExtraFiles", "child: "+msg)
- if ph == nil {
- t.Fatalf("Failed to get vexec.ParentHandle\n")
- }
- e := ph.Wait(1 * time.Second)
- if e != nil {
- t.Errorf("Wait failed: err %s\n", e)
- }
- rd.Close()
- wr.Close()
- clean(t, ph)
-}
-
-// TestHelperProcess isn't a real test; it's used as a helper process
-// for the other tests.
-func TestHelperProcess(*testing.T) {
- // Return immediately if this is not run as the child helper
- // for the other tests.
- if os.Getenv("GO_WANT_HELPER_PROCESS_EXEC") != "1" {
- return
- }
- defer os.Exit(0)
-
- // Write errors to stderr or using log. since the parent
- // process is reading stderr.
- args := os.Args
- for len(args) > 0 {
- if args[0] == "--" {
- args = args[1:]
- break
- }
- args = args[1:]
- }
-
- if len(args) == 0 {
- log.Fatal(os.Stderr, "No command\n")
- }
-
- cmd, args := args[0], args[1:]
-
- switch cmd {
- case "testNeverReady":
- _, err := vexec.GetChildHandle()
- if err != nil {
- log.Fatal(os.Stderr, "%s\n", err)
- }
- fmt.Fprintf(os.Stderr, "never ready")
- case "testTooSlowToReady":
- ch, err := vexec.GetChildHandle()
- if err != nil {
- log.Fatal(os.Stderr, "%s\n", err)
- }
- // Wait for the parent to tell us when it's ok to proceed.
- controlPipe := ch.NewExtraFile(0, "control_rd")
- for {
- buf := make([]byte, 100)
- n, err := controlPipe.Read(buf)
- if err != nil {
- log.Fatal(os.Stderr, "%s", err)
- }
- if n > 0 {
- break
- }
- }
- // SetReady should return an error since the parent has
- // timed out on us and we'd be writing to a closed pipe.
- if err := ch.SetReady(); err != nil {
- fmt.Fprintf(os.Stderr, "%s", err)
- } else {
- fmt.Fprintf(os.Stderr, "didn't get the expected error")
- }
- os.Exit(0)
- case "testReady":
- ch, err := vexec.GetChildHandle()
- if err != nil {
- log.Fatal(os.Stderr, "%s", err)
- }
- ch.SetReady()
- fmt.Fprintf(os.Stderr, ".")
- case "testReadySlow":
- ch, err := vexec.GetChildHandle()
- if err != nil {
- log.Fatal(os.Stderr, "%s", err)
- }
- // Wait for the parent to tell us when it's ok to proceed.
- controlPipe := ch.NewExtraFile(0, "control_rd")
- for {
- buf := make([]byte, 100)
- n, err := controlPipe.Read(buf)
- if err != nil {
- log.Fatal(os.Stderr, "%s", err)
- }
- if n > 0 {
- break
- }
- }
- ch.SetReady()
- fmt.Fprintf(os.Stderr, "..")
- case "testSuccess", "testError":
- ch, err := vexec.GetChildHandle()
- if err != nil {
- log.Fatal(os.Stderr, "%s\n", err)
- }
- ch.SetReady()
- rc := make(chan int)
- go func() {
- if cmd == "testError" {
- fmt.Fprintf(os.Stderr, "...err")
- rc <- 1
- } else {
- fmt.Fprintf(os.Stderr, "...ok")
- rc <- 0
- }
- }()
- r := <-rc
- os.Exit(r)
- case "testConfig":
- ch, err := vexec.GetChildHandle()
- if err != nil {
- log.Fatalf("%v", err)
- } else {
- serialized, err := ch.Config.Serialize()
- if err != nil {
- log.Fatalf("%v", err)
- }
- fmt.Fprintf(os.Stderr, "%s", serialized)
- }
- case "testSecret":
- ch, err := vexec.GetChildHandle()
- if err != nil {
- log.Fatalf("%s", err)
- } else {
- fmt.Fprintf(os.Stderr, "%s", ch.Secret)
- }
- case "testExtraFiles":
- ch, err := vexec.GetChildHandle()
- if err != nil {
- log.Fatal("error.... %s\n", err)
- }
- err = ch.SetReady()
- rd := ch.NewExtraFile(0, "read")
- buf := make([]byte, 1024)
- if n, err := rd.Read(buf); err != nil {
- fmt.Fprintf(os.Stderr, "child: error %s\n", err)
- } else {
- fmt.Fprintf(os.Stderr, "child: %s", string(buf[:n]))
- }
- os.Exit(0)
- }
-}
diff --git a/services/mgmt/lib/exec/parent.go b/services/mgmt/lib/exec/parent.go
deleted file mode 100644
index 91a6147..0000000
--- a/services/mgmt/lib/exec/parent.go
+++ /dev/null
@@ -1,250 +0,0 @@
-package exec
-
-import (
- "encoding/binary"
- "errors"
- "io"
- "os"
- "os/exec"
- "syscall"
- "time"
-
- "veyron.io/veyron/veyron/lib/config"
- // TODO(cnicolaou): move timekeeper out of runtimes
- "veyron.io/veyron/veyron/runtimes/google/lib/timekeeper"
-
- "veyron.io/veyron/veyron2/vlog"
-)
-
-var (
- ErrAuthTimeout = errors.New("timout in auth handshake")
- ErrTimeout = errors.New("timeout waiting for child")
- ErrSecretTooLarge = errors.New("secret is too large")
-)
-
-// A ParentHandle is the Parent process' means of managing a single child.
-type ParentHandle struct {
- c *exec.Cmd
- config config.Config
- secret string
- statusRead *os.File
- statusWrite *os.File
- tk timekeeper.TimeKeeper
-}
-
-// ParentHandleOpt is an option for NewParentHandle.
-type ParentHandleOpt interface {
- // ExecParentHandleOpt is a signature 'dummy' method for the
- // interface.
- ExecParentHandleOpt()
-}
-
-// ConfigOpt can be used to seed the parent handle with a
-// config to be passed to the child.
-type ConfigOpt struct {
- config.Config
-}
-
-// ExecParentHandleOpt makes ConfigOpt an instance of
-// ParentHandleOpt.
-func (ConfigOpt) ExecParentHandleOpt() {}
-
-// SecretOpt can be used to seed the parent handle with a custom secret.
-type SecretOpt string
-
-// ExecParentHandleOpt makes SecretOpt an instance of ParentHandleOpt.
-func (SecretOpt) ExecParentHandleOpt() {}
-
-// TimeKeeperOpt can be used to seed the parent handle with a custom timekeeper.
-type TimeKeeperOpt struct {
- timekeeper.TimeKeeper
-}
-
-// ExecParentHandleOpt makes TimeKeeperOpt an instance of ParentHandleOpt.
-func (TimeKeeperOpt) ExecParentHandleOpt() {}
-
-// NewParentHandle creates a ParentHandle for the child process represented by
-// an instance of exec.Cmd.
-func NewParentHandle(c *exec.Cmd, opts ...ParentHandleOpt) *ParentHandle {
- c.Env = append(c.Env, versionVariable+"="+version1)
- cfg, secret := config.New(), ""
- tk := timekeeper.RealTime()
- for _, opt := range opts {
- switch v := opt.(type) {
- case ConfigOpt:
- cfg = v
- case SecretOpt:
- secret = string(v)
- case TimeKeeperOpt:
- tk = v
- default:
- vlog.Errorf("Unrecognized parent option: %v", v)
- }
- }
- return &ParentHandle{
- c: c,
- config: cfg,
- secret: secret,
- tk: tk,
- }
-}
-
-// Start starts the child process, sharing a secret with it and
-// setting up a communication channel over which to read its status.
-func (p *ParentHandle) Start() error {
- // Create anonymous pipe for communicating data between the child
- // and the parent.
- dataRead, dataWrite, err := os.Pipe()
- if err != nil {
- return err
- }
- defer dataRead.Close()
- defer dataWrite.Close()
- statusRead, statusWrite, err := os.Pipe()
- if err != nil {
- return err
- }
- p.statusRead = statusRead
- p.statusWrite = statusWrite
- // Add the parent-child pipes to cmd.ExtraFiles, offsetting all
- // existing file descriptors accordingly.
- extraFiles := make([]*os.File, len(p.c.ExtraFiles)+2)
- extraFiles[0] = dataRead
- extraFiles[1] = statusWrite
- for i, _ := range p.c.ExtraFiles {
- extraFiles[i+2] = p.c.ExtraFiles[i]
- }
- p.c.ExtraFiles = extraFiles
- // Start the child process.
- if err := p.c.Start(); err != nil {
- p.statusWrite.Close()
- p.statusRead.Close()
- return err
- }
- // Pass data to the child using a pipe.
- serializedConfig, err := p.config.Serialize()
- if err != nil {
- return err
- }
- if err := encodeString(dataWrite, serializedConfig); err != nil {
- p.statusWrite.Close()
- p.statusRead.Close()
- return err
- }
- if err := encodeString(dataWrite, p.secret); err != nil {
- p.statusWrite.Close()
- p.statusRead.Close()
- return err
- }
- return nil
-}
-
-func waitForStatus(c chan string, e chan error, r *os.File) {
- buf := make([]byte, 100)
- n, err := r.Read(buf)
- if err != nil {
- e <- err
- } else {
- c <- string(buf[:n])
- }
- r.Close()
- close(c)
- close(e)
-}
-
-// WaitForReady will wait for the child process to become ready.
-func (p *ParentHandle) WaitForReady(timeout time.Duration) error {
- defer p.statusWrite.Close()
- c := make(chan string, 1)
- e := make(chan error, 1)
- go waitForStatus(c, e, p.statusRead)
- for {
- select {
- case err := <-e:
- return err
- case st := <-c:
- if st == readyStatus {
- return nil
- }
- case <-p.tk.After(timeout):
- // Make sure that the read in waitForStatus
- // returns now.
- p.statusWrite.Write([]byte("quit"))
- return ErrTimeout
- }
- }
- panic("unreachable")
-}
-
-// Wait will wait for the child process to terminate of its own accord.
-// It returns nil if the process exited cleanly with an exit status of 0,
-// any other exit code or error will result in an appropriate error return
-func (p *ParentHandle) Wait(timeout time.Duration) error {
- c := make(chan error, 1)
- go func() {
- c <- p.c.Wait()
- close(c)
- }()
- // If timeout is zero time.After will panic; we handle zero specially
- // to mean infinite timeout.
- if timeout > 0 {
- select {
- case <-p.tk.After(timeout):
- return ErrTimeout
- case err := <-c:
- return err
- }
- } else {
- return <-c
- }
- panic("unreachable")
-}
-
-// Pid returns the pid of the child, 0 if the child process doesn't exist
-func (p *ParentHandle) Pid() int {
- if p.c.Process != nil {
- return p.c.Process.Pid
- }
- return 0
-}
-
-// Exists returns true if the child process exists and can be signal'ed
-func (p *ParentHandle) Exists() bool {
- if p.c.Process != nil {
- return syscall.Kill(p.c.Process.Pid, 0) == nil
- }
- return false
-}
-
-// Kill kills the child process.
-func (p *ParentHandle) Kill() error {
- return p.c.Process.Kill()
-}
-
-// Signal sends the given signal to the child process.
-func (p *ParentHandle) Signal(sig syscall.Signal) error {
- return syscall.Kill(p.c.Process.Pid, sig)
-}
-
-// Clean will clean up state, including killing the child process.
-func (p *ParentHandle) Clean() error {
- if err := p.Kill(); err != nil {
- return err
- }
- return p.c.Wait()
-}
-
-func encodeString(w io.Writer, data string) error {
- l := len(data)
- if err := binary.Write(w, binary.BigEndian, int64(l)); err != nil {
- return err
- }
- if n, err := w.Write([]byte(data)); err != nil || n != l {
- if err != nil {
- return err
- } else {
- return errors.New("partial write")
- }
- }
- return nil
-}
diff --git a/services/mgmt/lib/exec/shared.go b/services/mgmt/lib/exec/shared.go
deleted file mode 100644
index 936d2bf..0000000
--- a/services/mgmt/lib/exec/shared.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package exec
-
-const (
- version1 = "1.0.0"
- readyStatus = "ready"
- initStatus = "init"
- versionVariable = "VEYRON_EXEC_VERSION"
-)
diff --git a/services/mgmt/lib/exec/util.go b/services/mgmt/lib/exec/util.go
deleted file mode 100644
index 8ecbeb2..0000000
--- a/services/mgmt/lib/exec/util.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package exec
-
-import (
- "errors"
- "strings"
-)
-
-// Getenv retrieves the value of the given variable from the given
-// slice of environment variable assignments.
-func Getenv(env []string, name string) (string, error) {
- for _, v := range env {
- if strings.HasPrefix(v, name+"=") {
- return strings.TrimPrefix(v, name+"="), nil
- }
- }
- return "", errors.New("not found")
-}
-
-// Setenv updates / adds the value assignment for the given variable
-// in the given slice of environment variable assigments.
-func Setenv(env []string, name, value string) []string {
- newValue := name + "=" + value
- for i, v := range env {
- if strings.HasPrefix(v, name+"=") {
- env[i] = newValue
- return env
- }
- }
- return append(env, newValue)
-}
diff --git a/services/mgmt/lib/exec/util_test.go b/services/mgmt/lib/exec/util_test.go
deleted file mode 100644
index a247524..0000000
--- a/services/mgmt/lib/exec/util_test.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package exec
-
-import (
- "testing"
-)
-
-func TestEnv(t *testing.T) {
- env := make([]string, 0)
- env = Setenv(env, "NAME", "VALUE1")
- if expected, got := 1, len(env); expected != got {
- t.Fatalf("Unexpected length of environment variable slice: expected %d, got %d", expected, got)
- }
- if expected, got := "NAME=VALUE1", env[0]; expected != got {
- t.Fatalf("Unexpected element in the environment variable slice: expected %d, got %d", expected, got)
- }
- env = Setenv(env, "NAME", "VALUE2")
- if expected, got := 1, len(env); expected != got {
- t.Fatalf("Unexpected length of environment variable slice: expected %d, got %d", expected, got)
- }
- if expected, got := "NAME=VALUE2", env[0]; expected != got {
- t.Fatalf("Unexpected element in the environment variable slice: expected %d, got %d", expected, got)
- }
- value, err := Getenv(env, "NAME")
- if err != nil {
- t.Fatalf("Unexpected error when looking up environment variable value: %v", err)
- }
- if expected, got := "VALUE2", value; expected != got {
- t.Fatalf("Unexpected value of an environment variable: expected %d, got %d", expected, got)
- }
- value, err = Getenv(env, "NONAME")
- if err == nil {
- t.Fatalf("Expected error when looking up environment variable value, got none", value)
- }
-}
diff --git a/services/mgmt/node/impl/app_invoker.go b/services/mgmt/node/impl/app_invoker.go
index ddf2701..b7e3a9a 100644
--- a/services/mgmt/node/impl/app_invoker.go
+++ b/services/mgmt/node/impl/app_invoker.go
@@ -96,10 +96,6 @@
"strings"
"time"
- "veyron.io/veyron/veyron/lib/config"
- vexec "veyron.io/veyron/veyron/services/mgmt/lib/exec"
- iconfig "veyron.io/veyron/veyron/services/mgmt/node/config"
-
"veyron.io/veyron/veyron2/context"
"veyron.io/veyron/veyron2/ipc"
"veyron.io/veyron/veyron2/mgmt"
@@ -108,6 +104,9 @@
"veyron.io/veyron/veyron2/services/mgmt/appcycle"
"veyron.io/veyron/veyron2/services/mgmt/application"
"veyron.io/veyron/veyron2/vlog"
+
+ vexec "veyron.io/veyron/veyron/lib/exec"
+ iconfig "veyron.io/veyron/veyron/services/mgmt/node/config"
)
// instanceInfo holds state about a running instance.
@@ -470,7 +469,7 @@
callbackState := i.callback
listener := callbackState.listenFor(mgmt.AppCycleManagerConfigKey)
defer listener.cleanup()
- cfg := config.New()
+ cfg := vexec.NewConfig()
cfg.Set(mgmt.ParentNodeManagerConfigKey, listener.name())
handle := vexec.NewParentHandle(cmd, vexec.ConfigOpt{cfg})
defer func() {
diff --git a/services/mgmt/node/impl/callback.go b/services/mgmt/node/impl/callback.go
index 92ddf6f..46dbc21 100644
--- a/services/mgmt/node/impl/callback.go
+++ b/services/mgmt/node/impl/callback.go
@@ -3,12 +3,12 @@
import (
"time"
- "veyron.io/veyron/veyron/services/mgmt/lib/exec"
- "veyron.io/veyron/veyron/services/mgmt/node"
-
"veyron.io/veyron/veyron2/mgmt"
"veyron.io/veyron/veyron2/rt"
"veyron.io/veyron/veyron2/vlog"
+
+ "veyron.io/veyron/veyron/lib/exec"
+ "veyron.io/veyron/veyron/services/mgmt/node"
)
// InvokeCallback provides the parent node manager with the given name (which is
diff --git a/services/mgmt/node/impl/impl_test.go b/services/mgmt/node/impl/impl_test.go
index 139c5bd..879a34a 100644
--- a/services/mgmt/node/impl/impl_test.go
+++ b/services/mgmt/node/impl/impl_test.go
@@ -16,11 +16,11 @@
"testing"
"time"
+ "veyron.io/veyron/veyron/lib/exec"
"veyron.io/veyron/veyron/lib/signals"
"veyron.io/veyron/veyron/lib/testutil/blackbox"
tsecurity "veyron.io/veyron/veyron/lib/testutil/security"
vsecurity "veyron.io/veyron/veyron/security"
- "veyron.io/veyron/veyron/services/mgmt/lib/exec"
"veyron.io/veyron/veyron/services/mgmt/node/config"
"veyron.io/veyron/veyron/services/mgmt/node/impl"
suidhelper "veyron.io/veyron/veyron/services/mgmt/suidhelper/impl"
diff --git a/services/mgmt/node/impl/node_invoker.go b/services/mgmt/node/impl/node_invoker.go
index 965c34c..9db7b75 100644
--- a/services/mgmt/node/impl/node_invoker.go
+++ b/services/mgmt/node/impl/node_invoker.go
@@ -34,11 +34,6 @@
"sync"
"time"
- "veyron.io/veyron/veyron/lib/config"
- vexec "veyron.io/veyron/veyron/services/mgmt/lib/exec"
- iconfig "veyron.io/veyron/veyron/services/mgmt/node/config"
- "veyron.io/veyron/veyron/services/mgmt/profile"
-
"veyron.io/veyron/veyron2/context"
"veyron.io/veyron/veyron2/ipc"
"veyron.io/veyron/veyron2/mgmt"
@@ -49,6 +44,10 @@
"veyron.io/veyron/veyron2/services/mgmt/binary"
"veyron.io/veyron/veyron2/services/mgmt/node"
"veyron.io/veyron/veyron2/vlog"
+
+ vexec "veyron.io/veyron/veyron/lib/exec"
+ iconfig "veyron.io/veyron/veyron/services/mgmt/node/config"
+ "veyron.io/veyron/veyron/services/mgmt/profile"
)
type updatingState struct {
@@ -167,7 +166,7 @@
callbackState := i.callback
listener := callbackState.listenFor(mgmt.ChildNodeManagerConfigKey)
defer listener.cleanup()
- cfg := config.New()
+ cfg := vexec.NewConfig()
cfg.Set(mgmt.ParentNodeManagerConfigKey, listener.name())
handle := vexec.NewParentHandle(cmd, vexec.ConfigOpt{cfg})
// Start the child process.
diff --git a/services/mgmt/node/impl/util_test.go b/services/mgmt/node/impl/util_test.go
index 6d7e0f2..c107d42 100644
--- a/services/mgmt/node/impl/util_test.go
+++ b/services/mgmt/node/impl/util_test.go
@@ -13,10 +13,10 @@
"veyron.io/veyron/veyron2/verror"
"veyron.io/veyron/veyron2/vlog"
+ "veyron.io/veyron/veyron/lib/exec"
"veyron.io/veyron/veyron/lib/testutil/blackbox"
"veyron.io/veyron/veyron/lib/testutil/security"
"veyron.io/veyron/veyron/profiles"
- "veyron.io/veyron/veyron/services/mgmt/lib/exec"
mtlib "veyron.io/veyron/veyron/services/mounttable/lib"
)