Browse Source

Merge pull request #2494 from shykes/engine-links

Engine: Minimal, unintrusive implementation of a cleaner Job API.
Victor Vieux 11 years ago
parent
commit
e3c49843d7
15 changed files with 468 additions and 129 deletions
  1. 26 1
      config.go
  2. 17 105
      docker/docker.go
  3. 1 0
      engine/MAINTAINERS
  4. 82 0
      engine/engine.go
  5. 29 0
      engine/env_test.go
  6. 42 0
      engine/init_test.go
  7. 113 0
      engine/job.go
  8. 29 0
      netlink/netlink_darwin.go
  9. 0 0
      netlink/netlink_linux.go
  10. 5 13
      runtime.go
  11. 3 3
      runtime_test.go
  12. 68 6
      server.go
  13. 36 0
      utils/daemon.go
  14. 16 0
      utils/random.go
  15. 1 1
      utils_test.go

+ 26 - 1
config.go

@@ -2,11 +2,13 @@ package docker
 
 import (
 	"net"
+	"github.com/dotcloud/docker/engine"
 )
 
+// FIXME: separate runtime configuration from http api configuration
 type DaemonConfig struct {
 	Pidfile                     string
-	GraphPath                   string
+	Root                        string
 	ProtoAddresses              []string
 	AutoRestart                 bool
 	EnableCors                  bool
@@ -16,3 +18,26 @@ type DaemonConfig struct {
 	DefaultIp                   net.IP
 	InterContainerCommunication bool
 }
+
+// ConfigFromJob creates and returns a new DaemonConfig object
+// by parsing the contents of a job's environment.
+func ConfigFromJob(job *engine.Job) *DaemonConfig {
+	var config DaemonConfig
+	config.Pidfile = job.Getenv("Pidfile")
+	config.Root = job.Getenv("Root")
+	config.AutoRestart = job.GetenvBool("AutoRestart")
+	config.EnableCors = job.GetenvBool("EnableCors")
+	if dns := job.Getenv("Dns"); dns != "" {
+		config.Dns = []string{dns}
+	}
+	config.EnableIptables = job.GetenvBool("EnableIptables")
+	if br := job.Getenv("BridgeIface"); br != "" {
+		config.BridgeIface = br
+	} else {
+		config.BridgeIface = DefaultNetworkBridge
+	}
+	config.ProtoAddresses = job.GetenvList("ProtoAddresses")
+	config.DefaultIp = net.ParseIP(job.Getenv("DefaultIp"))
+	config.InterContainerCommunication = job.GetenvBool("InterContainerCommunication")
+	return &config
+}

+ 17 - 105
docker/docker.go

@@ -6,14 +6,10 @@ import (
 	"github.com/dotcloud/docker"
 	"github.com/dotcloud/docker/sysinit"
 	"github.com/dotcloud/docker/utils"
-	"io/ioutil"
+	"github.com/dotcloud/docker/engine"
 	"log"
-	"net"
 	"os"
-	"os/signal"
-	"strconv"
 	"strings"
-	"syscall"
 )
 
 var (
@@ -34,7 +30,7 @@ func main() {
 	flAutoRestart := flag.Bool("r", true, "Restart previously running containers")
 	bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge. Use 'none' to disable container networking")
 	pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
-	flGraphPath := flag.String("g", "/var/lib/docker", "Path to graph storage base dir.")
+	flRoot := flag.String("g", "/var/lib/docker", "Path to use as the root of the docker runtime.")
 	flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
 	flDns := flag.String("dns", "", "Set custom dns servers")
 	flHosts := utils.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)}
@@ -61,10 +57,6 @@ func main() {
 		}
 	}
 
-	bridge := docker.DefaultNetworkBridge
-	if *bridgeName != "" {
-		bridge = *bridgeName
-	}
 	if *flDebug {
 		os.Setenv("DEBUG", "1")
 	}
@@ -75,26 +67,22 @@ func main() {
 			flag.Usage()
 			return
 		}
-		var dns []string
-		if *flDns != "" {
-			dns = []string{*flDns}
-		}
-
-		ip := net.ParseIP(*flDefaultIp)
-
-		config := &docker.DaemonConfig{
-			Pidfile:                     *pidfile,
-			GraphPath:                   *flGraphPath,
-			AutoRestart:                 *flAutoRestart,
-			EnableCors:                  *flEnableCors,
-			Dns:                         dns,
-			EnableIptables:              *flEnableIptables,
-			BridgeIface:                 bridge,
-			ProtoAddresses:              flHosts,
-			DefaultIp:                   ip,
-			InterContainerCommunication: *flInterContainerComm,
+		eng, err := engine.New(*flRoot)
+		if err != nil {
+			log.Fatal(err)
 		}
-		if err := daemon(config); err != nil {
+		job := eng.Job("serveapi")
+		job.Setenv("Pidfile", *pidfile)
+		job.Setenv("Root", *flRoot)
+		job.SetenvBool("AutoRestart", *flAutoRestart)
+		job.SetenvBool("EnableCors", *flEnableCors)
+		job.Setenv("Dns", *flDns)
+		job.SetenvBool("EnableIptables", *flEnableIptables)
+		job.Setenv("BridgeIface", *bridgeName)
+		job.SetenvList("ProtoAddresses", flHosts)
+		job.Setenv("DefaultIp", *flDefaultIp)
+		job.SetenvBool("InterContainerCommunication", *flInterContainerComm)
+		if err := job.Run(); err != nil {
 			log.Fatal(err)
 		}
 	} else {
@@ -114,79 +102,3 @@ func main() {
 func showVersion() {
 	fmt.Printf("Docker version %s, build %s\n", VERSION, GITCOMMIT)
 }
-
-func createPidFile(pidfile string) error {
-	if pidString, err := ioutil.ReadFile(pidfile); err == nil {
-		pid, err := strconv.Atoi(string(pidString))
-		if err == nil {
-			if _, err := os.Stat(fmt.Sprintf("/proc/%d/", pid)); err == nil {
-				return fmt.Errorf("pid file found, ensure docker is not running or delete %s", pidfile)
-			}
-		}
-	}
-
-	file, err := os.Create(pidfile)
-	if err != nil {
-		return err
-	}
-
-	defer file.Close()
-
-	_, err = fmt.Fprintf(file, "%d", os.Getpid())
-	return err
-}
-
-func removePidFile(pidfile string) {
-	if err := os.Remove(pidfile); err != nil {
-		log.Printf("Error removing %s: %s", pidfile, err)
-	}
-}
-
-func daemon(config *docker.DaemonConfig) error {
-	if err := createPidFile(config.Pidfile); err != nil {
-		log.Fatal(err)
-	}
-	defer removePidFile(config.Pidfile)
-
-	server, err := docker.NewServer(config)
-	if err != nil {
-		return err
-	}
-	defer server.Close()
-
-	c := make(chan os.Signal, 1)
-	signal.Notify(c, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM))
-	go func() {
-		sig := <-c
-		log.Printf("Received signal '%v', exiting\n", sig)
-		server.Close()
-		removePidFile(config.Pidfile)
-		os.Exit(0)
-	}()
-
-	chErrors := make(chan error, len(config.ProtoAddresses))
-	for _, protoAddr := range config.ProtoAddresses {
-		protoAddrParts := strings.SplitN(protoAddr, "://", 2)
-		if protoAddrParts[0] == "unix" {
-			syscall.Unlink(protoAddrParts[1])
-		} else if protoAddrParts[0] == "tcp" {
-			if !strings.HasPrefix(protoAddrParts[1], "127.0.0.1") {
-				log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
-			}
-		} else {
-			server.Close()
-			removePidFile(config.Pidfile)
-			log.Fatal("Invalid protocol format.")
-		}
-		go func() {
-			chErrors <- docker.ListenAndServe(protoAddrParts[0], protoAddrParts[1], server, true)
-		}()
-	}
-	for i := 0; i < len(config.ProtoAddresses); i += 1 {
-		err := <-chErrors
-		if err != nil {
-			return err
-		}
-	}
-	return nil
-}

+ 1 - 0
engine/MAINTAINERS

@@ -0,0 +1 @@
+Solomon Hykes <solomon@dotcloud.com>

+ 82 - 0
engine/engine.go

@@ -0,0 +1,82 @@
+package engine
+
+import (
+	"fmt"
+	"os"
+	"log"
+	"runtime"
+	"github.com/dotcloud/docker/utils"
+)
+
+
+type Handler func(*Job) string
+
+var globalHandlers map[string]Handler
+
+func Register(name string, handler Handler) error {
+	if globalHandlers == nil {
+		globalHandlers = make(map[string]Handler)
+	}
+	globalHandlers[name] = handler
+	return nil
+}
+
+// The Engine is the core of Docker.
+// It acts as a store for *containers*, and allows manipulation of these
+// containers by executing *jobs*.
+type Engine struct {
+	root		string
+	handlers	map[string]Handler
+}
+
+// New initializes a new engine managing the directory specified at `root`.
+// `root` is used to store containers and any other state private to the engine.
+// Changing the contents of the root without executing a job will cause unspecified
+// behavior.
+func New(root string) (*Engine, error) {
+	// Check for unsupported architectures
+	if runtime.GOARCH != "amd64" {
+		return nil, fmt.Errorf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH)
+	}
+	// Check for unsupported kernel versions
+	// FIXME: it would be cleaner to not test for specific versions, but rather
+	// test for specific functionalities.
+	// Unfortunately we can't test for the feature "does not cause a kernel panic"
+	// without actually causing a kernel panic, so we need this workaround until
+	// the circumstances of pre-3.8 crashes are clearer.
+	// For details see http://github.com/dotcloud/docker/issues/407
+	if k, err := utils.GetKernelVersion(); err != nil {
+		log.Printf("WARNING: %s\n", err)
+	} else {
+		if utils.CompareKernelVersion(k, &utils.KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}) < 0 {
+			log.Printf("WARNING: You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.8.0.", k.String())
+		}
+	}
+	if err := os.MkdirAll(root, 0700); err != nil && !os.IsExist(err) {
+		return nil, err
+	}
+	eng := &Engine{
+		root:		root,
+		handlers:	globalHandlers,
+	}
+	return eng, nil
+}
+
+// Job creates a new job which can later be executed.
+// This function mimics `Command` from the standard os/exec package.
+func (eng *Engine) Job(name string, args ...string) *Job {
+	job := &Job{
+		eng:		eng,
+		Name:		name,
+		Args:		args,
+		Stdin:		os.Stdin,
+		Stdout:		os.Stdout,
+		Stderr:		os.Stderr,
+	}
+	handler, exists := eng.handlers[name]
+	if exists {
+		job.handler = handler
+	}
+	return job
+}
+

+ 29 - 0
engine/env_test.go

@@ -0,0 +1,29 @@
+package engine
+
+import (
+	"testing"
+)
+
+func TestNewJob(t *testing.T) {
+	job := mkJob(t, "dummy", "--level=awesome")
+	if job.Name != "dummy" {
+		t.Fatalf("Wrong job name: %s", job.Name)
+	}
+	if len(job.Args) != 1 {
+		t.Fatalf("Wrong number of job arguments: %d", len(job.Args))
+	}
+	if job.Args[0] != "--level=awesome" {
+		t.Fatalf("Wrong job arguments: %s", job.Args[0])
+	}
+}
+
+func TestSetenv(t *testing.T) {
+	job := mkJob(t, "dummy")
+	job.Setenv("foo", "bar")
+	if val := job.Getenv("foo"); val != "bar" {
+		t.Fatalf("Getenv returns incorrect value: %s", val)
+	}
+	if val := job.Getenv("nonexistent"); val != "" {
+		t.Fatalf("Getenv returns incorrect value: %s", val)
+	}
+}

+ 42 - 0
engine/init_test.go

@@ -0,0 +1,42 @@
+package engine
+
+import (
+	"testing"
+	"runtime"
+	"strings"
+	"fmt"
+	"io/ioutil"
+	"github.com/dotcloud/docker/utils"
+)
+
+var globalTestID string
+
+func init() {
+	Register("dummy", func(job *Job) string { return ""; })
+}
+
+func mkEngine(t *testing.T) *Engine {
+	// Use the caller function name as a prefix.
+	// This helps trace temp directories back to their test.
+	pc, _, _, _ := runtime.Caller(1)
+	callerLongName := runtime.FuncForPC(pc).Name()
+	parts := strings.Split(callerLongName, ".")
+	callerShortName := parts[len(parts)-1]
+	if globalTestID == "" {
+		globalTestID = utils.RandomString()[:4]
+	}
+	prefix := fmt.Sprintf("docker-test%s-%s-", globalTestID, callerShortName)
+	root, err := ioutil.TempDir("", prefix)
+	if err != nil {
+		t.Fatal(err)
+	}
+	eng, err := New(root)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return eng
+}
+
+func mkJob(t *testing.T, name string, args ...string) *Job {
+	return mkEngine(t).Job(name, args...)
+}

+ 113 - 0
engine/job.go

@@ -0,0 +1,113 @@
+package engine
+
+import (
+	"io"
+	"strings"
+	"fmt"
+	"encoding/json"
+	"github.com/dotcloud/docker/utils"
+)
+
+// A job is the fundamental unit of work in the docker engine.
+// Everything docker can do should eventually be exposed as a job.
+// For example: execute a process in a container, create a new container,
+// download an archive from the internet, serve the http api, etc.
+//
+// The job API is designed after unix processes: a job has a name, arguments,
+// environment variables, standard streams for input, output and error, and
+// an exit status which can indicate success (0) or error (anything else).
+//
+// One slight variation is that jobs report their status as a string. The
+// string "0" indicates success, and any other strings indicates an error.
+// This allows for richer error reporting.
+// 
+type Job struct {
+	eng	*Engine
+	Name	string
+	Args	[]string
+	env	[]string
+	Stdin	io.ReadCloser
+	Stdout	io.WriteCloser
+	Stderr	io.WriteCloser
+	handler	func(*Job) string
+	status	string
+}
+
+// Run executes the job and blocks until the job completes.
+// If the job returns a failure status, an error is returned
+// which includes the status.
+func (job *Job) Run() error {
+	randId := utils.RandomString()[:4]
+	fmt.Printf("Job #%s: %s\n", randId, job)
+	defer fmt.Printf("Job #%s: %s = '%s'", randId, job, job.status)
+	if job.handler == nil {
+		job.status = "command not found"
+	} else {
+		job.status = job.handler(job)
+	}
+	if job.status != "0" {
+		return fmt.Errorf("%s: %s", job.Name, job.status)
+	}
+	return nil
+}
+
+// String returns a human-readable description of `job`
+func (job *Job) String() string {
+	return strings.Join(append([]string{job.Name}, job.Args...), " ")
+}
+
+func (job *Job) Getenv(key string) (value string) {
+        for _, kv := range job.env {
+                if strings.Index(kv, "=") == -1 {
+                        continue
+                }
+                parts := strings.SplitN(kv, "=", 2)
+                if parts[0] != key {
+                        continue
+                }
+                if len(parts) < 2 {
+                        value = ""
+                } else {
+                        value = parts[1]
+                }
+        }
+        return
+}
+
+func (job *Job) GetenvBool(key string) (value bool) {
+	s := strings.ToLower(strings.Trim(job.Getenv(key), " \t"))
+	if s == "" || s == "0" || s == "no" || s == "false" || s == "none" {
+		return false
+	}
+	return true
+}
+
+func (job *Job) SetenvBool(key string, value bool) {
+	if value {
+		job.Setenv(key, "1")
+	} else {
+		job.Setenv(key, "0")
+	}
+}
+
+func (job *Job) GetenvList(key string) []string {
+	sval := job.Getenv(key)
+	l := make([]string, 0, 1)
+	if err := json.Unmarshal([]byte(sval), &l); err != nil {
+		l = append(l, sval)
+	}
+	return l
+}
+
+func (job *Job) SetenvList(key string, value []string) error {
+	sval, err := json.Marshal(value)
+	if err != nil {
+		return err
+	}
+	job.Setenv(key, string(sval))
+	return nil
+}
+
+func (job *Job) Setenv(key, value string) {
+	job.env = append(job.env, key + "=" + value)
+}

+ 29 - 0
netlink/netlink_darwin.go

@@ -0,0 +1,29 @@
+package netlink
+
+import (
+	"fmt"
+	"net"
+)
+
+func NetworkGetRoutes() ([]*net.IPNet, error) {
+	return nil, fmt.Errorf("Not implemented")
+}
+
+
+func NetworkLinkAdd(name string, linkType string) error {
+	return fmt.Errorf("Not implemented")
+}
+
+func NetworkLinkUp(iface *net.Interface) error {
+	return fmt.Errorf("Not implemented")
+}
+
+
+func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error {
+	return fmt.Errorf("Not implemented")
+}
+
+func AddDefaultGw(ip net.IP) error {
+	return fmt.Errorf("Not implemented")
+
+}

+ 0 - 0
netlink/netlink.go → netlink/netlink_linux.go


+ 5 - 13
runtime.go

@@ -563,34 +563,26 @@ func NewRuntime(config *DaemonConfig) (*Runtime, error) {
 	if err != nil {
 		return nil, err
 	}
-
-	if k, err := utils.GetKernelVersion(); err != nil {
-		log.Printf("WARNING: %s\n", err)
-	} else {
-		if utils.CompareKernelVersion(k, &utils.KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}) < 0 {
-			log.Printf("WARNING: You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.8.0.", k.String())
-		}
-	}
 	runtime.UpdateCapabilities(false)
 	return runtime, nil
 }
 
 func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
-	runtimeRepo := path.Join(config.GraphPath, "containers")
+	runtimeRepo := path.Join(config.Root, "containers")
 
 	if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) {
 		return nil, err
 	}
 
-	g, err := NewGraph(path.Join(config.GraphPath, "graph"))
+	g, err := NewGraph(path.Join(config.Root, "graph"))
 	if err != nil {
 		return nil, err
 	}
-	volumes, err := NewGraph(path.Join(config.GraphPath, "volumes"))
+	volumes, err := NewGraph(path.Join(config.Root, "volumes"))
 	if err != nil {
 		return nil, err
 	}
-	repositories, err := NewTagStore(path.Join(config.GraphPath, "repositories"), g)
+	repositories, err := NewTagStore(path.Join(config.Root, "repositories"), g)
 	if err != nil {
 		return nil, fmt.Errorf("Couldn't create Tag store: %s", err)
 	}
@@ -602,7 +594,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
 		return nil, err
 	}
 
-	gographPath := path.Join(config.GraphPath, "linkgraph.db")
+	gographPath := path.Join(config.Root, "linkgraph.db")
 	initDatabase := false
 	if _, err := os.Stat(gographPath); err != nil {
 		if os.IsNotExist(err) {

+ 3 - 3
runtime_test.go

@@ -46,8 +46,8 @@ func nuke(runtime *Runtime) error {
 	wg.Wait()
 	runtime.Close()
 
-	os.Remove(filepath.Join(runtime.config.GraphPath, "linkgraph.db"))
-	return os.RemoveAll(runtime.config.GraphPath)
+	os.Remove(filepath.Join(runtime.config.Root, "linkgraph.db"))
+	return os.RemoveAll(runtime.config.Root)
 }
 
 func cleanup(runtime *Runtime) error {
@@ -119,7 +119,7 @@ func init() {
 
 func setupBaseImage() {
 	config := &DaemonConfig{
-		GraphPath:   unitTestStoreBase,
+		Root:   unitTestStoreBase,
 		AutoRestart: false,
 		BridgeIface: unitTestNetworkBridge,
 	}

+ 68 - 6
server.go

@@ -9,6 +9,7 @@ import (
 	"github.com/dotcloud/docker/gograph"
 	"github.com/dotcloud/docker/registry"
 	"github.com/dotcloud/docker/utils"
+	"github.com/dotcloud/docker/engine"
 	"io"
 	"io/ioutil"
 	"log"
@@ -22,12 +23,76 @@ import (
 	"strings"
 	"sync"
 	"time"
+	"syscall"
+	"os/signal"
 )
 
 func (srv *Server) Close() error {
 	return srv.runtime.Close()
 }
 
+func init() {
+	engine.Register("serveapi", JobServeApi)
+}
+
+func JobServeApi(job *engine.Job) string {
+	srv, err := NewServer(ConfigFromJob(job))
+	if err != nil {
+		return err.Error()
+	}
+	defer srv.Close()
+	if err := srv.Daemon(); err != nil {
+		return err.Error()
+	}
+	return "0"
+}
+
+// Daemon runs the remote api server `srv` as a daemon,
+// Only one api server can run at the same time - this is enforced by a pidfile.
+// The signals SIGINT, SIGKILL and SIGTERM are intercepted for cleanup.
+func (srv *Server) Daemon() error {
+	if err := utils.CreatePidFile(srv.runtime.config.Pidfile); err != nil {
+		log.Fatal(err)
+	}
+	defer utils.RemovePidFile(srv.runtime.config.Pidfile)
+
+	c := make(chan os.Signal, 1)
+	signal.Notify(c, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM))
+	go func() {
+		sig := <-c
+		log.Printf("Received signal '%v', exiting\n", sig)
+		utils.RemovePidFile(srv.runtime.config.Pidfile)
+		srv.Close()
+		os.Exit(0)
+	}()
+
+	protoAddrs := srv.runtime.config.ProtoAddresses
+	chErrors := make(chan error, len(protoAddrs))
+	for _, protoAddr := range protoAddrs {
+		protoAddrParts := strings.SplitN(protoAddr, "://", 2)
+		if protoAddrParts[0] == "unix" {
+			syscall.Unlink(protoAddrParts[1])
+		} else if protoAddrParts[0] == "tcp" {
+			if !strings.HasPrefix(protoAddrParts[1], "127.0.0.1") {
+				log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
+			}
+		} else {
+			return fmt.Errorf("Invalid protocol format.")
+		}
+		go func() {
+			chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], srv, true)
+		}()
+	}
+	for i := 0; i < len(protoAddrs); i += 1 {
+		err := <-chErrors
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+
 func (srv *Server) DockerVersion() APIVersion {
 	return APIVersion{
 		Version:   VERSION,
@@ -119,7 +184,7 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
 }
 
 func (srv *Server) ImagesSearch(term string) ([]APISearch, error) {
-	r, err := registry.NewRegistry(srv.runtime.config.GraphPath, nil, srv.HTTPRequestFactory(nil))
+	r, err := registry.NewRegistry(srv.runtime.config.Root, nil, srv.HTTPRequestFactory(nil))
 	if err != nil {
 		return nil, err
 	}
@@ -664,7 +729,7 @@ func (srv *Server) poolRemove(kind, key string) error {
 }
 
 func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig, metaHeaders map[string][]string, parallel bool) error {
-	r, err := registry.NewRegistry(srv.runtime.config.GraphPath, authConfig, srv.HTTPRequestFactory(metaHeaders))
+	r, err := registry.NewRegistry(srv.runtime.config.Root, authConfig, srv.HTTPRequestFactory(metaHeaders))
 	if err != nil {
 		return err
 	}
@@ -873,7 +938,7 @@ func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFo
 
 	out = utils.NewWriteFlusher(out)
 	img, err := srv.runtime.graph.Get(localName)
-	r, err2 := registry.NewRegistry(srv.runtime.config.GraphPath, authConfig, srv.HTTPRequestFactory(metaHeaders))
+	r, err2 := registry.NewRegistry(srv.runtime.config.Root, authConfig, srv.HTTPRequestFactory(metaHeaders))
 	if err2 != nil {
 		return err2
 	}
@@ -1410,9 +1475,6 @@ func (srv *Server) ContainerCopy(name string, resource string, out io.Writer) er
 }
 
 func NewServer(config *DaemonConfig) (*Server, error) {
-	if runtime.GOARCH != "amd64" {
-		log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH)
-	}
 	runtime, err := NewRuntime(config)
 	if err != nil {
 		return nil, err

+ 36 - 0
utils/daemon.go

@@ -0,0 +1,36 @@
+package utils
+
+import (
+	"os"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"strconv"
+)
+
+func CreatePidFile(pidfile string) error {
+	if pidString, err := ioutil.ReadFile(pidfile); err == nil {
+		pid, err := strconv.Atoi(string(pidString))
+		if err == nil {
+			if _, err := os.Stat(fmt.Sprintf("/proc/%d/", pid)); err == nil {
+				return fmt.Errorf("pid file found, ensure docker is not running or delete %s", pidfile)
+			}
+		}
+	}
+
+	file, err := os.Create(pidfile)
+	if err != nil {
+		return err
+	}
+
+	defer file.Close()
+
+	_, err = fmt.Fprintf(file, "%d", os.Getpid())
+	return err
+}
+
+func RemovePidFile(pidfile string) {
+	if err := os.Remove(pidfile); err != nil {
+		log.Printf("Error removing %s: %s", pidfile, err)
+	}
+}

+ 16 - 0
utils/random.go

@@ -0,0 +1,16 @@
+package utils
+
+import (
+	"io"
+	"crypto/rand"
+	"encoding/hex"
+)
+
+func RandomString() string {
+	id := make([]byte, 32)
+	_, err := io.ReadFull(rand.Reader, id)
+	if err != nil {
+		panic(err) // This shouldn't happen
+	}
+	return hex.EncodeToString(id)
+}

+ 1 - 1
utils_test.go

@@ -67,7 +67,7 @@ func newTestRuntime(prefix string) (runtime *Runtime, err error) {
 	}
 
 	config := &DaemonConfig{
-		GraphPath:   root,
+		Root:   root,
 		AutoRestart: false,
 	}
 	runtime, err = NewRuntimeFromDirectory(config)