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
+}