mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
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.
This commit is contained in:
parent
60b97576cf
commit
0d1a825137
6 changed files with 260 additions and 60 deletions
27
config.go
27
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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
eng, err := engine.New(*flGraphPath)
|
||||||
if *flDns != "" {
|
if err != nil {
|
||||||
dns = []string{*flDns}
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
job, err := eng.Job("serveapi")
|
||||||
ip := net.ParseIP(*flDefaultIp)
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
config := &docker.DaemonConfig{
|
|
||||||
Pidfile: *pidfile,
|
|
||||||
GraphPath: *flGraphPath,
|
|
||||||
AutoRestart: *flAutoRestart,
|
|
||||||
EnableCors: *flEnableCors,
|
|
||||||
Dns: dns,
|
|
||||||
EnableIptables: *flEnableIptables,
|
|
||||||
BridgeIface: bridge,
|
|
||||||
ProtoAddresses: flHosts,
|
|
||||||
DefaultIp: ip,
|
|
||||||
InterContainerCommunication: *flInterContainerComm,
|
|
||||||
}
|
}
|
||||||
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 {
|
// daemon runs `job` as a daemon.
|
||||||
if err := createPidFile(config.Pidfile); err != nil {
|
// 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)
|
defer removePidFile(pidfile)
|
||||||
|
|
||||||
server, err := docker.NewServer(config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer server.Close()
|
|
||||||
|
|
||||||
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(pidfile)
|
||||||
removePidFile(config.Pidfile)
|
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}()
|
}()
|
||||||
|
return job.Run()
|
||||||
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
engine/MAINTAINERS
Normal file
1
engine/MAINTAINERS
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Solomon Hykes <solomon@dotcloud.com>
|
62
engine/engine.go
Normal file
62
engine/engine.go
Normal file
|
@ -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
engine/job.go
Normal file
105
engine/job.go
Normal file
|
@ -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
server.go
39
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,
|
||||||
|
|
Loading…
Reference in a new issue