Sfoglia il codice sorgente

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.
Solomon Hykes 11 anni fa
parent
commit
0d1a825137
6 ha cambiato i file con 260 aggiunte e 60 eliminazioni
  1. 27 0
      config.go
  2. 26 60
      docker/docker.go
  3. 1 0
      engine/MAINTAINERS
  4. 62 0
      engine/engine.go
  5. 105 0
      engine/job.go
  6. 39 0
      server.go

+ 27 - 0
config.go

@@ -2,10 +2,14 @@ package docker
 
 
 import (
 import (
 	"net"
 	"net"
+	"github.com/dotcloud/docker/engine"
 )
 )
 
 
+// FIXME: separate runtime configuration from http api configuration
 type DaemonConfig struct {
 type DaemonConfig struct {
 	Pidfile                     string
 	Pidfile                     string
+	// FIXME: don't call this GraphPath, it doesn't actually
+	// point to /var/lib/docker/graph, which is confusing.
 	GraphPath                   string
 	GraphPath                   string
 	ProtoAddresses              []string
 	ProtoAddresses              []string
 	AutoRestart                 bool
 	AutoRestart                 bool
@@ -16,3 +20,26 @@ type DaemonConfig struct {
 	DefaultIp                   net.IP
 	DefaultIp                   net.IP
 	InterContainerCommunication bool
 	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
+}

+ 26 - 60
docker/docker.go

@@ -6,9 +6,9 @@ import (
 	"github.com/dotcloud/docker"
 	"github.com/dotcloud/docker"
 	"github.com/dotcloud/docker/sysinit"
 	"github.com/dotcloud/docker/sysinit"
 	"github.com/dotcloud/docker/utils"
 	"github.com/dotcloud/docker/utils"
+	"github.com/dotcloud/docker/engine"
 	"io/ioutil"
 	"io/ioutil"
 	"log"
 	"log"
-	"net"
 	"os"
 	"os"
 	"os/signal"
 	"os/signal"
 	"strconv"
 	"strconv"
@@ -61,10 +61,6 @@ func main() {
 		}
 		}
 	}
 	}
 
 
-	bridge := docker.DefaultNetworkBridge
-	if *bridgeName != "" {
-		bridge = *bridgeName
-	}
 	if *flDebug {
 	if *flDebug {
 		os.Setenv("DEBUG", "1")
 		os.Setenv("DEBUG", "1")
 	}
 	}
@@ -75,26 +71,25 @@ func main() {
 			flag.Usage()
 			flag.Usage()
 			return
 			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)
 			log.Fatal(err)
 		}
 		}
 	} else {
 	} 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)
 		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)
 	c := make(chan os.Signal, 1)
 	signal.Notify(c, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM))
 	signal.Notify(c, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM))
 	go func() {
 	go func() {
 		sig := <-c
 		sig := <-c
 		log.Printf("Received signal '%v', exiting\n", sig)
 		log.Printf("Received signal '%v', exiting\n", sig)
-		server.Close()
-		removePidFile(config.Pidfile)
+		removePidFile(pidfile)
 		os.Exit(0)
 		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()
 }
 }

+ 1 - 0
engine/MAINTAINERS

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

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

+ 105 - 0
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)
+}

+ 39 - 0
server.go

@@ -9,6 +9,7 @@ import (
 	"github.com/dotcloud/docker/gograph"
 	"github.com/dotcloud/docker/gograph"
 	"github.com/dotcloud/docker/registry"
 	"github.com/dotcloud/docker/registry"
 	"github.com/dotcloud/docker/utils"
 	"github.com/dotcloud/docker/utils"
+	"github.com/dotcloud/docker/engine"
 	"io"
 	"io"
 	"io/ioutil"
 	"io/ioutil"
 	"log"
 	"log"
@@ -22,12 +23,50 @@ import (
 	"strings"
 	"strings"
 	"sync"
 	"sync"
 	"time"
 	"time"
+	"syscall"
 )
 )
 
 
 func (srv *Server) Close() error {
 func (srv *Server) Close() error {
 	return srv.runtime.Close()
 	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 {
 func (srv *Server) DockerVersion() APIVersion {
 	return APIVersion{
 	return APIVersion{
 		Version:   VERSION,
 		Version:   VERSION,