Merge "lib/testutil/integration: unexport some methods, remove StartServer"
diff --git a/lib/testutil/integration/util.go b/lib/testutil/integration/util.go
index 0044341..4f7eb6f 100644
--- a/lib/testutil/integration/util.go
+++ b/lib/testutil/integration/util.go
@@ -445,10 +445,6 @@
 	if err := s.Error(); err != nil {
 		return nil, "", err
 	}
-	s.ExpectVar("MT_ADDR")
-	if err := s.Error(); err != nil {
-		return nil, "", err
-	}
 
 	return handle, name, nil
 }
diff --git a/services/mgmt/sysinit/init_linux.go b/services/mgmt/sysinit/init_linux.go
new file mode 100644
index 0000000..beacc8e
--- /dev/null
+++ b/services/mgmt/sysinit/init_linux.go
@@ -0,0 +1,291 @@
+//
+// +build linux
+
+package sysinit
+
+// TODO(cnicolaou): will need to figure out a simple of way of handling the
+// different init systems supported by various versions of linux. One simple
+// option is to just include them in the name when installing - e.g.
+// simplevns-upstart.
+
+import (
+	"fmt"
+	"log"
+	"os"
+	"os/exec"
+)
+
+// action is a var so we can override it for testing.
+var action = func(command, action, service string) error {
+	output, err := exec.Command(command, action, service).CombinedOutput()
+	log.Printf("%s output: for %s %s: %s\n",
+		command, action, service, output)
+	return err
+}
+
+var (
+	upstartDir        = "/etc/init"
+	systemdDir        = "/usr/lib/systemd/system"
+	systemdTmpFileDir = "/usr/lib/tmpfiles.d"
+	dockerDir         = "/home/veyron/init"
+	logDir            = "/var/log/veyron"
+)
+
+// InitSystem attempts to determine what kind of init system is in use on
+// the platform that it is run on. It recognises upstart and systemd by
+// testing for the presence of the initctl and systemctl commands. upstart
+// is tested for first and hence is preferred in the unlikely case that both
+// are installed. Docker containers do not support upstart and systemd and
+// for them we have our own init system that uses the daemon command to
+// start/stop/respawn jobs.
+func InitSystem() string {
+	// NOTE(spetrovic): This check is kind of a hack. Ideally, we would
+	// detect a docker system by looking at the "container=lxc" environment
+	// variable.  However, we run sysinit during image creation, at which
+	// point we're on a native system and this variable isn't set.
+	if fi, err := os.Stat("/home/veyron/init"); err == nil && fi.Mode().IsDir() {
+		return "docker"
+	}
+	if fi, err := os.Stat("/sbin/initctl"); err == nil {
+		if (fi.Mode() & os.ModePerm & 0100) != 0 {
+			return "upstart"
+		}
+	}
+	if fi, err := os.Stat("/sbin/systemctl"); err == nil {
+		if (fi.Mode() & os.ModePerm & 0100) != 0 {
+			return "systemd"
+		}
+	}
+	if fi, err := os.Stat("/usr/bin/systemctl"); err == nil {
+		if (fi.Mode() & os.ModePerm & 0100) != 0 {
+			return "systemd"
+		}
+	}
+	return ""
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Upstart support
+
+// See http://upstart.ubuntu.com/cookbook/ for info on upstart
+
+// UpstartService is the implementation of InstallSystemInit interfacing with
+// the Upstart system.
+type UpstartService ServiceDescription
+
+var upstartTemplate = `# This file was auto-generated by the Veyron SysInit tool.
+# Date: {{.Date}}
+#
+# {{.Service}} - {{.Description}}
+#
+# Upstart config for Ubuntu-GCE
+
+description	"{{.Description}}"
+
+start on runlevel [2345]
+stop on runlevel [!2345]
+{{if .Environment}}
+# Environment variables
+{{range $var, $value := .Environment}}
+env {{$var}}={{$value}}{{end}}
+{{end}}
+respawn
+respawn limit 10 5
+umask 022
+
+pre-start script
+    test -x {{.Binary}} || { stop; exit 0; }
+    mkdir -p -m0755 /var/log/veyron
+    chown -R {{.User}} /var/log/veyron
+end script
+
+script
+  set -e
+  #  setuid {{.User}} - causes the subsequent exec to fail for unknown reasons.
+  echo '{{.Service}} starting'
+  exec{{range $cmd := .Command}} {{$cmd}}{{end}}
+end script
+`
+
+// Install implements the InstallSystemInit method.
+func (u *UpstartService) Install() error {
+	if u.Setup != nil {
+		if err := u.Setup((*ServiceDescription)(u)); err != nil {
+			return err
+		}
+	}
+	file := fmt.Sprintf("%s/%s.conf", upstartDir, u.Service)
+	return (*ServiceDescription)(u).writeTemplate(upstartTemplate, file)
+}
+
+// Print implements the InstallSystemInit method.
+func (u *UpstartService) Print() error {
+	return (*ServiceDescription)(u).writeTemplate(upstartTemplate, "")
+}
+
+// Uninstall implements the InstallSystemInit method.
+func (u *UpstartService) Uninstall() error {
+	if err := u.Stop(); err != nil {
+		return err
+	}
+	file := fmt.Sprintf("%s/%s.conf", upstartDir, u.Service)
+	return os.Remove(file)
+}
+
+// Start implements the InstallSystemInit method.
+func (u *UpstartService) Start() error {
+	return action("initctl", "start", u.Service)
+}
+
+// Stop implements the InstallSystemInit method.
+func (u *UpstartService) Stop() error {
+	return action("initctl", "stop", u.Service)
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Systemd support
+
+// SystemdService is the implementation of InstallSystemInit interfacing with
+// the Systemd system.
+type SystemdService ServiceDescription
+
+const systemdTemplate = `# This file was auto-generated by the Veyron SysInit tool.
+# Date: {{.Date}}
+#
+# {{.Service}} - {{.Description}}
+#
+[Unit]
+Description={{.Description}}
+After=openntpd.service
+
+[Service]
+User={{.User}}{{if .Environment}}{{println}}Environment={{range $var, $value := .Environment}}"{{$var}}={{$value}}" {{end}}{{end}}
+ExecStart={{range $cmd := .Command}}{{$cmd}} {{end}}
+ExecReload=/bin/kill -HUP $MAINPID
+KillMode=process
+Restart=always
+RestartSecs=10
+StandardOutput=syslog
+
+
+[Install]
+WantedBy=multi-user.target
+`
+
+// Install implements the InstallSystemInit method.
+func (s *SystemdService) Install() error {
+	if s.Setup != nil {
+		if err := s.Setup((*ServiceDescription)(s)); err != nil {
+			return err
+		}
+	}
+	file := fmt.Sprintf("%s/%s.service", systemdDir, s.Service)
+	if err := (*ServiceDescription)(s).writeTemplate(systemdTemplate, file); err != nil {
+		return err
+	}
+	file = fmt.Sprintf("%s/veyron.conf", systemdTmpFileDir)
+	f, err := os.Create(file)
+	if err != nil {
+		return err
+	}
+	f.WriteString("d /var/log/veyron 0755 veyron veyron\n")
+	f.Close()
+	err = action("systemd-tmpfiles", "--create", file)
+	if err != nil {
+		return err
+	}
+	return action("systemctl", "enable", s.Service)
+}
+
+// Print implements the InstallSystemInit method.
+func (s *SystemdService) Print() error {
+	return (*ServiceDescription)(s).writeTemplate(systemdTemplate, "")
+}
+
+// Uninstall implements the InstallSystemInit method.
+func (s *SystemdService) Uninstall() error {
+	if err := s.Stop(); err != nil {
+		return err
+	}
+	if err := action("systemctl", "disable", s.Service); err != nil {
+		return err
+	}
+	file := fmt.Sprintf("%s/%s.service", systemdDir, s.Service)
+	return os.Remove(file)
+}
+
+// Start implements the InstallSystemInit method.
+func (s *SystemdService) Start() error {
+	return action("systemctl", "start", s.Service)
+}
+
+// Stop implements the InstallSystemInit method.
+func (s *SystemdService) Stop() error {
+	return action("systemctl", "stop", s.Service)
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Docker support
+
+// DockerService is the implementation of InstallSystemInit interfacing with
+// Docker.
+type DockerService ServiceDescription
+
+const dockerTemplate = `#!/bin/bash
+# This file was auto-generated by the Veyron SysInit tool.
+# Date: {{.Date}}
+#
+# {{.Service}} - {{.Description}}
+#
+set -e
+{{if .Environment}}
+# Environment variables
+{{range $var, $value := .Environment}}export {{$var}}={{$value}}{{end}}
+{{end}}
+echo '{{.Service}} setup.'
+test -x {{.Binary}} || { stop; exit 0; }
+mkdir -p -m0755 /var/log/veyron
+chown -R {{.User}} /var/log/veyron
+
+echo '{{.Service}} starting'
+exec daemon -n {{.Service}} -r -A 2 -L 10 -M 5 -X '{{range $cmd := .Command}} {{$cmd}}{{end}}' &
+`
+
+// Install implements the InstallSystemInit method.
+func (s *DockerService) Install() error {
+	if s.Setup != nil {
+		if err := s.Setup((*ServiceDescription)(s)); err != nil {
+			return err
+		}
+	}
+	file := fmt.Sprintf("%s/%s.sh", dockerDir, s.Service)
+	if err := (*ServiceDescription)(s).writeTemplate(dockerTemplate, file); err != nil {
+		return err
+	}
+	os.Chmod(file, 0755)
+	return nil
+}
+
+// Print implements the InstallSystemInit method.
+func (s *DockerService) Print() error {
+	return (*ServiceDescription)(s).writeTemplate(dockerTemplate, "")
+}
+
+// Uninstall implements the InstallSystemInit method.
+func (s *DockerService) Uninstall() error {
+	if err := s.Stop(); err != nil {
+		return err
+	}
+	file := fmt.Sprintf("%s/%s.sh", dockerDir, s.Service)
+	return os.Remove(file)
+}
+
+// Start implements the InstallSystemInit method.
+func (s *DockerService) Start() error {
+	return action(fmt.Sprintf("%s/%s.sh", dockerDir, s.Service), "", s.Service)
+}
+
+// Stop implements the InstallSystemInit method.
+func (s *DockerService) Stop() error {
+	return action("daemon", fmt.Sprintf("-n %s --stop", s.Service), s.Service)
+}
diff --git a/services/mgmt/sysinit/linux_test.go b/services/mgmt/sysinit/linux_test.go
new file mode 100644
index 0000000..a926229
--- /dev/null
+++ b/services/mgmt/sysinit/linux_test.go
@@ -0,0 +1,127 @@
+//
+// +build linux
+
+package sysinit
+
+import (
+	"bufio"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+	"testing"
+	"time"
+)
+
+func TestUpstart(t *testing.T) {
+	oldTemplate := upstartTemplate
+	upstartTemplate = `{{.Date}}
+{{.Service}}
+{{.Description}}
+{{.Binary}}
+{{.Command}}
+`
+	oldUpstartDir := upstartDir
+	upstartDir, _ = ioutil.TempDir(".", "etc-init")
+
+	defer func() {
+		upstartTemplate = oldTemplate
+		upstartDir = oldUpstartDir
+	}()
+
+	defer os.RemoveAll(upstartDir)
+	serviceName := ""
+	u := &UpstartService{
+		Service:     "tester",
+		Description: "my test",
+		Binary:      "/bin/echo",
+		Command:     []string{"/bin/echo -n foo"},
+		Setup: func(s *ServiceDescription) error {
+			serviceName = s.Service
+			return nil
+		},
+	}
+	if err := u.Install(); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	if serviceName != "tester" {
+		t.Errorf("Setup was not called")
+	}
+	rc, _ := os.Open(upstartDir + "/tester.conf")
+	lines := bufio.NewScanner(rc)
+	lines.Scan()
+	timestr := lines.Text()
+	_, err := time.Parse(dateFormat, timestr)
+	if err != nil {
+		t.Fatalf("unexpected error parsing time: %s, err: %s", t, err)
+	}
+	lines.Scan()
+	if lines.Text() != "tester" {
+		t.Fatalf("unexpected output: %s", lines.Text())
+	}
+	lines.Scan()
+	lines.Scan()
+	if lines.Text() != "/bin/echo" {
+		t.Fatalf("unexpected output: %s", lines.Text())
+	}
+	lines.Scan()
+	if lines.Scan() {
+		t.Fatalf("failed to find end of file")
+	}
+}
+
+func TestSystemd(t *testing.T) {
+	s := &SystemdService{
+		Service:     "tester",
+		Description: "my test",
+		Binary:      "/bin/echo",
+		Command:     []string{"/bin/echo", "-n", "foo"},
+	}
+
+	oldSystemdDir := systemdDir
+	oldSystemdTmpFileDir := systemdTmpFileDir
+	oldAction := action
+
+	systemdDir, _ = ioutil.TempDir(".", "usr-lib-systemd-system")
+	defer os.RemoveAll(systemdDir)
+	systemdTmpFileDir, _ = ioutil.TempDir(".", "usr-lib-tmpfiles.d")
+	defer os.RemoveAll(systemdTmpFileDir)
+
+	var cmd, act, srv string
+	action = func(command, action, service string) error {
+		cmd, act, srv = command, action, service
+		return nil
+	}
+
+	defer func() {
+		systemdDir = oldSystemdDir
+		systemdTmpFileDir = oldSystemdTmpFileDir
+		action = oldAction
+	}()
+
+	if err := s.Install(); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+
+	if want, got := "systemctl", cmd; want != got {
+		t.Errorf("action command: want %q, got %q", want, got)
+	}
+	if want, got := "enable", act; want != got {
+		t.Errorf("action action: want %q, got %q", want, got)
+	}
+	if want, got := "tester", srv; want != got {
+		t.Errorf("action service: want %q, got %q", want, got)
+	}
+
+	c, err := ioutil.ReadFile(filepath.Join(systemdDir, "tester.service"))
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	contents := string(c)
+	if !strings.Contains(contents, "Description=my test") {
+		t.Errorf("bad Description in generated service spec: ", contents)
+	}
+	if !strings.Contains(contents, "ExecStart=/bin/echo -n foo") {
+		t.Errorf("bad ExecStart in generated service spec: ", contents)
+	}
+}
diff --git a/services/mgmt/sysinit/service_description.go b/services/mgmt/sysinit/service_description.go
new file mode 100644
index 0000000..925c8c1
--- /dev/null
+++ b/services/mgmt/sysinit/service_description.go
@@ -0,0 +1,45 @@
+package sysinit
+
+import (
+	"os"
+	"text/template"
+	"time"
+)
+
+const dateFormat = "Jan 2 2006 at 15:04:05 (MST)"
+
+// ServiceDescription is a generic service description that represents the
+// common configuration details for specific systems.
+type ServiceDescription struct {
+	Service     string                          // The name of the Service
+	Description string                          // A description of the Service
+	Environment map[string]string               // Environment variables needed by the service
+	Binary      string                          // The binary to be run
+	Command     []string                        // The script/binary and command line options to use to start/stop the binary
+	User        string                          // The username this service is to run as
+	Setup       func(*ServiceDescription) error // Optional function to run before install.
+	SetupParams map[string]string               // Params for the Setup function.
+}
+
+func (sd *ServiceDescription) writeTemplate(templateContents, file string) error {
+	conf, err := template.New(sd.Service + ".template").Parse(templateContents)
+	if err != nil {
+		return err
+	}
+	w := os.Stdout
+	if len(file) > 0 {
+		w, err = os.Create(file)
+		if err != nil {
+			return err
+		}
+	}
+	type tmp struct {
+		*ServiceDescription
+		Date string
+	}
+	data := &tmp{
+		ServiceDescription: sd,
+		Date:               time.Now().Format(dateFormat),
+	}
+	return conf.Execute(w, &data)
+}
diff --git a/services/mgmt/sysinit/sysinit.go b/services/mgmt/sysinit/sysinit.go
new file mode 100644
index 0000000..ca73c84
--- /dev/null
+++ b/services/mgmt/sysinit/sysinit.go
@@ -0,0 +1,13 @@
+// Package sysinit provides config generation for a variety of platforms and
+// "init" systems such as upstart, systemd etc. It is intended purely for
+// bootstrapping into the Veyron system proper.
+package sysinit
+
+// InstallSystemInit defines the interface that all configs must implement.
+type InstallSystemInit interface {
+	Print() error
+	Install() error
+	Uninstall() error
+	Start() error
+	Stop() error
+}