From af06b61d3646b4cf7b01520611c4c29e97767536 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 24 Oct 2013 22:45:37 -0600 Subject: [PATCH 01/10] Fix build on darwin Upstream-commit: 60b97576cfc2e43bfc5d09fa8ea2b71777d1f2b3 Component: engine --- components/engine/netlink/netlink_darwin.go | 29 +++++++++++++++++++ .../netlink/{netlink.go => netlink_linux.go} | 0 2 files changed, 29 insertions(+) create mode 100644 components/engine/netlink/netlink_darwin.go rename components/engine/netlink/{netlink.go => netlink_linux.go} (100%) diff --git a/components/engine/netlink/netlink_darwin.go b/components/engine/netlink/netlink_darwin.go new file mode 100644 index 0000000000..becfb87759 --- /dev/null +++ b/components/engine/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") + +} diff --git a/components/engine/netlink/netlink.go b/components/engine/netlink/netlink_linux.go similarity index 100% rename from components/engine/netlink/netlink.go rename to components/engine/netlink/netlink_linux.go From 2bbe323db0bb376b2f3ea16b4a402e1551c677f2 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 21 Oct 2013 10:04:42 -0600 Subject: [PATCH 02/10] Minimal, unintrusive implementation of a cleaner Job API. * Implement a new package: engine. It exposes a useful but minimalist job API. * Refactor main() to instanciate an Engine instead of a Server directly. * Refactor server.go to register an engine job. This is the smallest possible refactor which can include the new Engine design into master. More gradual refactoring will follow. Upstream-commit: 0d1a825137448e2f41e5aaa5ecae8094c8ab6817 Component: engine --- components/engine/config.go | 27 +++++++ components/engine/docker/docker.go | 86 +++++++--------------- components/engine/engine/MAINTAINERS | 1 + components/engine/engine/engine.go | 62 ++++++++++++++++ components/engine/engine/job.go | 105 +++++++++++++++++++++++++++ components/engine/server.go | 39 ++++++++++ 6 files changed, 260 insertions(+), 60 deletions(-) create mode 100644 components/engine/engine/MAINTAINERS create mode 100644 components/engine/engine/engine.go create mode 100644 components/engine/engine/job.go diff --git a/components/engine/config.go b/components/engine/config.go index 8f2d22c255..a572cfadaf 100644 --- a/components/engine/config.go +++ b/components/engine/config.go @@ -2,10 +2,14 @@ package docker import ( "net" + "github.com/dotcloud/docker/engine" ) +// FIXME: separate runtime configuration from http api configuration type DaemonConfig struct { Pidfile string + // FIXME: don't call this GraphPath, it doesn't actually + // point to /var/lib/docker/graph, which is confusing. GraphPath string ProtoAddresses []string AutoRestart bool @@ -16,3 +20,26 @@ type DaemonConfig struct { DefaultIp net.IP InterContainerCommunication bool } + +// ConfigGetenv creates and returns a new DaemonConfig object +// by parsing the contents of a job's environment. +func ConfigGetenv(job *engine.Job) *DaemonConfig { + var config DaemonConfig + config.Pidfile = job.Getenv("Pidfile") + config.GraphPath = job.Getenv("GraphPath") + 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 +} diff --git a/components/engine/docker/docker.go b/components/engine/docker/docker.go index 362e899ba1..7487dba86f 100644 --- a/components/engine/docker/docker.go +++ b/components/engine/docker/docker.go @@ -6,9 +6,9 @@ import ( "github.com/dotcloud/docker" "github.com/dotcloud/docker/sysinit" "github.com/dotcloud/docker/utils" + "github.com/dotcloud/docker/engine" "io/ioutil" "log" - "net" "os" "os/signal" "strconv" @@ -61,10 +61,6 @@ func main() { } } - bridge := docker.DefaultNetworkBridge - if *bridgeName != "" { - bridge = *bridgeName - } if *flDebug { os.Setenv("DEBUG", "1") } @@ -75,26 +71,25 @@ func main() { flag.Usage() return } - var dns []string - if *flDns != "" { - dns = []string{*flDns} + eng, err := engine.New(*flGraphPath) + if err != nil { + log.Fatal(err) } - - 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, + job, err := eng.Job("serveapi") + if err != nil { + log.Fatal(err) } - if err := daemon(config); err != nil { + job.Setenv("Pidfile", *pidfile) + job.Setenv("GraphPath", *flGraphPath) + 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 := daemon(job, *pidfile); err != nil { log.Fatal(err) } } else { @@ -142,51 +137,22 @@ func removePidFile(pidfile string) { } } -func daemon(config *docker.DaemonConfig) error { - if err := createPidFile(config.Pidfile); err != nil { +// daemon runs `job` as a daemon. +// A pidfile is created for the duration of the job, +// and all signals are intercepted. +func daemon(job *engine.Job, pidfile string) error { + if err := createPidFile(pidfile); err != nil { log.Fatal(err) } - defer removePidFile(config.Pidfile) - - server, err := docker.NewServer(config) - if err != nil { - return err - } - defer server.Close() + defer removePidFile(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) - server.Close() - removePidFile(config.Pidfile) + removePidFile(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 + return job.Run() } diff --git a/components/engine/engine/MAINTAINERS b/components/engine/engine/MAINTAINERS new file mode 100644 index 0000000000..e1c6f2ccfc --- /dev/null +++ b/components/engine/engine/MAINTAINERS @@ -0,0 +1 @@ +Solomon Hykes diff --git a/components/engine/engine/engine.go b/components/engine/engine/engine.go new file mode 100644 index 0000000000..e253678069 --- /dev/null +++ b/components/engine/engine/engine.go @@ -0,0 +1,62 @@ +package engine + +import ( + "fmt" + "os" +) + + +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) { + 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, error) { + handler, exists := eng.handlers[name] + if !exists || handler == nil { + return nil, fmt.Errorf("Undefined command; %s", name) + } + job := &Job{ + eng: eng, + Name: name, + Args: args, + Stdin: os.Stdin, + Stdout: os.Stdout, + Stderr: os.Stderr, + handler: handler, + } + return job, nil +} + diff --git a/components/engine/engine/job.go b/components/engine/engine/job.go new file mode 100644 index 0000000000..8ff722d2fb --- /dev/null +++ b/components/engine/engine/job.go @@ -0,0 +1,105 @@ +package engine + +import ( + "io" + "strings" + "fmt" + "encoding/json" +) + +// 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 { + if job.handler == nil { + return fmt.Errorf("Undefined job handler") + } + status := job.handler(job) + job.status = status + if status != "0" { + return fmt.Errorf("Job failed with status %s", status) + } + return nil +} + + +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) +} diff --git a/components/engine/server.go b/components/engine/server.go index d1bbe1bfe3..d833f06b7f 100644 --- a/components/engine/server.go +++ b/components/engine/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,50 @@ import ( "strings" "sync" "time" + "syscall" ) func (srv *Server) Close() error { return srv.runtime.Close() } +func init() { + engine.Register("serveapi", JobServeApi) +} + +func JobServeApi(job *engine.Job) string { + srv, err := NewServer(ConfigGetenv(job)) + if err != nil { + return err.Error() + } + defer srv.Close() + // Parse addresses to serve on + protoAddrs := job.Args + 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 "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.Error() + } + } + return "0" +} + func (srv *Server) DockerVersion() APIVersion { return APIVersion{ Version: VERSION, From e490910840eab0aedbf3ee67f37c33a10544d260 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 23 Oct 2013 08:35:26 +0000 Subject: [PATCH 03/10] Engine: basic testing harness Upstream-commit: 2a29bf624536c9daed25966dcaf80d840d0972c3 Component: engine --- components/engine/engine/env_test.go | 29 +++++++++++++++++ components/engine/engine/init_test.go | 46 +++++++++++++++++++++++++++ components/engine/utils/random.go | 16 ++++++++++ 3 files changed, 91 insertions(+) create mode 100644 components/engine/engine/env_test.go create mode 100644 components/engine/engine/init_test.go create mode 100644 components/engine/utils/random.go diff --git a/components/engine/engine/env_test.go b/components/engine/engine/env_test.go new file mode 100644 index 0000000000..3acbddad77 --- /dev/null +++ b/components/engine/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) + } +} diff --git a/components/engine/engine/init_test.go b/components/engine/engine/init_test.go new file mode 100644 index 0000000000..b46e0bc9a1 --- /dev/null +++ b/components/engine/engine/init_test.go @@ -0,0 +1,46 @@ +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 { + job, err := mkEngine(t).Job(name, args...) + if err != nil { + t.Fatal(err) + } + return job +} diff --git a/components/engine/utils/random.go b/components/engine/utils/random.go new file mode 100644 index 0000000000..d5c37e44d0 --- /dev/null +++ b/components/engine/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) +} From ff1fcc1bf9f86a16d7b850bf0f9f19a0fa42671b Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 23 Oct 2013 06:53:40 +0000 Subject: [PATCH 04/10] Engine: centralize checks for supported architectures and kernel versions Upstream-commit: 1b8eef4efbdd308f0dff0ae063ea49c0f4142888 Component: engine --- components/engine/engine/engine.go | 21 +++++++++++++++++++++ components/engine/runtime.go | 8 -------- components/engine/server.go | 3 --- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/components/engine/engine/engine.go b/components/engine/engine/engine.go index e253678069..c912c98cbd 100644 --- a/components/engine/engine/engine.go +++ b/components/engine/engine/engine.go @@ -3,6 +3,9 @@ package engine import ( "fmt" "os" + "log" + "runtime" + "github.com/dotcloud/docker/utils" ) @@ -31,6 +34,24 @@ type Engine struct { // 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 } diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 58c2007f7b..cdd53c58f3 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -577,14 +577,6 @@ 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 } diff --git a/components/engine/server.go b/components/engine/server.go index d833f06b7f..c5de67d60c 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -1430,9 +1430,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 From b6319ac31ad4bfb95cfb71b5896e214394f9f27f Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 23 Oct 2013 08:09:16 +0000 Subject: [PATCH 05/10] Rename 'GraphPath' to the more logical 'Root'. This does not affect users except for a slight text change in the usage messge Upstream-commit: 7e691e11b033f35fe823fbc6dcb40decd584696d Component: engine --- components/engine/config.go | 6 ++---- components/engine/docker/docker.go | 6 +++--- components/engine/runtime.go | 10 +++++----- components/engine/runtime_test.go | 6 +++--- components/engine/server.go | 6 +++--- components/engine/utils_test.go | 2 +- 6 files changed, 17 insertions(+), 19 deletions(-) diff --git a/components/engine/config.go b/components/engine/config.go index a572cfadaf..1f32d8fb3e 100644 --- a/components/engine/config.go +++ b/components/engine/config.go @@ -8,9 +8,7 @@ import ( // FIXME: separate runtime configuration from http api configuration type DaemonConfig struct { Pidfile string - // FIXME: don't call this GraphPath, it doesn't actually - // point to /var/lib/docker/graph, which is confusing. - GraphPath string + Root string ProtoAddresses []string AutoRestart bool EnableCors bool @@ -26,7 +24,7 @@ type DaemonConfig struct { func ConfigGetenv(job *engine.Job) *DaemonConfig { var config DaemonConfig config.Pidfile = job.Getenv("Pidfile") - config.GraphPath = job.Getenv("GraphPath") + config.Root = job.Getenv("Root") config.AutoRestart = job.GetenvBool("AutoRestart") config.EnableCors = job.GetenvBool("EnableCors") if dns := job.Getenv("Dns"); dns != "" { diff --git a/components/engine/docker/docker.go b/components/engine/docker/docker.go index 7487dba86f..cd0369636d 100644 --- a/components/engine/docker/docker.go +++ b/components/engine/docker/docker.go @@ -34,7 +34,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)} @@ -71,7 +71,7 @@ func main() { flag.Usage() return } - eng, err := engine.New(*flGraphPath) + eng, err := engine.New(*flRoot) if err != nil { log.Fatal(err) } @@ -80,7 +80,7 @@ func main() { log.Fatal(err) } job.Setenv("Pidfile", *pidfile) - job.Setenv("GraphPath", *flGraphPath) + job.Setenv("Root", *flRoot) job.SetenvBool("AutoRestart", *flAutoRestart) job.SetenvBool("EnableCors", *flEnableCors) job.Setenv("Dns", *flDns) diff --git a/components/engine/runtime.go b/components/engine/runtime.go index cdd53c58f3..22c5d572bf 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -582,21 +582,21 @@ func NewRuntime(config *DaemonConfig) (*Runtime, error) { } 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) } @@ -608,7 +608,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) { diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index 085e5be1a3..662dfee657 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/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, } diff --git a/components/engine/server.go b/components/engine/server.go index c5de67d60c..e6cc729a99 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -158,7 +158,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 } @@ -702,7 +702,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 } @@ -911,7 +911,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 } diff --git a/components/engine/utils_test.go b/components/engine/utils_test.go index 917c4c1591..ec0aa1e013 100644 --- a/components/engine/utils_test.go +++ b/components/engine/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) From 8363dd13c79f364e0848b3c17111a0272a1650c4 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 23 Oct 2013 08:00:31 +0000 Subject: [PATCH 06/10] Rename ConfigGetenv to ConfigFromJob Upstream-commit: c542b2f873f181d7cb3127b4700a01bd62f43fbf Component: engine --- components/engine/config.go | 4 ++-- components/engine/server.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/engine/config.go b/components/engine/config.go index 1f32d8fb3e..40c47e692c 100644 --- a/components/engine/config.go +++ b/components/engine/config.go @@ -19,9 +19,9 @@ type DaemonConfig struct { InterContainerCommunication bool } -// ConfigGetenv creates and returns a new DaemonConfig object +// ConfigFromJob creates and returns a new DaemonConfig object // by parsing the contents of a job's environment. -func ConfigGetenv(job *engine.Job) *DaemonConfig { +func ConfigFromJob(job *engine.Job) *DaemonConfig { var config DaemonConfig config.Pidfile = job.Getenv("Pidfile") config.Root = job.Getenv("Root") diff --git a/components/engine/server.go b/components/engine/server.go index e6cc729a99..08ee7a4974 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -35,7 +35,7 @@ func init() { } func JobServeApi(job *engine.Job) string { - srv, err := NewServer(ConfigGetenv(job)) + srv, err := NewServer(ConfigFromJob(job)) if err != nil { return err.Error() } From ae44c0355750aba25e23afdaf741809d842cb4f2 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 25 Oct 2013 00:30:34 -0600 Subject: [PATCH 07/10] Setup daemon pidfile/cleanup in Server.Daemon() instead of main() Upstream-commit: c1ae1a0e1cfae1aed681add5b1cb1c39e2c0e929 Component: engine --- components/engine/docker/docker.go | 53 +----------------------------- components/engine/server.go | 35 +++++++++++++++++--- components/engine/utils/daemon.go | 36 ++++++++++++++++++++ 3 files changed, 67 insertions(+), 57 deletions(-) create mode 100644 components/engine/utils/daemon.go diff --git a/components/engine/docker/docker.go b/components/engine/docker/docker.go index cd0369636d..d8d30ff341 100644 --- a/components/engine/docker/docker.go +++ b/components/engine/docker/docker.go @@ -7,13 +7,9 @@ import ( "github.com/dotcloud/docker/sysinit" "github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/engine" - "io/ioutil" "log" "os" - "os/signal" - "strconv" "strings" - "syscall" ) var ( @@ -89,7 +85,7 @@ func main() { job.SetenvList("ProtoAddresses", flHosts) job.Setenv("DefaultIp", *flDefaultIp) job.SetenvBool("InterContainerCommunication", *flInterContainerComm) - if err := daemon(job, *pidfile); err != nil { + if err := job.Run(); err != nil { log.Fatal(err) } } else { @@ -109,50 +105,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) - } -} - -// daemon runs `job` as a daemon. -// A pidfile is created for the duration of the job, -// and all signals are intercepted. -func daemon(job *engine.Job, pidfile string) error { - if err := createPidFile(pidfile); err != nil { - log.Fatal(err) - } - defer removePidFile(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) - removePidFile(pidfile) - os.Exit(0) - }() - return job.Run() -} diff --git a/components/engine/server.go b/components/engine/server.go index 08ee7a4974..1547a0e122 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -24,6 +24,7 @@ import ( "sync" "time" "syscall" + "os/signal" ) func (srv *Server) Close() error { @@ -40,8 +41,31 @@ func JobServeApi(job *engine.Job) string { return err.Error() } defer srv.Close() - // Parse addresses to serve on - protoAddrs := job.Args + 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) + os.Exit(0) + }() + + protoAddrs := srv.runtime.config.ProtoAddresses chErrors := make(chan error, len(protoAddrs)) for _, protoAddr := range protoAddrs { protoAddrParts := strings.SplitN(protoAddr, "://", 2) @@ -52,7 +76,7 @@ func JobServeApi(job *engine.Job) string { 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 "Invalid protocol format." + return fmt.Errorf("Invalid protocol format.") } go func() { chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], srv, true) @@ -61,12 +85,13 @@ func JobServeApi(job *engine.Job) string { for i := 0; i < len(protoAddrs); i += 1 { err := <-chErrors if err != nil { - return err.Error() + return err } } - return "0" + return nil } + func (srv *Server) DockerVersion() APIVersion { return APIVersion{ Version: VERSION, diff --git a/components/engine/utils/daemon.go b/components/engine/utils/daemon.go new file mode 100644 index 0000000000..179ff90e10 --- /dev/null +++ b/components/engine/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) + } +} From b744ea13a4aecdf5245ecfa56e9eb8eb6cfba8ca Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 26 Oct 2013 14:28:53 -0700 Subject: [PATCH 08/10] Close sqlite persistent connection on SIGINT, SITERM or SIGKILL Upstream-commit: 464ded79fcd46dbdf8859811912a244006626e86 Component: engine --- components/engine/server.go | 1 + 1 file changed, 1 insertion(+) diff --git a/components/engine/server.go b/components/engine/server.go index 1547a0e122..fedd3e84a0 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -62,6 +62,7 @@ func (srv *Server) Daemon() error { sig := <-c log.Printf("Received signal '%v', exiting\n", sig) utils.RemovePidFile(srv.runtime.config.Pidfile) + srv.Close() os.Exit(0) }() From f33d33af857507daba00108b5c7215b7ea17d748 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 26 Oct 2013 17:17:45 -0700 Subject: [PATCH 09/10] Engine: cleanly log the start and end of each engine job Upstream-commit: e8491ae54cad1e7d8aaffdae84f81d3c6c8e377a Component: engine --- components/engine/engine/job.go | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/components/engine/engine/job.go b/components/engine/engine/job.go index 8ff722d2fb..0bde2a0beb 100644 --- a/components/engine/engine/job.go +++ b/components/engine/engine/job.go @@ -5,6 +5,7 @@ import ( "strings" "fmt" "encoding/json" + "github.com/dotcloud/docker/utils" ) // A job is the fundamental unit of work in the docker engine. @@ -36,17 +37,24 @@ type Job struct { // 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 { - return fmt.Errorf("Undefined job handler") + job.status = "command not found" + } else { + job.status = job.handler(job) } - status := job.handler(job) - job.status = status - if status != "0" { - return fmt.Errorf("Job failed with status %s", status) + 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 { From 1ae36ff617bcc94b9f37f3e63846049f17e634bf Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 26 Oct 2013 17:49:16 -0700 Subject: [PATCH 10/10] Engine: Engine.Job() never fails, to mimic the os/exec API (and make usage less verbose) Upstream-commit: a13241d370bb9862acdefbc7dbb1e338d9552eaa Component: engine --- components/engine/docker/docker.go | 5 +---- components/engine/engine/engine.go | 13 ++++++------- components/engine/engine/init_test.go | 6 +----- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/components/engine/docker/docker.go b/components/engine/docker/docker.go index d8d30ff341..6540b568fd 100644 --- a/components/engine/docker/docker.go +++ b/components/engine/docker/docker.go @@ -71,10 +71,7 @@ func main() { if err != nil { log.Fatal(err) } - job, err := eng.Job("serveapi") - if err != nil { - log.Fatal(err) - } + job := eng.Job("serveapi") job.Setenv("Pidfile", *pidfile) job.Setenv("Root", *flRoot) job.SetenvBool("AutoRestart", *flAutoRestart) diff --git a/components/engine/engine/engine.go b/components/engine/engine/engine.go index c912c98cbd..8d67242ca2 100644 --- a/components/engine/engine/engine.go +++ b/components/engine/engine/engine.go @@ -64,11 +64,7 @@ func New(root string) (*Engine, error) { // 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, error) { - handler, exists := eng.handlers[name] - if !exists || handler == nil { - return nil, fmt.Errorf("Undefined command; %s", name) - } +func (eng *Engine) Job(name string, args ...string) *Job { job := &Job{ eng: eng, Name: name, @@ -76,8 +72,11 @@ func (eng *Engine) Job(name string, args ...string) (*Job, error) { Stdin: os.Stdin, Stdout: os.Stdout, Stderr: os.Stderr, - handler: handler, } - return job, nil + handler, exists := eng.handlers[name] + if exists { + job.handler = handler + } + return job } diff --git a/components/engine/engine/init_test.go b/components/engine/engine/init_test.go index b46e0bc9a1..5c03ded87d 100644 --- a/components/engine/engine/init_test.go +++ b/components/engine/engine/init_test.go @@ -38,9 +38,5 @@ func mkEngine(t *testing.T) *Engine { } func mkJob(t *testing.T, name string, args ...string) *Job { - job, err := mkEngine(t).Job(name, args...) - if err != nil { - t.Fatal(err) - } - return job + return mkEngine(t).Job(name, args...) }