From 4faf16b5c01f549d5c0901cd7e23decc65ef7036 Mon Sep 17 00:00:00 2001 From: Diptanu Choudhury Date: Thu, 24 Mar 2016 00:13:30 -0700 Subject: [PATCH 01/25] Added implementation to run checks for docker, exec and raw_exec --- client/driver/executor/checks.go | 237 +++++++++++++++++++++++++ client/driver/executor/checks_basic.go | 10 ++ client/driver/executor/checks_linux.go | 16 ++ client/driver/executor/checks_test.go | 37 ++++ client/driver/structs/structs.go | 12 ++ 5 files changed, 312 insertions(+) create mode 100644 client/driver/executor/checks.go create mode 100644 client/driver/executor/checks_basic.go create mode 100644 client/driver/executor/checks_linux.go create mode 100644 client/driver/executor/checks_test.go diff --git a/client/driver/executor/checks.go b/client/driver/executor/checks.go new file mode 100644 index 000000000000..dc46d10627af --- /dev/null +++ b/client/driver/executor/checks.go @@ -0,0 +1,237 @@ +package executor + +import ( + "container/heap" + "fmt" + "log" + "os/exec" + "syscall" + "time" + + "github.com/armon/circbuf" + docker "github.com/fsouza/go-dockerclient" + + cstructs "github.com/hashicorp/nomad/client/driver/structs" +) + +type Check interface { + Run() *cstructs.CheckResult + ID() string +} + +type DockerScriptCheck struct { + id string + containerID string + client *docker.Client + logger *log.Logger + script []string +} + +func (d *DockerScriptCheck) Run() *cstructs.CheckResult { + execOpts := docker.CreateExecOptions{ + AttachStdin: false, + AttachStdout: true, + AttachStderr: true, + Tty: false, + Cmd: d.script, + Container: d.containerID, + } + var ( + exec *docker.Exec + err error + execRes *docker.ExecInspect + time = time.Now() + ) + if exec, err = d.client.CreateExec(execOpts); err != nil { + return &cstructs.CheckResult{Err: err} + } + + output, _ := circbuf.NewBuffer(int64(cstructs.CheckBufSize)) + startOpts := docker.StartExecOptions{ + Detach: false, + Tty: false, + OutputStream: output, + ErrorStream: output, + } + + if err = d.client.StartExec(exec.ID, startOpts); err != nil { + return &cstructs.CheckResult{Err: err} + } + if execRes, err = d.client.InspectExec(exec.ID); err != nil { + return &cstructs.CheckResult{Err: err} + } + return &cstructs.CheckResult{ + ExitCode: execRes.ExitCode, + Output: string(output.Bytes()), + Timestamp: time, + } +} + +func (d *DockerScriptCheck) ID() string { + return d.id +} + +type ExecScriptCheck struct { + id string + cmd string + args []string + taskDir string + + ctx *ExecutorContext + FSIsolation bool +} + +func (e *ExecScriptCheck) Run() *cstructs.CheckResult { + buf, _ := circbuf.NewBuffer(int64(cstructs.CheckBufSize)) + cmd := exec.Command(e.cmd, e.args...) + cmd.Stdout = buf + cmd.Stderr = buf + e.setChroot(cmd) + ts := time.Now() + if err := cmd.Start(); err != nil { + return &cstructs.CheckResult{Err: err} + } + errCh := make(chan error, 2) + go func() { + errCh <- cmd.Wait() + }() + for { + select { + case err := <-errCh: + if err == nil { + return &cstructs.CheckResult{ExitCode: 0, Output: string(buf.Bytes()), Timestamp: ts} + } + exitCode := 1 + if exitErr, ok := err.(*exec.ExitError); ok { + if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { + exitCode = status.ExitStatus() + } + } + return &cstructs.CheckResult{ExitCode: exitCode, Output: string(buf.Bytes()), Timestamp: ts} + case <-time.After(30 * time.Second): + errCh <- fmt.Errorf("timed out after waiting 30s") + } + } + return nil +} + +func (e *ExecScriptCheck) ID() string { + return e.id +} + +type consulCheck struct { + check Check + next time.Time + index int +} + +type checkHeap struct { + index map[string]*consulCheck + heap checksHeapImp +} + +func NewConsulChecksHeap() *checkHeap { + return &checkHeap{ + index: make(map[string]*consulCheck), + heap: make(checksHeapImp, 0), + } +} + +func (c *checkHeap) Push(check Check, next time.Time) error { + if _, ok := c.index[check.ID()]; ok { + return fmt.Errorf("check %v already exists", check.ID()) + } + + cCheck := &consulCheck{check, next, 0} + + c.index[check.ID()] = cCheck + heap.Push(&c.heap, cCheck) + return nil +} + +func (c *checkHeap) Pop() *consulCheck { + if len(c.heap) == 0 { + return nil + } + + cCheck := heap.Pop(&c.heap).(*consulCheck) + delete(c.index, cCheck.check.ID()) + return cCheck +} + +func (c *checkHeap) Peek() *consulCheck { + if len(c.heap) == 0 { + return nil + } + return c.heap[0] +} + +func (c *checkHeap) Contains(check Check) bool { + _, ok := c.index[check.ID()] + return ok +} + +func (c *checkHeap) Update(check Check, next time.Time) error { + if cCheck, ok := c.index[check.ID()]; ok { + cCheck.check = check + cCheck.next = next + heap.Fix(&c.heap, cCheck.index) + return nil + } + + return fmt.Errorf("heap doesn't contain check %v", check.ID()) +} + +func (c *checkHeap) Remove(check Check) error { + if cCheck, ok := c.index[check.ID()]; ok { + heap.Remove(&c.heap, cCheck.index) + delete(c.index, check.ID()) + return nil + } + return fmt.Errorf("heap doesn't contain check %v", check.ID()) +} + +func (c *checkHeap) Len() int { return len(c.heap) } + +type checksHeapImp []*consulCheck + +func (h checksHeapImp) Len() int { return len(h) } + +func (h checksHeapImp) Less(i, j int) bool { + // Two zero times should return false. + // Otherwise, zero is "greater" than any other time. + // (To sort it at the end of the list.) + // Sort such that zero times are at the end of the list. + iZero, jZero := h[i].next.IsZero(), h[j].next.IsZero() + if iZero && jZero { + return false + } else if iZero { + return false + } else if jZero { + return true + } + + return h[i].next.Before(h[j].next) +} + +func (h checksHeapImp) Swap(i, j int) { + h[i], h[j] = h[j], h[i] + h[i].index = i + h[j].index = j +} + +func (h *checksHeapImp) Push(x interface{}) { + n := len(*h) + check := x.(*consulCheck) + check.index = n + *h = append(*h, check) +} + +func (h *checksHeapImp) Pop() interface{} { + old := *h + n := len(old) + check := old[n-1] + check.index = -1 // for safety + *h = old[0 : n-1] + return check +} diff --git a/client/driver/executor/checks_basic.go b/client/driver/executor/checks_basic.go new file mode 100644 index 000000000000..c812bf6cc2c6 --- /dev/null +++ b/client/driver/executor/checks_basic.go @@ -0,0 +1,10 @@ +// +build !linux + +package executor + +import ( + "os/exec" +) + +func (e *ExecScriptCheck) setChroot(cmd *exec.Cmd) { +} diff --git a/client/driver/executor/checks_linux.go b/client/driver/executor/checks_linux.go new file mode 100644 index 000000000000..d4a684a8bf34 --- /dev/null +++ b/client/driver/executor/checks_linux.go @@ -0,0 +1,16 @@ +package executor + +import ( + "os/exec" + "syscall" +) + +func (e *ExecScriptCheck) setChroot(cmd *exec.Cmd) { + if e.FSIsolation { + if cmd.SysProcAttr == nil { + cmd.SysProcAttr = &syscall.SysProcAttr{} + } + } + cmd.SysProcAttr.Chroot = e.taskDir + cmd.Dir = "/" +} diff --git a/client/driver/executor/checks_test.go b/client/driver/executor/checks_test.go new file mode 100644 index 000000000000..c959e7ee0b88 --- /dev/null +++ b/client/driver/executor/checks_test.go @@ -0,0 +1,37 @@ +package executor + +import ( + "reflect" + "testing" + "time" +) + +func TestCheckHeapOrder(t *testing.T) { + h := NewConsulChecksHeap() + + c1 := ExecScriptCheck{id: "a"} + c2 := ExecScriptCheck{id: "b"} + c3 := ExecScriptCheck{id: "c"} + + lookup := map[Check]string{ + &c1: "c1", + &c2: "c2", + &c3: "c3", + } + + h.Push(&c1, time.Time{}) + h.Push(&c2, time.Unix(10, 0)) + h.Push(&c3, time.Unix(11, 0)) + + expected := []string{"c2", "c3", "c1"} + var actual []string + for i := 0; i < 3; i++ { + cCheck := h.Pop() + + actual = append(actual, lookup[cCheck.check]) + } + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("Wrong ordering; got %v; want %v", actual, expected) + } +} diff --git a/client/driver/structs/structs.go b/client/driver/structs/structs.go index a23990427b60..60d4860eb957 100644 --- a/client/driver/structs/structs.go +++ b/client/driver/structs/structs.go @@ -2,6 +2,7 @@ package structs import ( "fmt" + "time" cgroupConfig "github.com/opencontainers/runc/libcontainer/configs" ) @@ -9,6 +10,9 @@ import ( const ( // The default user that the executor uses to run tasks DefaultUnpriviledgedUser = "nobody" + + // CheckBufSize is the size of the check output result + CheckBufSize = 4 * 1024 ) // WaitResult stores the result of a Wait operation. @@ -60,3 +64,11 @@ func NewRecoverableError(e error, recoverable bool) *RecoverableError { func (r *RecoverableError) Error() string { return r.Err.Error() } + +// CheckResult encapsulates the result of a check +type CheckResult struct { + ExitCode int + Output string + Timestamp time.Time + Err error +} From 42dc8bea16cab6003f17c7bbca907629f507211d Mon Sep 17 00:00:00 2001 From: Diptanu Choudhury Date: Thu, 24 Mar 2016 10:06:40 -0700 Subject: [PATCH 02/25] Added a check type for consul service to delegate certain checks --- client/consul/check.go | 10 +++++ .../executor => consul}/checks_test.go | 2 +- client/consul/sync.go | 12 +++++- client/driver/docker.go | 1 + client/driver/exec.go | 1 + client/driver/executor/checks.go | 22 +++++------ client/driver/executor/executor.go | 38 +++++++++++++++++++ client/driver/java.go | 1 + client/driver/qemu.go | 1 + client/driver/raw_exec.go | 1 + client/driver/rkt.go | 1 + nomad/structs/structs.go | 9 +++-- 12 files changed, 79 insertions(+), 20 deletions(-) create mode 100644 client/consul/check.go rename client/{driver/executor => consul}/checks_test.go (97%) diff --git a/client/consul/check.go b/client/consul/check.go new file mode 100644 index 000000000000..ac3604893ec0 --- /dev/null +++ b/client/consul/check.go @@ -0,0 +1,10 @@ +package consul + +import ( + cstructs "github.com/hashicorp/nomad/client/driver/structs" +) + +type Check interface { + Run() *cstructs.CheckResult + ID() string +} diff --git a/client/driver/executor/checks_test.go b/client/consul/checks_test.go similarity index 97% rename from client/driver/executor/checks_test.go rename to client/consul/checks_test.go index c959e7ee0b88..6354084f94f8 100644 --- a/client/driver/executor/checks_test.go +++ b/client/consul/checks_test.go @@ -1,4 +1,4 @@ -package executor +package consul import ( "reflect" diff --git a/client/consul/sync.go b/client/consul/sync.go index dc1410886acb..e1cfb07e4f30 100644 --- a/client/consul/sync.go +++ b/client/consul/sync.go @@ -21,8 +21,10 @@ import ( type ConsulService struct { client *consul.Client - task *structs.Task - allocID string + task *structs.Task + allocID string + delegateChecks map[string]struct{} + createCheck func(structs.ServiceCheck, string) (Check, error) trackedServices map[string]*consul.AgentService trackedChecks map[string]*structs.ServiceCheck @@ -99,6 +101,12 @@ func NewConsulService(config *ConsulConfig, logger *log.Logger, allocID string) return &consulService, nil } +func (c *ConsulService) SetDelegatedChecks(delegateChecks map[string]struct{}, createCheck func(structs.ServiceCheck, string) (Check, error)) *ConsulService { + c.delegateChecks = delegateChecks + c.createCheck = createCheck + return c +} + // SyncTask sync the services and task with consul func (c *ConsulService) SyncTask(task *structs.Task) error { var mErr multierror.Error diff --git a/client/driver/docker.go b/client/driver/docker.go index cf518a6a7f6d..2c0d5accf223 100644 --- a/client/driver/docker.go +++ b/client/driver/docker.go @@ -545,6 +545,7 @@ func (d *DockerDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle executorCtx := &executor.ExecutorContext{ TaskEnv: d.taskEnv, Task: task, + Driver: "docker", AllocDir: ctx.AllocDir, AllocID: ctx.AllocID, PortLowerBound: d.config.ClientMinPort, diff --git a/client/driver/exec.go b/client/driver/exec.go index c6bb8b0e2fcb..f1ada449253d 100644 --- a/client/driver/exec.go +++ b/client/driver/exec.go @@ -107,6 +107,7 @@ func (d *ExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, } executorCtx := &executor.ExecutorContext{ TaskEnv: d.taskEnv, + Driver: "exec", AllocDir: ctx.AllocDir, AllocID: ctx.AllocID, Task: task, diff --git a/client/driver/executor/checks.go b/client/driver/executor/checks.go index dc46d10627af..cb035f70862d 100644 --- a/client/driver/executor/checks.go +++ b/client/driver/executor/checks.go @@ -11,20 +11,17 @@ import ( "github.com/armon/circbuf" docker "github.com/fsouza/go-dockerclient" + "github.com/hashicorp/nomad/client/consul" cstructs "github.com/hashicorp/nomad/client/driver/structs" ) -type Check interface { - Run() *cstructs.CheckResult - ID() string -} - type DockerScriptCheck struct { id string containerID string client *docker.Client logger *log.Logger - script []string + cmd string + args []string } func (d *DockerScriptCheck) Run() *cstructs.CheckResult { @@ -33,7 +30,7 @@ func (d *DockerScriptCheck) Run() *cstructs.CheckResult { AttachStdout: true, AttachStderr: true, Tty: false, - Cmd: d.script, + Cmd: append([]string{d.cmd}, d.args...), Container: d.containerID, } var ( @@ -77,7 +74,6 @@ type ExecScriptCheck struct { args []string taskDir string - ctx *ExecutorContext FSIsolation bool } @@ -120,7 +116,7 @@ func (e *ExecScriptCheck) ID() string { } type consulCheck struct { - check Check + check consul.Check next time.Time index int } @@ -137,7 +133,7 @@ func NewConsulChecksHeap() *checkHeap { } } -func (c *checkHeap) Push(check Check, next time.Time) error { +func (c *checkHeap) Push(check consul.Check, next time.Time) error { if _, ok := c.index[check.ID()]; ok { return fmt.Errorf("check %v already exists", check.ID()) } @@ -166,12 +162,12 @@ func (c *checkHeap) Peek() *consulCheck { return c.heap[0] } -func (c *checkHeap) Contains(check Check) bool { +func (c *checkHeap) Contains(check consul.Check) bool { _, ok := c.index[check.ID()] return ok } -func (c *checkHeap) Update(check Check, next time.Time) error { +func (c *checkHeap) Update(check consul.Check, next time.Time) error { if cCheck, ok := c.index[check.ID()]; ok { cCheck.check = check cCheck.next = next @@ -182,7 +178,7 @@ func (c *checkHeap) Update(check Check, next time.Time) error { return fmt.Errorf("heap doesn't contain check %v", check.ID()) } -func (c *checkHeap) Remove(check Check) error { +func (c *checkHeap) Remove(check consul.Check) error { if cCheck, ok := c.index[check.ID()]; ok { heap.Remove(&c.heap, cCheck.index) delete(c.index, check.ID()) diff --git a/client/driver/executor/executor.go b/client/driver/executor/executor.go index 5e0762253c1e..23da11f246db 100644 --- a/client/driver/executor/executor.go +++ b/client/driver/executor/executor.go @@ -55,6 +55,12 @@ type ExecutorContext struct { // AllocID is the allocation id to which the task belongs AllocID string + // Driver is the name of the driver that invoked the executor + Driver string + + // ContainerID is the ID of the container + ContainerID string + // PortUpperBound is the upper bound of the ports that we can use to start // the syslog server PortUpperBound uint @@ -359,6 +365,7 @@ func (e *UniversalExecutor) RegisterServices() error { if err != nil { return err } + cs.SetDelegatedChecks(e.createCheckMap(), e.createCheck) e.consulService = cs } err := e.consulService.SyncTask(e.ctx.Task) @@ -478,3 +485,34 @@ func (e *UniversalExecutor) listenerUnix() (net.Listener, error) { return net.Listen("unix", path) } + +func (e *UniversalExecutor) createCheckMap() map[string]struct{} { + checks := map[string]struct{}{ + "script": struct{}{}, + } + return checks +} + +func (e *UniversalExecutor) createCheck(check structs.ServiceCheck, checkID string) (consul.Check, error) { + if check.Type == structs.ServiceCheckScript && e.ctx.Driver == "docker" { + return &DockerScriptCheck{ + id: checkID, + containerID: e.ctx.ContainerID, + logger: e.logger, + cmd: check.Cmd, + args: check.Args, + }, nil + } + + if check.Type == structs.ServiceCheckScript && e.ctx.Driver == "exec" { + return &ExecScriptCheck{ + id: checkID, + cmd: check.Cmd, + args: check.Args, + taskDir: e.taskDir, + FSIsolation: e.command.FSIsolation, + }, nil + + } + return nil, fmt.Errorf("couldn't create check for %v", check.Name) +} diff --git a/client/driver/java.go b/client/driver/java.go index d980253a83e6..87c95b60ffcb 100644 --- a/client/driver/java.go +++ b/client/driver/java.go @@ -160,6 +160,7 @@ func (d *JavaDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, } executorCtx := &executor.ExecutorContext{ TaskEnv: d.taskEnv, + Driver: "java", AllocDir: ctx.AllocDir, AllocID: ctx.AllocID, Task: task, diff --git a/client/driver/qemu.go b/client/driver/qemu.go index 1d1ac80ffb51..8cc077c5bf69 100644 --- a/client/driver/qemu.go +++ b/client/driver/qemu.go @@ -192,6 +192,7 @@ func (d *QemuDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, } executorCtx := &executor.ExecutorContext{ TaskEnv: d.taskEnv, + Driver: "qemu", AllocDir: ctx.AllocDir, AllocID: ctx.AllocID, Task: task, diff --git a/client/driver/raw_exec.go b/client/driver/raw_exec.go index 41767cea51df..17a4950ead18 100644 --- a/client/driver/raw_exec.go +++ b/client/driver/raw_exec.go @@ -102,6 +102,7 @@ func (d *RawExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandl } executorCtx := &executor.ExecutorContext{ TaskEnv: d.taskEnv, + Driver: "raw_exec", AllocDir: ctx.AllocDir, AllocID: ctx.AllocID, Task: task, diff --git a/client/driver/rkt.go b/client/driver/rkt.go index 09870a2e5160..dfbd89872e96 100644 --- a/client/driver/rkt.go +++ b/client/driver/rkt.go @@ -234,6 +234,7 @@ func (d *RktDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, e } executorCtx := &executor.ExecutorContext{ TaskEnv: d.taskEnv, + Driver: "rkt", AllocDir: ctx.AllocDir, AllocID: ctx.AllocID, Task: task, diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index 065b07d1f35e..ab5968475c0c 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -1420,7 +1420,8 @@ const ( type ServiceCheck struct { Name string // Name of the check, defaults to id Type string // Type of the check - tcp, http, docker and script - Script string // Script to invoke for script check + Cmd string // Cmd is the command to run for script checks + Args []string // Args is a list of argumes for script checks Path string // path of the health check url for http type check Protocol string // Protocol to use if check is http, defaults to http Interval time.Duration // Interval of the check @@ -1445,7 +1446,7 @@ func (sc *ServiceCheck) Validate() error { return fmt.Errorf("service checks of http type must have a valid http path") } - if sc.Type == ServiceCheckScript && sc.Script == "" { + if sc.Type == ServiceCheckScript && sc.Cmd == "" { return fmt.Errorf("service checks of script type must have a valid script path") } @@ -1460,8 +1461,8 @@ func (sc *ServiceCheck) Hash(serviceID string) string { io.WriteString(h, serviceID) io.WriteString(h, sc.Name) io.WriteString(h, sc.Type) - io.WriteString(h, sc.Script) - io.WriteString(h, sc.Path) + io.WriteString(h, sc.Cmd) + io.WriteString(h, strings.Join(sc.Args, "")) io.WriteString(h, sc.Path) io.WriteString(h, sc.Protocol) io.WriteString(h, sc.Interval.String()) From 2b60e8b8a93f4206952d770180d4281b2a3eb1f6 Mon Sep 17 00:00:00 2001 From: Diptanu Choudhury Date: Thu, 24 Mar 2016 11:34:32 -0700 Subject: [PATCH 03/25] Updated consul related dependencies --- Godeps/Godeps.json | 9 +- .../github.com/hashicorp/consul/api/agent.go | 85 +++++++++++++++---- vendor/github.com/hashicorp/consul/api/api.go | 32 +++++-- .../hashicorp/consul/api/catalog.go | 15 ++-- .../github.com/hashicorp/consul/api/health.go | 22 +++-- 5 files changed, 123 insertions(+), 40 deletions(-) diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 185636f08b7c..be5653492a5d 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1,7 +1,6 @@ { "ImportPath": "github.com/hashicorp/nomad", "GoVersion": "go1.6", - "GodepVersion": "v60", "Deps": [ { "ImportPath": "github.com/StackExchange/wmi", @@ -252,13 +251,13 @@ }, { "ImportPath": "github.com/hashicorp/consul/api", - "Comment": "v0.6.3-119-g0562b95", - "Rev": "0562b95a551568e8dd2f93af6c60982f1a252a3a" + "Comment": "v0.6.3-290-ge3f6c6a", + "Rev": "e3f6c6a7987ff879a1c138abcc4d14d8b65fc13f" }, { "ImportPath": "github.com/hashicorp/consul/tlsutil", - "Comment": "v0.6.3-119-g0562b95", - "Rev": "0562b95a551568e8dd2f93af6c60982f1a252a3a" + "Comment": "v0.6.3-290-ge3f6c6a", + "Rev": "e3f6c6a7987ff879a1c138abcc4d14d8b65fc13f" }, { "ImportPath": "github.com/hashicorp/errwrap", diff --git a/vendor/github.com/hashicorp/consul/api/agent.go b/vendor/github.com/hashicorp/consul/api/agent.go index e4466a651116..67855c67fbcd 100644 --- a/vendor/github.com/hashicorp/consul/api/agent.go +++ b/vendor/github.com/hashicorp/consul/api/agent.go @@ -18,11 +18,12 @@ type AgentCheck struct { // AgentService represents a service known to the agent type AgentService struct { - ID string - Service string - Tags []string - Port int - Address string + ID string + Service string + Tags []string + Port int + Address string + EnableTagOverride bool } // AgentMember represents a cluster member known to the agent @@ -42,13 +43,14 @@ type AgentMember struct { // AgentServiceRegistration is used to register a new service type AgentServiceRegistration struct { - ID string `json:",omitempty"` - Name string `json:",omitempty"` - Tags []string `json:",omitempty"` - Port int `json:",omitempty"` - Address string `json:",omitempty"` - Check *AgentServiceCheck - Checks AgentServiceChecks + ID string `json:",omitempty"` + Name string `json:",omitempty"` + Tags []string `json:",omitempty"` + Port int `json:",omitempty"` + Address string `json:",omitempty"` + EnableTagOverride bool `json:",omitempty"` + Check *AgentServiceCheck + Checks AgentServiceChecks } // AgentCheckRegistration is used to register a new check @@ -198,21 +200,26 @@ func (a *Agent) ServiceDeregister(serviceID string) error { // PassTTL is used to set a TTL check to the passing state func (a *Agent) PassTTL(checkID, note string) error { - return a.UpdateTTL(checkID, note, "pass") + return a.updateTTL(checkID, note, "pass") } // WarnTTL is used to set a TTL check to the warning state func (a *Agent) WarnTTL(checkID, note string) error { - return a.UpdateTTL(checkID, note, "warn") + return a.updateTTL(checkID, note, "warn") } // FailTTL is used to set a TTL check to the failing state func (a *Agent) FailTTL(checkID, note string) error { - return a.UpdateTTL(checkID, note, "fail") + return a.updateTTL(checkID, note, "fail") } -// UpdateTTL is used to update the TTL of a check -func (a *Agent) UpdateTTL(checkID, note, status string) error { +// updateTTL is used to update the TTL of a check. This is the internal +// method that uses the old API that's present in Consul versions prior +// to 0.6.4. Since Consul didn't have an analogous "update" API before it +// seemed ok to break this (former) UpdateTTL in favor of the new UpdateTTL +// below, but keep the old Pass/Warn/Fail methods using the old API under the +// hood. +func (a *Agent) updateTTL(checkID, note, status string) error { switch status { case "pass": case "warn": @@ -231,6 +238,50 @@ func (a *Agent) UpdateTTL(checkID, note, status string) error { return nil } +// checkUpdate is the payload for a PUT for a check update. +type checkUpdate struct { + // Status us one of the structs.Health* states, "passing", "warning", or + // "critical". + Status string + + // Output is the information to post to the UI for operators as the + // output of the process that decided to hit the TTL check. This is + // different from the note field that's associated with the check + // itself. + Output string +} + +// UpdateTTL is used to update the TTL of a check. This uses the newer API +// that was introduced in Consul 0.6.4 and later. We translate the old status +// strings for compatibility (though a newer version of Consul will still be +// required to use this API). +func (a *Agent) UpdateTTL(checkID, output, status string) error { + switch status { + case "pass", HealthPassing: + status = HealthPassing + case "warn", HealthWarning: + status = HealthWarning + case "fail", HealthCritical: + status = HealthCritical + default: + return fmt.Errorf("Invalid status: %s", status) + } + + endpoint := fmt.Sprintf("/v1/agent/check/update/%s", checkID) + r := a.c.newRequest("PUT", endpoint) + r.obj = &checkUpdate{ + Status: status, + Output: output, + } + + _, resp, err := requireOK(a.c.doRequest(r)) + if err != nil { + return err + } + resp.Body.Close() + return nil +} + // CheckRegister is used to register a new check with // the local agent func (a *Agent) CheckRegister(check *AgentCheckRegistration) error { diff --git a/vendor/github.com/hashicorp/consul/api/api.go b/vendor/github.com/hashicorp/consul/api/api.go index 0a2a76e5dc6d..d0e0ceeae390 100644 --- a/vendor/github.com/hashicorp/consul/api/api.go +++ b/vendor/github.com/hashicorp/consul/api/api.go @@ -122,12 +122,34 @@ type Config struct { Token string } -// DefaultConfig returns a default configuration for the client +// DefaultConfig returns a default configuration for the client. By default this +// will pool and reuse idle connections to Consul. If you have a long-lived +// client object, this is the desired behavior and should make the most efficient +// use of the connections to Consul. If you don't reuse a client object , which +// is not recommended, then you may notice idle connections building up over +// time. To avoid this, use the DefaultNonPooledConfig() instead. func DefaultConfig() *Config { + return defaultConfig(cleanhttp.DefaultPooledTransport) +} + +// DefaultNonPooledConfig returns a default configuration for the client which +// does not pool connections. This isn't a recommended configuration because it +// will reconnect to Consul on every request, but this is useful to avoid the +// accumulation of idle connections if you make many client objects during the +// lifetime of your application. +func DefaultNonPooledConfig() *Config { + return defaultConfig(cleanhttp.DefaultTransport) +} + +// defaultConfig returns the default configuration for the client, using the +// given function to make the transport. +func defaultConfig(transportFn func() *http.Transport) *Config { config := &Config{ - Address: "127.0.0.1:8500", - Scheme: "http", - HttpClient: cleanhttp.DefaultClient(), + Address: "127.0.0.1:8500", + Scheme: "http", + HttpClient: &http.Client{ + Transport: transportFn(), + }, } if addr := os.Getenv("CONSUL_HTTP_ADDR"); addr != "" { @@ -172,7 +194,7 @@ func DefaultConfig() *Config { } if !doVerify { - transport := cleanhttp.DefaultTransport() + transport := transportFn() transport.TLSClientConfig = &tls.Config{ InsecureSkipVerify: true, } diff --git a/vendor/github.com/hashicorp/consul/api/catalog.go b/vendor/github.com/hashicorp/consul/api/catalog.go index cf64bd90919d..52a00b3043f8 100644 --- a/vendor/github.com/hashicorp/consul/api/catalog.go +++ b/vendor/github.com/hashicorp/consul/api/catalog.go @@ -6,13 +6,14 @@ type Node struct { } type CatalogService struct { - Node string - Address string - ServiceID string - ServiceName string - ServiceAddress string - ServiceTags []string - ServicePort int + Node string + Address string + ServiceID string + ServiceName string + ServiceAddress string + ServiceTags []string + ServicePort int + ServiceEnableTagOverride bool } type CatalogNode struct { diff --git a/vendor/github.com/hashicorp/consul/api/health.go b/vendor/github.com/hashicorp/consul/api/health.go index 1a273e087cb3..5bb403f554f0 100644 --- a/vendor/github.com/hashicorp/consul/api/health.go +++ b/vendor/github.com/hashicorp/consul/api/health.go @@ -4,6 +4,16 @@ import ( "fmt" ) +const ( + // HealthAny is special, and is used as a wild card, + // not as a specific state. + HealthAny = "any" + HealthUnknown = "unknown" + HealthPassing = "passing" + HealthWarning = "warning" + HealthCritical = "critical" +) + // HealthCheck is used to represent a single check type HealthCheck struct { Node string @@ -85,7 +95,7 @@ func (h *Health) Service(service, tag string, passingOnly bool, q *QueryOptions) r.params.Set("tag", tag) } if passingOnly { - r.params.Set("passing", "1") + r.params.Set(HealthPassing, "1") } rtt, resp, err := requireOK(h.c.doRequest(r)) if err != nil { @@ -108,11 +118,11 @@ func (h *Health) Service(service, tag string, passingOnly bool, q *QueryOptions) // The wildcard "any" state can also be used for all checks. func (h *Health) State(state string, q *QueryOptions) ([]*HealthCheck, *QueryMeta, error) { switch state { - case "any": - case "warning": - case "critical": - case "passing": - case "unknown": + case HealthAny: + case HealthWarning: + case HealthCritical: + case HealthPassing: + case HealthUnknown: default: return nil, nil, fmt.Errorf("Unsupported state: %v", state) } From 1e4da820a37fe990c8cfac4502865f42c85e1f77 Mon Sep 17 00:00:00 2001 From: Diptanu Choudhury Date: Thu, 24 Mar 2016 13:05:08 -0700 Subject: [PATCH 04/25] Running script checks periodically --- client/consul/check.go | 121 +++++++++++++++++++++++++++++ client/consul/sync.go | 57 +++++++++++++- client/driver/executor/checks.go | 119 ---------------------------- client/driver/executor/executor.go | 2 +- 4 files changed, 176 insertions(+), 123 deletions(-) diff --git a/client/consul/check.go b/client/consul/check.go index ac3604893ec0..37516b23da4b 100644 --- a/client/consul/check.go +++ b/client/consul/check.go @@ -1,6 +1,10 @@ package consul import ( + "container/heap" + "fmt" + "time" + cstructs "github.com/hashicorp/nomad/client/driver/structs" ) @@ -8,3 +12,120 @@ type Check interface { Run() *cstructs.CheckResult ID() string } + +type consulCheck struct { + check Check + next time.Time + index int +} + +type checkHeap struct { + index map[string]*consulCheck + heap checksHeapImp +} + +func NewConsulChecksHeap() *checkHeap { + return &checkHeap{ + index: make(map[string]*consulCheck), + heap: make(checksHeapImp, 0), + } +} + +func (c *checkHeap) Push(check Check, next time.Time) error { + if _, ok := c.index[check.ID()]; ok { + return fmt.Errorf("check %v already exists", check.ID()) + } + + cCheck := &consulCheck{check, next, 0} + + c.index[check.ID()] = cCheck + heap.Push(&c.heap, cCheck) + return nil +} + +func (c *checkHeap) Pop() *consulCheck { + if len(c.heap) == 0 { + return nil + } + + cCheck := heap.Pop(&c.heap).(*consulCheck) + delete(c.index, cCheck.check.ID()) + return cCheck +} + +func (c *checkHeap) Peek() *consulCheck { + if len(c.heap) == 0 { + return nil + } + return c.heap[0] +} + +func (c *checkHeap) Contains(check Check) bool { + _, ok := c.index[check.ID()] + return ok +} + +func (c *checkHeap) Update(check Check, next time.Time) error { + if cCheck, ok := c.index[check.ID()]; ok { + cCheck.check = check + cCheck.next = next + heap.Fix(&c.heap, cCheck.index) + return nil + } + + return fmt.Errorf("heap doesn't contain check %v", check.ID()) +} + +func (c *checkHeap) Remove(id string) error { + if cCheck, ok := c.index[id]; ok { + heap.Remove(&c.heap, cCheck.index) + delete(c.index, id) + return nil + } + return fmt.Errorf("heap doesn't contain check %v", id) +} + +func (c *checkHeap) Len() int { return len(c.heap) } + +type checksHeapImp []*consulCheck + +func (h checksHeapImp) Len() int { return len(h) } + +func (h checksHeapImp) Less(i, j int) bool { + // Two zero times should return false. + // Otherwise, zero is "greater" than any other time. + // (To sort it at the end of the list.) + // Sort such that zero times are at the end of the list. + iZero, jZero := h[i].next.IsZero(), h[j].next.IsZero() + if iZero && jZero { + return false + } else if iZero { + return false + } else if jZero { + return true + } + + return h[i].next.Before(h[j].next) +} + +func (h checksHeapImp) Swap(i, j int) { + h[i], h[j] = h[j], h[i] + h[i].index = i + h[j].index = j +} + +func (h *checksHeapImp) Push(x interface{}) { + n := len(*h) + check := x.(*consulCheck) + check.index = n + *h = append(*h, check) +} + +func (h *checksHeapImp) Pop() interface{} { + old := *h + n := len(old) + check := old[n-1] + check.index = -1 // for safety + *h = old[0 : n-1] + return check +} diff --git a/client/consul/sync.go b/client/consul/sync.go index e1cfb07e4f30..47c2a6e6d776 100644 --- a/client/consul/sync.go +++ b/client/consul/sync.go @@ -24,13 +24,15 @@ type ConsulService struct { task *structs.Task allocID string delegateChecks map[string]struct{} - createCheck func(structs.ServiceCheck, string) (Check, error) + createCheck func(*structs.ServiceCheck, string) (Check, error) trackedServices map[string]*consul.AgentService trackedChecks map[string]*structs.ServiceCheck + execChecks *checkHeap logger *log.Logger + updateCh chan struct{} shutdownCh chan struct{} shutdown bool shutdownLock sync.Mutex @@ -95,13 +97,15 @@ func NewConsulService(config *ConsulConfig, logger *log.Logger, allocID string) logger: logger, trackedServices: make(map[string]*consul.AgentService), trackedChecks: make(map[string]*structs.ServiceCheck), + execChecks: NewConsulChecksHeap(), shutdownCh: make(chan struct{}), + updateCh: make(chan struct{}), } return &consulService, nil } -func (c *ConsulService) SetDelegatedChecks(delegateChecks map[string]struct{}, createCheck func(structs.ServiceCheck, string) (Check, error)) *ConsulService { +func (c *ConsulService) SetDelegatedChecks(delegateChecks map[string]struct{}, createCheck func(*structs.ServiceCheck, string) (Check, error)) *ConsulService { c.delegateChecks = delegateChecks c.createCheck = createCheck return c @@ -236,7 +240,20 @@ func (c *ConsulService) registerCheck(check *structs.ServiceCheck, service *cons case structs.ServiceCheckTCP: chkReg.TCP = fmt.Sprintf("%s:%d", service.Address, service.Port) case structs.ServiceCheckScript: - chkReg.TTL = check.Interval.String() + chkReg.TTL = (check.Interval + 30*time.Second).String() + } + if _, ok := c.delegateChecks[check.Type]; !ok { + chk, err := c.createCheck(check, chkReg.ID) + if err != nil { + return err + } + if err := c.execChecks.Push(chk, time.Now().Add(check.Interval)); err != nil { + c.logger.Printf("[ERR] consulservice: unable to add check %q to heap", chk.ID()) + } + select { + case c.updateCh <- struct{}{}: + default: + } } return c.client.Agent().CheckRegister(&chkReg) } @@ -280,20 +297,31 @@ func (c *ConsulService) deregisterService(ID string) error { // deregisterCheck de-registers a check with a given ID from Consul. func (c *ConsulService) deregisterCheck(ID string) error { + if err := c.execChecks.Remove(ID); err != nil { + c.logger.Printf("[DEBUG] consulservice: unable to remove check with ID %q from heap", ID) + } return c.client.Agent().CheckDeregister(ID) } // PeriodicSync triggers periodic syncing of services and checks with Consul. // This is a long lived go-routine which is stopped during shutdown func (c *ConsulService) PeriodicSync() { + var runCheck <-chan time.Time sync := time.After(syncInterval) for { + runCheck = c.sleepBeforeRunningCheck() select { case <-sync: if err := c.performSync(); err != nil { c.logger.Printf("[DEBUG] consul: error in syncing task %q: %v", c.task.Name, err) } sync = time.After(syncInterval) + case <-c.updateCh: + continue + case <-runCheck: + chk := c.execChecks.heap.Pop().(consulCheck) + runCheck = c.sleepBeforeRunningCheck() + c.runCheck(chk.check) case <-c.shutdownCh: c.logger.Printf("[INFO] consul: shutting down sync for task %q", c.task.Name) return @@ -301,6 +329,13 @@ func (c *ConsulService) PeriodicSync() { } } +func (c *ConsulService) sleepBeforeRunningCheck() <-chan time.Time { + if c := c.execChecks.Peek(); c != nil { + return time.After(time.Now().Sub(c.next)) + } + return nil +} + // performSync sync the services and checks we are tracking with Consul. func (c *ConsulService) performSync() error { var mErr multierror.Error @@ -357,7 +392,23 @@ func (c *ConsulService) filterConsulChecks(chks map[string]*consul.AgentCheck) m return nomadChecks } +// consulPresent indicates whether the consul agent is responding func (c *ConsulService) consulPresent() bool { _, err := c.client.Agent().Self() return err == nil } + +// runCheck runs a check and updates the corresponding ttl check in consul +func (c *ConsulService) runCheck(check Check) { + res := check.Run() + if res.Err != nil { + c.client.Agent().UpdateTTL(check.ID(), res.Output, consul.HealthCritical) + } + if res.ExitCode == 0 { + c.client.Agent().UpdateTTL(check.ID(), res.Output, consul.HealthPassing) + } + if res.ExitCode == 1 { + c.client.Agent().UpdateTTL(check.ID(), res.Output, consul.HealthWarning) + } + c.client.Agent().UpdateTTL(check.ID(), res.Output, consul.HealthCritical) +} diff --git a/client/driver/executor/checks.go b/client/driver/executor/checks.go index cb035f70862d..adc4541b0617 100644 --- a/client/driver/executor/checks.go +++ b/client/driver/executor/checks.go @@ -1,7 +1,6 @@ package executor import ( - "container/heap" "fmt" "log" "os/exec" @@ -11,7 +10,6 @@ import ( "github.com/armon/circbuf" docker "github.com/fsouza/go-dockerclient" - "github.com/hashicorp/nomad/client/consul" cstructs "github.com/hashicorp/nomad/client/driver/structs" ) @@ -114,120 +112,3 @@ func (e *ExecScriptCheck) Run() *cstructs.CheckResult { func (e *ExecScriptCheck) ID() string { return e.id } - -type consulCheck struct { - check consul.Check - next time.Time - index int -} - -type checkHeap struct { - index map[string]*consulCheck - heap checksHeapImp -} - -func NewConsulChecksHeap() *checkHeap { - return &checkHeap{ - index: make(map[string]*consulCheck), - heap: make(checksHeapImp, 0), - } -} - -func (c *checkHeap) Push(check consul.Check, next time.Time) error { - if _, ok := c.index[check.ID()]; ok { - return fmt.Errorf("check %v already exists", check.ID()) - } - - cCheck := &consulCheck{check, next, 0} - - c.index[check.ID()] = cCheck - heap.Push(&c.heap, cCheck) - return nil -} - -func (c *checkHeap) Pop() *consulCheck { - if len(c.heap) == 0 { - return nil - } - - cCheck := heap.Pop(&c.heap).(*consulCheck) - delete(c.index, cCheck.check.ID()) - return cCheck -} - -func (c *checkHeap) Peek() *consulCheck { - if len(c.heap) == 0 { - return nil - } - return c.heap[0] -} - -func (c *checkHeap) Contains(check consul.Check) bool { - _, ok := c.index[check.ID()] - return ok -} - -func (c *checkHeap) Update(check consul.Check, next time.Time) error { - if cCheck, ok := c.index[check.ID()]; ok { - cCheck.check = check - cCheck.next = next - heap.Fix(&c.heap, cCheck.index) - return nil - } - - return fmt.Errorf("heap doesn't contain check %v", check.ID()) -} - -func (c *checkHeap) Remove(check consul.Check) error { - if cCheck, ok := c.index[check.ID()]; ok { - heap.Remove(&c.heap, cCheck.index) - delete(c.index, check.ID()) - return nil - } - return fmt.Errorf("heap doesn't contain check %v", check.ID()) -} - -func (c *checkHeap) Len() int { return len(c.heap) } - -type checksHeapImp []*consulCheck - -func (h checksHeapImp) Len() int { return len(h) } - -func (h checksHeapImp) Less(i, j int) bool { - // Two zero times should return false. - // Otherwise, zero is "greater" than any other time. - // (To sort it at the end of the list.) - // Sort such that zero times are at the end of the list. - iZero, jZero := h[i].next.IsZero(), h[j].next.IsZero() - if iZero && jZero { - return false - } else if iZero { - return false - } else if jZero { - return true - } - - return h[i].next.Before(h[j].next) -} - -func (h checksHeapImp) Swap(i, j int) { - h[i], h[j] = h[j], h[i] - h[i].index = i - h[j].index = j -} - -func (h *checksHeapImp) Push(x interface{}) { - n := len(*h) - check := x.(*consulCheck) - check.index = n - *h = append(*h, check) -} - -func (h *checksHeapImp) Pop() interface{} { - old := *h - n := len(old) - check := old[n-1] - check.index = -1 // for safety - *h = old[0 : n-1] - return check -} diff --git a/client/driver/executor/executor.go b/client/driver/executor/executor.go index 23da11f246db..811875e55cd1 100644 --- a/client/driver/executor/executor.go +++ b/client/driver/executor/executor.go @@ -493,7 +493,7 @@ func (e *UniversalExecutor) createCheckMap() map[string]struct{} { return checks } -func (e *UniversalExecutor) createCheck(check structs.ServiceCheck, checkID string) (consul.Check, error) { +func (e *UniversalExecutor) createCheck(check *structs.ServiceCheck, checkID string) (consul.Check, error) { if check.Type == structs.ServiceCheckScript && e.ctx.Driver == "docker" { return &DockerScriptCheck{ id: checkID, From f1cf87bb6ece109823626b734d11a8f871c9fdbb Mon Sep 17 00:00:00 2001 From: Diptanu Choudhury Date: Thu, 24 Mar 2016 14:26:13 -0700 Subject: [PATCH 05/25] Enabling script checks --- nomad/structs/structs.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index ab5968475c0c..cd90fcc26b43 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -1439,8 +1439,8 @@ func (sc *ServiceCheck) Copy() *ServiceCheck { func (sc *ServiceCheck) Validate() error { t := strings.ToLower(sc.Type) - if t != ServiceCheckTCP && t != ServiceCheckHTTP { - return fmt.Errorf("service check must be either http or tcp type") + if t != ServiceCheckTCP && t != ServiceCheckHTTP && t != ServiceCheckScript { + return fmt.Errorf("service check must be either http, tcp or script type") } if sc.Type == ServiceCheckHTTP && sc.Path == "" { return fmt.Errorf("service checks of http type must have a valid http path") From 588666d0dfee6f3ac441e904d81bb2ace165f000 Mon Sep 17 00:00:00 2001 From: Diptanu Choudhury Date: Thu, 24 Mar 2016 15:39:10 -0700 Subject: [PATCH 06/25] Introducing ConsulContext --- client/consul/sync.go | 4 ++-- client/driver/docker.go | 7 +++++-- client/driver/exec.go | 16 +++++++++------- client/driver/executor/executor.go | 25 +++++++++++++++---------- client/driver/executor_plugin.go | 13 +++++++++---- client/driver/java.go | 16 +++++++++------- client/driver/qemu.go | 16 +++++++++------- client/driver/raw_exec.go | 16 +++++++++------- client/driver/rkt.go | 17 +++++++++-------- client/driver/utils.go | 7 +++++-- 10 files changed, 81 insertions(+), 56 deletions(-) diff --git a/client/consul/sync.go b/client/consul/sync.go index b749c246eb26..fb34aa79d4c1 100644 --- a/client/consul/sync.go +++ b/client/consul/sync.go @@ -194,7 +194,7 @@ func (c *ConsulService) Shutdown() error { // of tasks passed to it func (c *ConsulService) KeepServices(tasks []*structs.Task) error { var mErr multierror.Error - var services map[string]struct{} + services := make(map[string]struct{}) // Indexing the services in the tasks for _, task := range tasks { @@ -252,7 +252,7 @@ func (c *ConsulService) createCheckReg(check *structs.ServiceCheck, service *con default: return nil, fmt.Errorf("check type %q not valid", check.Type) } - if _, ok := c.delegateChecks[check.Type]; !ok { + if _, ok := c.delegateChecks[check.Type]; ok { chk, err := c.createCheck(check, chkReg.ID) if err != nil { return nil, err diff --git a/client/driver/docker.go b/client/driver/docker.go index 21a8edfc60a1..d050e664e1b8 100644 --- a/client/driver/docker.go +++ b/client/driver/docker.go @@ -550,7 +550,6 @@ func (d *DockerDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle AllocID: ctx.AllocID, PortLowerBound: d.config.ClientMinPort, PortUpperBound: d.config.ClientMaxPort, - ConsulConfig: consulConfig(d.config), } ss, err := exec.LaunchSyslogServer(executorCtx) if err != nil { @@ -645,7 +644,7 @@ func (d *DockerDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle doneCh: make(chan struct{}), waitCh: make(chan *cstructs.WaitResult, 1), } - if err := exec.RegisterServices(); err != nil { + if err := exec.SyncServices(consulContext(d.config, container.ID)); err != nil { d.logger.Printf("[ERR] driver.docker: error registering services with consul for task: %q: %v", task.Name, err) } go h.run() @@ -717,6 +716,10 @@ func (d *DockerDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, er doneCh: make(chan struct{}), waitCh: make(chan *cstructs.WaitResult, 1), } + if err := exec.SyncServices(consulContext(d.config, pid.ContainerID)); err != nil { + h.logger.Printf("[ERR] driver.docker: error registering services with consul: %v", err) + } + go h.run() return h, nil } diff --git a/client/driver/exec.go b/client/driver/exec.go index d8dd8ef993f4..590bb90d2d9f 100644 --- a/client/driver/exec.go +++ b/client/driver/exec.go @@ -106,12 +106,11 @@ func (d *ExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, return nil, err } executorCtx := &executor.ExecutorContext{ - TaskEnv: d.taskEnv, - Driver: "exec", - AllocDir: ctx.AllocDir, - AllocID: ctx.AllocID, - Task: task, - ConsulConfig: consulConfig(d.config), + TaskEnv: d.taskEnv, + Driver: "exec", + AllocDir: ctx.AllocDir, + AllocID: ctx.AllocID, + Task: task, } ps, err := exec.LaunchCmd(&executor.ExecCommand{ @@ -142,7 +141,7 @@ func (d *ExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, doneCh: make(chan struct{}), waitCh: make(chan *cstructs.WaitResult, 1), } - if err := exec.RegisterServices(); err != nil { + if err := exec.SyncServices(consulContext(d.config, "")); err != nil { d.logger.Printf("[ERR] driver.exec: error registering services with consul for task: %q: %v", task.Name, err) } go h.run() @@ -202,6 +201,9 @@ func (d *ExecDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, erro doneCh: make(chan struct{}), waitCh: make(chan *cstructs.WaitResult, 1), } + if err := exec.SyncServices(consulContext(d.config, "")); err != nil { + d.logger.Printf("[ERR] driver.exec: error registering services with consul: %v", err) + } go h.run() return h, nil } diff --git a/client/driver/executor/executor.go b/client/driver/executor/executor.go index 811875e55cd1..646caa9f1d5e 100644 --- a/client/driver/executor/executor.go +++ b/client/driver/executor/executor.go @@ -35,10 +35,19 @@ type Executor interface { Exit() error UpdateLogConfig(logConfig *structs.LogConfig) error UpdateTask(task *structs.Task) error - RegisterServices() error + SyncServices(ctx *ConsulContext) error DeregisterServices() error } +// ConsulContext holds context to configure the consul client and run checks +type ConsulContext struct { + // ConsulConfig is the configuration used to create a consul client + ConsulConfig *consul.ConsulConfig + + // ContainerID is the ID of the container + ContainerID string +} + // ExecutorContext holds context to configure the command user // wants to run and isolate it type ExecutorContext struct { @@ -58,9 +67,6 @@ type ExecutorContext struct { // Driver is the name of the driver that invoked the executor Driver string - // ContainerID is the ID of the container - ContainerID string - // PortUpperBound is the upper bound of the ports that we can use to start // the syslog server PortUpperBound uint @@ -68,9 +74,6 @@ type ExecutorContext struct { // PortLowerBound is the lower bound of the ports that we can use to start // the syslog server PortLowerBound uint - - // ConsulConfig is the configuration used to create a consul client - ConsulConfig *consul.ConsulConfig } // ExecCommand holds the user command, args, and other isolation related @@ -132,6 +135,7 @@ type UniversalExecutor struct { cgLock sync.Mutex consulService *consul.ConsulService + consulCtx *ConsulContext logger *log.Logger } @@ -358,10 +362,11 @@ func (e *UniversalExecutor) ShutDown() error { return nil } -func (e *UniversalExecutor) RegisterServices() error { +func (e *UniversalExecutor) SyncServices(ctx *ConsulContext) error { e.logger.Printf("[INFO] executor: registering services") + e.consulCtx = ctx if e.consulService == nil { - cs, err := consul.NewConsulService(e.ctx.ConsulConfig, e.logger, e.ctx.AllocID) + cs, err := consul.NewConsulService(ctx.ConsulConfig, e.logger, e.ctx.AllocID) if err != nil { return err } @@ -497,7 +502,7 @@ func (e *UniversalExecutor) createCheck(check *structs.ServiceCheck, checkID str if check.Type == structs.ServiceCheckScript && e.ctx.Driver == "docker" { return &DockerScriptCheck{ id: checkID, - containerID: e.ctx.ContainerID, + containerID: e.consulCtx.ContainerID, logger: e.logger, cmd: check.Cmd, args: check.Args, diff --git a/client/driver/executor_plugin.go b/client/driver/executor_plugin.go index a55bb3d91674..c8561f74cacd 100644 --- a/client/driver/executor_plugin.go +++ b/client/driver/executor_plugin.go @@ -34,6 +34,11 @@ type LaunchSyslogServerArgs struct { Ctx *executor.ExecutorContext } +// SyncServicesArgs wraps the consul context for the purposes of RPC +type SyncServicesArgs struct { + Ctx *executor.ConsulContext +} + func (e *ExecutorRPC) LaunchCmd(cmd *executor.ExecCommand, ctx *executor.ExecutorContext) (*executor.ProcessState, error) { var ps *executor.ProcessState err := e.client.Call("Plugin.LaunchCmd", LaunchCmdArgs{Cmd: cmd, Ctx: ctx}, &ps) @@ -68,8 +73,8 @@ func (e *ExecutorRPC) UpdateTask(task *structs.Task) error { return e.client.Call("Plugin.UpdateTask", task, new(interface{})) } -func (e *ExecutorRPC) RegisterServices() error { - return e.client.Call("Plugin.RegisterServices", new(interface{}), new(interface{})) +func (e *ExecutorRPC) SyncServices(ctx *executor.ConsulContext) error { + return e.client.Call("Plugin.SyncServices", SyncServicesArgs{Ctx: ctx}, new(interface{})) } func (e *ExecutorRPC) DeregisterServices() error { @@ -120,8 +125,8 @@ func (e *ExecutorRPCServer) UpdateTask(args *structs.Task, resp *interface{}) er return e.Impl.UpdateTask(args) } -func (e *ExecutorRPCServer) RegisterServices(args interface{}, resp *interface{}) error { - return e.Impl.RegisterServices() +func (e *ExecutorRPCServer) SyncServices(args SyncServicesArgs, resp *interface{}) error { + return e.Impl.SyncServices(args.Ctx) } func (e *ExecutorRPCServer) DeregisterServices(args interface{}, resp *interface{}) error { diff --git a/client/driver/java.go b/client/driver/java.go index ea7d9bc67e83..4e71ceb459dd 100644 --- a/client/driver/java.go +++ b/client/driver/java.go @@ -159,12 +159,11 @@ func (d *JavaDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, return nil, err } executorCtx := &executor.ExecutorContext{ - TaskEnv: d.taskEnv, - Driver: "java", - AllocDir: ctx.AllocDir, - AllocID: ctx.AllocID, - Task: task, - ConsulConfig: consulConfig(d.config), + TaskEnv: d.taskEnv, + Driver: "java", + AllocDir: ctx.AllocDir, + AllocID: ctx.AllocID, + Task: task, } absPath, err := GetAbsolutePath("java") @@ -201,7 +200,7 @@ func (d *JavaDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, doneCh: make(chan struct{}), waitCh: make(chan *cstructs.WaitResult, 1), } - if err := h.executor.RegisterServices(); err != nil { + if err := h.executor.SyncServices(consulContext(d.config, "")); err != nil { d.logger.Printf("[ERR] driver.java: error registering services with consul for task: %q: %v", task.Name, err) } go h.run() @@ -270,6 +269,9 @@ func (d *JavaDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, erro doneCh: make(chan struct{}), waitCh: make(chan *cstructs.WaitResult, 1), } + if err := h.executor.SyncServices(consulContext(d.config, "")); err != nil { + d.logger.Printf("[ERR] driver.java: error registering services with consul: %v", err) + } go h.run() return h, nil diff --git a/client/driver/qemu.go b/client/driver/qemu.go index 42adbfaacfab..e64633b88a1b 100644 --- a/client/driver/qemu.go +++ b/client/driver/qemu.go @@ -191,12 +191,11 @@ func (d *QemuDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, return nil, err } executorCtx := &executor.ExecutorContext{ - TaskEnv: d.taskEnv, - Driver: "qemu", - AllocDir: ctx.AllocDir, - AllocID: ctx.AllocID, - Task: task, - ConsulConfig: consulConfig(d.config), + TaskEnv: d.taskEnv, + Driver: "qemu", + AllocDir: ctx.AllocDir, + AllocID: ctx.AllocID, + Task: task, } ps, err := exec.LaunchCmd(&executor.ExecCommand{ Cmd: args[0], @@ -224,7 +223,7 @@ func (d *QemuDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, waitCh: make(chan *cstructs.WaitResult, 1), } - if err := h.executor.RegisterServices(); err != nil { + if err := h.executor.SyncServices(consulContext(d.config, "")); err != nil { h.logger.Printf("[ERR] driver.qemu: error registering services for task: %q: %v", task.Name, err) } go h.run() @@ -272,6 +271,9 @@ func (d *QemuDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, erro doneCh: make(chan struct{}), waitCh: make(chan *cstructs.WaitResult, 1), } + if err := h.executor.SyncServices(consulContext(d.config, "")); err != nil { + h.logger.Printf("[ERR] driver.qemu: error registering services: %v", err) + } go h.run() return h, nil } diff --git a/client/driver/raw_exec.go b/client/driver/raw_exec.go index 1afe84ad1b2c..e1619ab0af7b 100644 --- a/client/driver/raw_exec.go +++ b/client/driver/raw_exec.go @@ -101,12 +101,11 @@ func (d *RawExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandl return nil, err } executorCtx := &executor.ExecutorContext{ - TaskEnv: d.taskEnv, - Driver: "raw_exec", - AllocDir: ctx.AllocDir, - AllocID: ctx.AllocID, - Task: task, - ConsulConfig: consulConfig(d.config), + TaskEnv: d.taskEnv, + Driver: "raw_exec", + AllocDir: ctx.AllocDir, + AllocID: ctx.AllocID, + Task: task, } ps, err := exec.LaunchCmd(&executor.ExecCommand{ @@ -134,7 +133,7 @@ func (d *RawExecDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandl doneCh: make(chan struct{}), waitCh: make(chan *cstructs.WaitResult, 1), } - if err := h.executor.RegisterServices(); err != nil { + if err := h.executor.SyncServices(consulContext(d.config, "")); err != nil { h.logger.Printf("[ERR] driver.raw_exec: error registering services with consul for task: %q: %v", task.Name, err) } go h.run() @@ -181,6 +180,9 @@ func (d *RawExecDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, e doneCh: make(chan struct{}), waitCh: make(chan *cstructs.WaitResult, 1), } + if err := h.executor.SyncServices(consulContext(d.config, "")); err != nil { + h.logger.Printf("[ERR] driver.raw_exec: error registering services with consul: %v", err) + } go h.run() return h, nil } diff --git a/client/driver/rkt.go b/client/driver/rkt.go index 2a0939d0419c..4398beb9a91a 100644 --- a/client/driver/rkt.go +++ b/client/driver/rkt.go @@ -233,12 +233,11 @@ func (d *RktDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, e return nil, err } executorCtx := &executor.ExecutorContext{ - TaskEnv: d.taskEnv, - Driver: "rkt", - AllocDir: ctx.AllocDir, - AllocID: ctx.AllocID, - Task: task, - ConsulConfig: consulConfig(d.config), + TaskEnv: d.taskEnv, + Driver: "rkt", + AllocDir: ctx.AllocDir, + AllocID: ctx.AllocID, + Task: task, } absPath, err := GetAbsolutePath("rkt") @@ -269,7 +268,7 @@ func (d *RktDriver) Start(ctx *ExecContext, task *structs.Task) (DriverHandle, e doneCh: make(chan struct{}), waitCh: make(chan *cstructs.WaitResult, 1), } - if h.executor.RegisterServices(); err != nil { + if h.executor.SyncServices(consulContext(d.config, "")); err != nil { h.logger.Printf("[ERR] driver.rkt: error registering services for task: %q: %v", task.Name, err) } go h.run() @@ -308,7 +307,9 @@ func (d *RktDriver) Open(ctx *ExecContext, handleID string) (DriverHandle, error doneCh: make(chan struct{}), waitCh: make(chan *cstructs.WaitResult, 1), } - + if h.executor.SyncServices(consulContext(d.config, "")); err != nil { + h.logger.Printf("[ERR] driver.rkt: error registering services: %v", err) + } go h.run() return h, nil } diff --git a/client/driver/utils.go b/client/driver/utils.go index b2a25364b3b2..fa7bfe114d52 100644 --- a/client/driver/utils.go +++ b/client/driver/utils.go @@ -72,7 +72,7 @@ func createLogCollector(config *plugin.ClientConfig, w io.Writer, return logCollector, syslogClient, nil } -func consulConfig(clientConfig *config.Config) *consul.ConsulConfig { +func consulContext(clientConfig *config.Config, containerID string) *executor.ConsulContext { cfg := consul.ConsulConfig{ Addr: clientConfig.ReadDefault("consul.address", "127.0.0.1:8500"), Token: clientConfig.Read("consul.token"), @@ -80,7 +80,10 @@ func consulConfig(clientConfig *config.Config) *consul.ConsulConfig { EnableSSL: clientConfig.ReadBoolDefault("consul.ssl", false), VerifySSL: clientConfig.ReadBoolDefault("consul.verifyssl", true), } - return &cfg + return &executor.ConsulContext{ + ConsulConfig: &cfg, + ContainerID: containerID, + } } // killProcess kills a process with the given pid From fe9e271619c9a3430014dadd7f8066429c480b8c Mon Sep 17 00:00:00 2001 From: Diptanu Choudhury Date: Thu, 24 Mar 2016 16:15:22 -0700 Subject: [PATCH 07/25] Creating the docker driver in the executor properly --- client/driver/executor/checks.go | 60 +++++++++++++++++++++++++----- client/driver/executor/executor.go | 13 +++++++ client/driver/utils.go | 8 +++- 3 files changed, 69 insertions(+), 12 deletions(-) diff --git a/client/driver/executor/checks.go b/client/driver/executor/checks.go index adc4541b0617..40cf3681dd74 100644 --- a/client/driver/executor/checks.go +++ b/client/driver/executor/checks.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "os/exec" + "sync" "syscall" "time" @@ -13,16 +14,61 @@ import ( cstructs "github.com/hashicorp/nomad/client/driver/structs" ) +var ( + // We store the client globally to cache the connection to the docker daemon. + createClient sync.Once + client *docker.Client +) + type DockerScriptCheck struct { id string containerID string - client *docker.Client logger *log.Logger cmd string args []string + + dockerEndpoint string + tlsCert string + tlsCa string + tlsKey string +} + +func (d *DockerScriptCheck) dockerClient() (*docker.Client, error) { + if client != nil { + return client, nil + } + + var err error + createClient.Do(func() { + if d.dockerEndpoint != "" { + if d.tlsCert+d.tlsKey+d.tlsCa != "" { + d.logger.Printf("[DEBUG] driver.docker: using TLS client connection to %s", d.dockerEndpoint) + client, err = docker.NewTLSClient(d.dockerEndpoint, d.tlsCert, d.tlsKey, d.tlsCa) + } else { + d.logger.Printf("[DEBUG] driver.docker: using standard client connection to %s", d.dockerEndpoint) + client, err = docker.NewClient(d.dockerEndpoint) + } + return + } + + d.logger.Println("[DEBUG] driver.docker: using client connection initialized from environment") + client, err = docker.NewClientFromEnv() + }) + return client, err } func (d *DockerScriptCheck) Run() *cstructs.CheckResult { + var ( + exec *docker.Exec + err error + execRes *docker.ExecInspect + time = time.Now() + ) + + if client, err = d.dockerClient(); err != nil { + return &cstructs.CheckResult{Err: err} + } + client = client execOpts := docker.CreateExecOptions{ AttachStdin: false, AttachStdout: true, @@ -31,13 +77,7 @@ func (d *DockerScriptCheck) Run() *cstructs.CheckResult { Cmd: append([]string{d.cmd}, d.args...), Container: d.containerID, } - var ( - exec *docker.Exec - err error - execRes *docker.ExecInspect - time = time.Now() - ) - if exec, err = d.client.CreateExec(execOpts); err != nil { + if exec, err = client.CreateExec(execOpts); err != nil { return &cstructs.CheckResult{Err: err} } @@ -49,10 +89,10 @@ func (d *DockerScriptCheck) Run() *cstructs.CheckResult { ErrorStream: output, } - if err = d.client.StartExec(exec.ID, startOpts); err != nil { + if err = client.StartExec(exec.ID, startOpts); err != nil { return &cstructs.CheckResult{Err: err} } - if execRes, err = d.client.InspectExec(exec.ID); err != nil { + if execRes, err = client.InspectExec(exec.ID); err != nil { return &cstructs.CheckResult{Err: err} } return &cstructs.CheckResult{ diff --git a/client/driver/executor/executor.go b/client/driver/executor/executor.go index 646caa9f1d5e..0f98f18daac8 100644 --- a/client/driver/executor/executor.go +++ b/client/driver/executor/executor.go @@ -46,6 +46,19 @@ type ConsulContext struct { // ContainerID is the ID of the container ContainerID string + + // TLSCert is the cert which docker client uses while interactng with the docker + // daemon over TLS + TLSCert string + + // TLSCa is the CA which the docker client uses while interacting with the docker + // daeemon over TLS + TLSCa string + + TLSKey string + + // DockerEndpoint is the endpoint of the docker daemon + DockerEndpoint string } // ExecutorContext holds context to configure the command user diff --git a/client/driver/utils.go b/client/driver/utils.go index fa7bfe114d52..baab9c31ae1b 100644 --- a/client/driver/utils.go +++ b/client/driver/utils.go @@ -81,8 +81,12 @@ func consulContext(clientConfig *config.Config, containerID string) *executor.Co VerifySSL: clientConfig.ReadBoolDefault("consul.verifyssl", true), } return &executor.ConsulContext{ - ConsulConfig: &cfg, - ContainerID: containerID, + ConsulConfig: &cfg, + ContainerID: containerID, + DockerEndpoint: clientConfig.Read("docker.endpoint"), + TLSCa: clientConfig.Read("docker.tls.ca"), + TLSCert: clientConfig.Read("docker.tls.cert"), + TLSKey: clientConfig.Read("docker.tls.key"), } } From 52d4b01b44b7644aff096c40c04e6a0328eee7f2 Mon Sep 17 00:00:00 2001 From: Diptanu Choudhury Date: Thu, 24 Mar 2016 16:33:04 -0700 Subject: [PATCH 08/25] Added a test for the exec script check --- client/driver/executor/checks_linux.go | 2 +- client/driver/executor/checks_test.go | 30 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 client/driver/executor/checks_test.go diff --git a/client/driver/executor/checks_linux.go b/client/driver/executor/checks_linux.go index d4a684a8bf34..9c384ec55acb 100644 --- a/client/driver/executor/checks_linux.go +++ b/client/driver/executor/checks_linux.go @@ -10,7 +10,7 @@ func (e *ExecScriptCheck) setChroot(cmd *exec.Cmd) { if cmd.SysProcAttr == nil { cmd.SysProcAttr = &syscall.SysProcAttr{} } + cmd.SysProcAttr.Chroot = e.taskDir } - cmd.SysProcAttr.Chroot = e.taskDir cmd.Dir = "/" } diff --git a/client/driver/executor/checks_test.go b/client/driver/executor/checks_test.go new file mode 100644 index 000000000000..15f49265ad3a --- /dev/null +++ b/client/driver/executor/checks_test.go @@ -0,0 +1,30 @@ +package executor + +import ( + "strings" + "testing" +) + +func TestExecScriptCheckNoIsolation(t *testing.T) { + check := &ExecScriptCheck{ + id: "foo", + cmd: "/bin/echo", + args: []string{"hello", "world"}, + taskDir: "/tmp", + FSIsolation: false, + } + + res := check.Run() + expectedOutput := "hello world" + expectedExitCode := 0 + if res.Err != nil { + t.Fatalf("err: %v", res.Err) + } + if strings.TrimSpace(res.Output) != expectedOutput { + t.Fatalf("output expected: %v, actual: %v", expectedOutput, res.Output) + } + + if res.ExitCode != expectedExitCode { + t.Fatalf("exitcode expected: %v, actual: %v", expectedExitCode, res.ExitCode) + } +} From 8342d7dd71ed8681fdb6eacd8cfba8bd1ccd1b4c Mon Sep 17 00:00:00 2001 From: Diptanu Choudhury Date: Thu, 24 Mar 2016 17:42:00 -0700 Subject: [PATCH 09/25] Updated the vendored copy of github.com/fsouza/go-dockerclient --- Godeps/Godeps.json | 36 +- .../fsouza/go-dockerclient/.travis.yml | 12 +- .../github.com/fsouza/go-dockerclient/AUTHORS | 4 + .../github.com/fsouza/go-dockerclient/LICENSE | 2 +- .../fsouza/go-dockerclient/Makefile | 23 +- .../fsouza/go-dockerclient/README.markdown | 2 +- .../fsouza/go-dockerclient/client.go | 4 +- .../fsouza/go-dockerclient/container.go | 139 +- .../fsouza/go-dockerclient/event.go | 70 +- .../github.com/gorilla/context/LICENSE | 27 - .../github.com/gorilla/context/README.md | 7 - .../github.com/gorilla/context/context.go | 143 -- .../github.com/gorilla/context/doc.go | 82 -- .../external/github.com/gorilla/mux/LICENSE | 27 - .../external/github.com/gorilla/mux/README.md | 240 ---- .../external/github.com/gorilla/mux/doc.go | 206 --- .../external/github.com/gorilla/mux/mux.go | 481 ------- .../external/github.com/gorilla/mux/regexp.go | 317 ----- .../external/github.com/gorilla/mux/route.go | 595 -------- .../fsouza/go-dockerclient/image.go | 23 + .../github.com/fsouza/go-dockerclient/misc.go | 75 +- .../fsouza/go-dockerclient/network.go | 65 +- .../testing/data/.dockerignore | 3 - .../go-dockerclient/testing/data/Dockerfile | 15 - .../go-dockerclient/testing/data/barfile | 0 .../go-dockerclient/testing/data/ca.pem | 18 - .../go-dockerclient/testing/data/cert.pem | 18 - .../testing/data/container.tar | Bin 2048 -> 0 bytes .../testing/data/dockerfile.tar | Bin 2560 -> 0 bytes .../go-dockerclient/testing/data/foofile | 0 .../go-dockerclient/testing/data/key.pem | 27 - .../go-dockerclient/testing/data/server.pem | 18 - .../testing/data/serverkey.pem | 27 - .../fsouza/go-dockerclient/testing/server.go | 1246 ----------------- 34 files changed, 369 insertions(+), 3583 deletions(-) delete mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/context/LICENSE delete mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/context/README.md delete mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/context/context.go delete mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/context/doc.go delete mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/LICENSE delete mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/README.md delete mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/doc.go delete mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/mux.go delete mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/regexp.go delete mode 100644 vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/route.go delete mode 100644 vendor/github.com/fsouza/go-dockerclient/testing/data/.dockerignore delete mode 100644 vendor/github.com/fsouza/go-dockerclient/testing/data/Dockerfile delete mode 100644 vendor/github.com/fsouza/go-dockerclient/testing/data/barfile delete mode 100644 vendor/github.com/fsouza/go-dockerclient/testing/data/ca.pem delete mode 100644 vendor/github.com/fsouza/go-dockerclient/testing/data/cert.pem delete mode 100644 vendor/github.com/fsouza/go-dockerclient/testing/data/container.tar delete mode 100644 vendor/github.com/fsouza/go-dockerclient/testing/data/dockerfile.tar delete mode 100644 vendor/github.com/fsouza/go-dockerclient/testing/data/foofile delete mode 100644 vendor/github.com/fsouza/go-dockerclient/testing/data/key.pem delete mode 100644 vendor/github.com/fsouza/go-dockerclient/testing/data/server.pem delete mode 100644 vendor/github.com/fsouza/go-dockerclient/testing/data/serverkey.pem delete mode 100644 vendor/github.com/fsouza/go-dockerclient/testing/server.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index be5653492a5d..f66d38a3110a 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -154,75 +154,75 @@ }, { "ImportPath": "github.com/fsouza/go-dockerclient", - "Rev": "02a8beb401b20e112cff3ea740545960b667eab1" + "Rev": "7c07ffce0f7e14a4da49ce92a2842d4e87be1c1e" }, { "ImportPath": "github.com/fsouza/go-dockerclient/external/github.com/Sirupsen/logrus", - "Rev": "02a8beb401b20e112cff3ea740545960b667eab1" + "Rev": "7c07ffce0f7e14a4da49ce92a2842d4e87be1c1e" }, { "ImportPath": "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/opts", - "Rev": "02a8beb401b20e112cff3ea740545960b667eab1" + "Rev": "7c07ffce0f7e14a4da49ce92a2842d4e87be1c1e" }, { "ImportPath": "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/archive", - "Rev": "02a8beb401b20e112cff3ea740545960b667eab1" + "Rev": "7c07ffce0f7e14a4da49ce92a2842d4e87be1c1e" }, { "ImportPath": "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/fileutils", - "Rev": "02a8beb401b20e112cff3ea740545960b667eab1" + "Rev": "7c07ffce0f7e14a4da49ce92a2842d4e87be1c1e" }, { "ImportPath": "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/homedir", - "Rev": "02a8beb401b20e112cff3ea740545960b667eab1" + "Rev": "7c07ffce0f7e14a4da49ce92a2842d4e87be1c1e" }, { "ImportPath": "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/idtools", - "Rev": "02a8beb401b20e112cff3ea740545960b667eab1" + "Rev": "7c07ffce0f7e14a4da49ce92a2842d4e87be1c1e" }, { "ImportPath": "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/ioutils", - "Rev": "02a8beb401b20e112cff3ea740545960b667eab1" + "Rev": "7c07ffce0f7e14a4da49ce92a2842d4e87be1c1e" }, { "ImportPath": "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/longpath", - "Rev": "02a8beb401b20e112cff3ea740545960b667eab1" + "Rev": "7c07ffce0f7e14a4da49ce92a2842d4e87be1c1e" }, { "ImportPath": "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/pools", - "Rev": "02a8beb401b20e112cff3ea740545960b667eab1" + "Rev": "7c07ffce0f7e14a4da49ce92a2842d4e87be1c1e" }, { "ImportPath": "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/promise", - "Rev": "02a8beb401b20e112cff3ea740545960b667eab1" + "Rev": "7c07ffce0f7e14a4da49ce92a2842d4e87be1c1e" }, { "ImportPath": "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/stdcopy", - "Rev": "02a8beb401b20e112cff3ea740545960b667eab1" + "Rev": "7c07ffce0f7e14a4da49ce92a2842d4e87be1c1e" }, { "ImportPath": "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/system", - "Rev": "02a8beb401b20e112cff3ea740545960b667eab1" + "Rev": "7c07ffce0f7e14a4da49ce92a2842d4e87be1c1e" }, { "ImportPath": "github.com/fsouza/go-dockerclient/external/github.com/docker/go-units", - "Rev": "02a8beb401b20e112cff3ea740545960b667eab1" + "Rev": "7c07ffce0f7e14a4da49ce92a2842d4e87be1c1e" }, { "ImportPath": "github.com/fsouza/go-dockerclient/external/github.com/hashicorp/go-cleanhttp", - "Rev": "02a8beb401b20e112cff3ea740545960b667eab1" + "Rev": "7c07ffce0f7e14a4da49ce92a2842d4e87be1c1e" }, { "ImportPath": "github.com/fsouza/go-dockerclient/external/github.com/opencontainers/runc/libcontainer/user", - "Rev": "02a8beb401b20e112cff3ea740545960b667eab1" + "Rev": "7c07ffce0f7e14a4da49ce92a2842d4e87be1c1e" }, { "ImportPath": "github.com/fsouza/go-dockerclient/external/golang.org/x/net/context", - "Rev": "02a8beb401b20e112cff3ea740545960b667eab1" + "Rev": "7c07ffce0f7e14a4da49ce92a2842d4e87be1c1e" }, { "ImportPath": "github.com/fsouza/go-dockerclient/external/golang.org/x/sys/unix", - "Rev": "02a8beb401b20e112cff3ea740545960b667eab1" + "Rev": "7c07ffce0f7e14a4da49ce92a2842d4e87be1c1e" }, { "ImportPath": "github.com/go-ini/ini", diff --git a/vendor/github.com/fsouza/go-dockerclient/.travis.yml b/vendor/github.com/fsouza/go-dockerclient/.travis.yml index aee4e902a4b8..a96df786526b 100644 --- a/vendor/github.com/fsouza/go-dockerclient/.travis.yml +++ b/vendor/github.com/fsouza/go-dockerclient/.travis.yml @@ -4,7 +4,7 @@ go: - 1.3.3 - 1.4.2 - 1.5.3 - - 1.6rc1 + - 1.6 - tip env: - GOARCH=amd64 DOCKER_VERSION=1.7.1 @@ -13,10 +13,16 @@ env: - GOARCH=386 DOCKER_VERSION=1.8.3 - GOARCH=amd64 DOCKER_VERSION=1.9.1 - GOARCH=386 DOCKER_VERSION=1.9.1 + - GOARCH=amd64 DOCKER_VERSION=1.10.3 + - GOARCH=386 DOCKER_VERSION=1.10.3 install: - - make prepare_docker + - travis_retry make prepare_docker script: - - make test + - travis-scripts/run-tests.bash - DOCKER_HOST=tcp://127.0.0.1:2375 make integration services: - docker +matrix: + fast_finish: true + allow_failures: + - go: tip diff --git a/vendor/github.com/fsouza/go-dockerclient/AUTHORS b/vendor/github.com/fsouza/go-dockerclient/AUTHORS index 0c42ae3444a9..c0913a55543d 100644 --- a/vendor/github.com/fsouza/go-dockerclient/AUTHORS +++ b/vendor/github.com/fsouza/go-dockerclient/AUTHORS @@ -14,6 +14,7 @@ Ben Marini Ben McCann Ben Parees Benno van den Berg +Bradley Cicenas Brendan Fosberry Brian Lalor Brian P. Hamachek @@ -48,6 +49,7 @@ Fabio Rehm Fatih Arslan Flavia Missi Francisco Souza +George Moura Grégoire Delattre Guillermo Álvarez Fernández Harry Zhang @@ -84,7 +86,9 @@ Michael Schmatz Michal Fojtik Mike Dillon Mrunal Patel +Nate Jones Nguyen Sy Thanh Son +Nicholas Van Wiggeren Nick Ethier Omeid Matten Orivej Desh diff --git a/vendor/github.com/fsouza/go-dockerclient/LICENSE b/vendor/github.com/fsouza/go-dockerclient/LICENSE index 4e11de1007ae..b1cdd4cd20cc 100644 --- a/vendor/github.com/fsouza/go-dockerclient/LICENSE +++ b/vendor/github.com/fsouza/go-dockerclient/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2015, go-dockerclient authors +Copyright (c) 2016, go-dockerclient authors All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/vendor/github.com/fsouza/go-dockerclient/Makefile b/vendor/github.com/fsouza/go-dockerclient/Makefile index 205d8f3c22db..86b7e0bbb13a 100644 --- a/vendor/github.com/fsouza/go-dockerclient/Makefile +++ b/vendor/github.com/fsouza/go-dockerclient/Makefile @@ -11,8 +11,7 @@ cov \ clean -SRCS = $(shell git ls-files '*.go' | grep -v '^external/') -PKGS = ./. ./testing +PKGS = . ./testing all: test @@ -22,32 +21,40 @@ vendor: lint: @ go get -v github.com/golang/lint/golint - $(foreach file,$(SRCS),golint $(file) || exit;) + @for file in $$(git ls-files '*.go' | grep -v 'external/'); do \ + export output="$$(golint $${file} | grep -v 'type name will be used as docker.DockerInfo')"; \ + [ -n "$${output}" ] && echo "$${output}" && export status=1; \ + done; \ + exit $${status:-0} vet: @-go get -v golang.org/x/tools/cmd/vet $(foreach pkg,$(PKGS),go vet $(pkg);) fmt: - gofmt -w $(SRCS) + gofmt -s -w $(PKGS) fmtcheck: - $(foreach file,$(SRCS),gofmt -d $(file);) + @ export output=$$(gofmt -s -d $(PKGS)); \ + [ -n "$${output}" ] && echo "$${output}" && export status=1; \ + exit $${status:-0} prepare_docker: - sudo stop docker + sudo stop docker || true sudo rm -rf /var/lib/docker sudo rm -f `which docker` sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D echo "deb https://apt.dockerproject.org/repo ubuntu-trusty main" | sudo tee /etc/apt/sources.list.d/docker.list sudo apt-get update - sudo apt-get install docker-engine=$(DOCKER_VERSION)-0~$(shell lsb_release -cs) -y --force-yes + sudo apt-get install docker-engine=$(DOCKER_VERSION)-0~$(shell lsb_release -cs) -y --force-yes -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" pretest: lint vet fmtcheck -test: pretest +gotest: $(foreach pkg,$(PKGS),go test $(pkg) || exit;) +test: pretest gotest + integration: go test -tags docker_integration -run TestIntegration -v diff --git a/vendor/github.com/fsouza/go-dockerclient/README.markdown b/vendor/github.com/fsouza/go-dockerclient/README.markdown index b75a7e920b64..b915039f19b9 100644 --- a/vendor/github.com/fsouza/go-dockerclient/README.markdown +++ b/vendor/github.com/fsouza/go-dockerclient/README.markdown @@ -4,7 +4,7 @@ [![GoDoc](https://img.shields.io/badge/api-Godoc-blue.svg?style=flat-square)](https://godoc.org/github.com/fsouza/go-dockerclient) This package presents a client for the Docker remote API. It also provides -support for the extensions in the [Swarm API](https://docs.docker.com/swarm/api/swarm-api/). +support for the extensions in the [Swarm API](https://docs.docker.com/swarm/swarm-api/). This package also provides support for docker's network API, which is a simple passthrough to the libnetwork remote API. Note that docker's network API is diff --git a/vendor/github.com/fsouza/go-dockerclient/client.go b/vendor/github.com/fsouza/go-dockerclient/client.go index cf9d616789d3..d893ba684ab9 100644 --- a/vendor/github.com/fsouza/go-dockerclient/client.go +++ b/vendor/github.com/fsouza/go-dockerclient/client.go @@ -555,6 +555,8 @@ type hijackOptions struct { data interface{} } +// CloseWaiter is an interface with methods for closing the underlying resource +// and then waiting for it to finish processing. type CloseWaiter interface { io.Closer Wait() error @@ -587,7 +589,7 @@ func (c *Client) hijack(method, path string, hijackOptions hijackOptions) (Close if err != nil { return nil, err } - req.Header.Set("Content-Type", "plain/text") + req.Header.Set("Content-Type", "application/json") req.Header.Set("Connection", "Upgrade") req.Header.Set("Upgrade", "tcp") protocol := c.endpointURL.Scheme diff --git a/vendor/github.com/fsouza/go-dockerclient/container.go b/vendor/github.com/fsouza/go-dockerclient/container.go index 317814b90bfb..c75318895182 100644 --- a/vendor/github.com/fsouza/go-dockerclient/container.go +++ b/vendor/github.com/fsouza/go-dockerclient/container.go @@ -52,7 +52,14 @@ type APIContainers struct { SizeRw int64 `json:"SizeRw,omitempty" yaml:"SizeRw,omitempty"` SizeRootFs int64 `json:"SizeRootFs,omitempty" yaml:"SizeRootFs,omitempty"` Names []string `json:"Names,omitempty" yaml:"Names,omitempty"` - Labels map[string]string `json:"Labels,omitempty" yaml:"Labels, omitempty"` + Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty"` + Networks NetworkList `json:"NetworkSettings,omitempty" yaml:"NetworkSettings,omitempty"` +} + +// NetworkList encapsulates a map of networks, as returned by the Docker API in +// ListContainers. +type NetworkList struct { + Networks map[string]ContainerNetwork `json:"Networks" yaml:"Networks,omitempty"` } // ListContainers returns a slice of containers matching the given criteria. @@ -135,6 +142,7 @@ type ContainerNetwork struct { IPAddress string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty"` Gateway string `json:"Gateway,omitempty" yaml:"Gateway,omitempty"` EndpointID string `json:"EndpointID,omitempty" yaml:"EndpointID,omitempty"` + NetworkID string `json:"NetworkID,omitempty" yaml:"NetworkID,omitempty"` } // NetworkSettings contains network-related information about a container @@ -308,6 +316,34 @@ type Container struct { AppArmorProfile string `json:"AppArmorProfile,omitempty" yaml:"AppArmorProfile,omitempty"` } +// UpdateContainerOptions specify parameters to the UpdateContainer function. +// +// See https://goo.gl/Y6fXUy for more details. +type UpdateContainerOptions struct { + BlkioWeight int `json:"BlkioWeight"` + CPUShares int `json:"CpuShares"` + CPUPeriod int `json:"CpuPeriod"` + CPUQuota int `json:"CpuQuota"` + CpusetCpus string `json:"CpusetCpus"` + CpusetMems string `json:"CpusetMems"` + Memory int `json:"Memory"` + MemorySwap int `json:"MemorySwap"` + MemoryReservation int `json:"MemoryReservation"` + KernelMemory int `json:"KernelMemory"` +} + +// UpdateContainer updates the container at ID with the options +// +// See https://goo.gl/Y6fXUy for more details. +func (c *Client) UpdateContainer(id string, opts UpdateContainerOptions) error { + resp, err := c.do("POST", fmt.Sprintf("/containers/"+id+"/update"), doOptions{data: opts, forceJSON: true}) + if err != nil { + return err + } + defer resp.Body.Close() + return nil +} + // RenameContainerOptions specify parameters to the RenameContainer function. // // See https://goo.gl/laSOIy for more details. @@ -469,48 +505,71 @@ type Device struct { CgroupPermissions string `json:"CgroupPermissions,omitempty" yaml:"CgroupPermissions,omitempty"` } +// BlockWeight represents a relative device weight for an individual device inside +// of a container +// +// See https://goo.gl/FSdP0H for more details. +type BlockWeight struct { + Path string `json:"Path,omitempty"` + Weight string `json:"Weight,omitempty"` +} + +// BlockLimit represents a read/write limit in IOPS or Bandwidth for a device +// inside of a container +// +// See https://goo.gl/FSdP0H for more details. +type BlockLimit struct { + Path string `json:"Path,omitempty"` + Rate string `json:"Rate,omitempty"` +} + // HostConfig contains the container options related to starting a container on // a given host type HostConfig struct { - Binds []string `json:"Binds,omitempty" yaml:"Binds,omitempty"` - CapAdd []string `json:"CapAdd,omitempty" yaml:"CapAdd,omitempty"` - CapDrop []string `json:"CapDrop,omitempty" yaml:"CapDrop,omitempty"` - GroupAdd []string `json:"GroupAdd,omitempty" yaml:"GroupAdd,omitempty"` - ContainerIDFile string `json:"ContainerIDFile,omitempty" yaml:"ContainerIDFile,omitempty"` - LxcConf []KeyValuePair `json:"LxcConf,omitempty" yaml:"LxcConf,omitempty"` - Privileged bool `json:"Privileged,omitempty" yaml:"Privileged,omitempty"` - PortBindings map[Port][]PortBinding `json:"PortBindings,omitempty" yaml:"PortBindings,omitempty"` - Links []string `json:"Links,omitempty" yaml:"Links,omitempty"` - PublishAllPorts bool `json:"PublishAllPorts,omitempty" yaml:"PublishAllPorts,omitempty"` - DNS []string `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.10 and above only - DNSOptions []string `json:"DnsOptions,omitempty" yaml:"DnsOptions,omitempty"` - DNSSearch []string `json:"DnsSearch,omitempty" yaml:"DnsSearch,omitempty"` - ExtraHosts []string `json:"ExtraHosts,omitempty" yaml:"ExtraHosts,omitempty"` - VolumesFrom []string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"` - NetworkMode string `json:"NetworkMode,omitempty" yaml:"NetworkMode,omitempty"` - IpcMode string `json:"IpcMode,omitempty" yaml:"IpcMode,omitempty"` - PidMode string `json:"PidMode,omitempty" yaml:"PidMode,omitempty"` - UTSMode string `json:"UTSMode,omitempty" yaml:"UTSMode,omitempty"` - RestartPolicy RestartPolicy `json:"RestartPolicy,omitempty" yaml:"RestartPolicy,omitempty"` - Devices []Device `json:"Devices,omitempty" yaml:"Devices,omitempty"` - LogConfig LogConfig `json:"LogConfig,omitempty" yaml:"LogConfig,omitempty"` - ReadonlyRootfs bool `json:"ReadonlyRootfs,omitempty" yaml:"ReadonlyRootfs,omitempty"` - SecurityOpt []string `json:"SecurityOpt,omitempty" yaml:"SecurityOpt,omitempty"` - CgroupParent string `json:"CgroupParent,omitempty" yaml:"CgroupParent,omitempty"` - Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty"` - MemorySwap int64 `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty"` - MemorySwappiness int64 `json:"MemorySwappiness,omitempty" yaml:"MemorySwappiness,omitempty"` - OOMKillDisable bool `json:"OomKillDisable,omitempty" yaml:"OomKillDisable"` - CPUShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty"` - CPUSet string `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty"` - CPUSetCPUs string `json:"CpusetCpus,omitempty" yaml:"CpusetCpus,omitempty"` - CPUSetMEMs string `json:"CpusetMems,omitempty" yaml:"CpusetMems,omitempty"` - CPUQuota int64 `json:"CpuQuota,omitempty" yaml:"CpuQuota,omitempty"` - CPUPeriod int64 `json:"CpuPeriod,omitempty" yaml:"CpuPeriod,omitempty"` - BlkioWeight int64 `json:"BlkioWeight,omitempty" yaml:"BlkioWeight"` - Ulimits []ULimit `json:"Ulimits,omitempty" yaml:"Ulimits,omitempty"` - VolumeDriver string `json:"VolumeDriver,omitempty" yaml:"VolumeDriver,omitempty"` - OomScoreAdj int `json:"OomScoreAdj,omitempty" yaml:"OomScoreAdj,omitempty"` + Binds []string `json:"Binds,omitempty" yaml:"Binds,omitempty"` + CapAdd []string `json:"CapAdd,omitempty" yaml:"CapAdd,omitempty"` + CapDrop []string `json:"CapDrop,omitempty" yaml:"CapDrop,omitempty"` + GroupAdd []string `json:"GroupAdd,omitempty" yaml:"GroupAdd,omitempty"` + ContainerIDFile string `json:"ContainerIDFile,omitempty" yaml:"ContainerIDFile,omitempty"` + LxcConf []KeyValuePair `json:"LxcConf,omitempty" yaml:"LxcConf,omitempty"` + Privileged bool `json:"Privileged,omitempty" yaml:"Privileged,omitempty"` + PortBindings map[Port][]PortBinding `json:"PortBindings,omitempty" yaml:"PortBindings,omitempty"` + Links []string `json:"Links,omitempty" yaml:"Links,omitempty"` + PublishAllPorts bool `json:"PublishAllPorts,omitempty" yaml:"PublishAllPorts,omitempty"` + DNS []string `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.10 and above only + DNSOptions []string `json:"DnsOptions,omitempty" yaml:"DnsOptions,omitempty"` + DNSSearch []string `json:"DnsSearch,omitempty" yaml:"DnsSearch,omitempty"` + ExtraHosts []string `json:"ExtraHosts,omitempty" yaml:"ExtraHosts,omitempty"` + VolumesFrom []string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"` + NetworkMode string `json:"NetworkMode,omitempty" yaml:"NetworkMode,omitempty"` + IpcMode string `json:"IpcMode,omitempty" yaml:"IpcMode,omitempty"` + PidMode string `json:"PidMode,omitempty" yaml:"PidMode,omitempty"` + UTSMode string `json:"UTSMode,omitempty" yaml:"UTSMode,omitempty"` + RestartPolicy RestartPolicy `json:"RestartPolicy,omitempty" yaml:"RestartPolicy,omitempty"` + Devices []Device `json:"Devices,omitempty" yaml:"Devices,omitempty"` + LogConfig LogConfig `json:"LogConfig,omitempty" yaml:"LogConfig,omitempty"` + ReadonlyRootfs bool `json:"ReadonlyRootfs,omitempty" yaml:"ReadonlyRootfs,omitempty"` + SecurityOpt []string `json:"SecurityOpt,omitempty" yaml:"SecurityOpt,omitempty"` + CgroupParent string `json:"CgroupParent,omitempty" yaml:"CgroupParent,omitempty"` + Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty"` + MemorySwap int64 `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty"` + MemorySwappiness int64 `json:"MemorySwappiness,omitempty" yaml:"MemorySwappiness,omitempty"` + OOMKillDisable bool `json:"OomKillDisable,omitempty" yaml:"OomKillDisable"` + CPUShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty"` + CPUSet string `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty"` + CPUSetCPUs string `json:"CpusetCpus,omitempty" yaml:"CpusetCpus,omitempty"` + CPUSetMEMs string `json:"CpusetMems,omitempty" yaml:"CpusetMems,omitempty"` + CPUQuota int64 `json:"CpuQuota,omitempty" yaml:"CpuQuota,omitempty"` + CPUPeriod int64 `json:"CpuPeriod,omitempty" yaml:"CpuPeriod,omitempty"` + BlkioWeight int64 `json:"BlkioWeight,omitempty" yaml:"BlkioWeight"` + BlkioWeightDevice []BlockWeight `json:"BlkioWeightDevice,omitempty" yaml:"BlkioWeightDevice"` + BlkioDeviceReadBps []BlockLimit `json:"BlkioDeviceReadBps,omitempty" yaml:"BlkioDeviceReadBps"` + BlkioDeviceReadIOps []BlockLimit `json:"BlkioDeviceReadIOps,omitempty" yaml:"BlkioDeviceReadIOps"` + BlkioDeviceWriteBps []BlockLimit `json:"BlkioDeviceWriteBps,omitempty" yaml:"BlkioDeviceWriteBps"` + BlkioDeviceWriteIOps []BlockLimit `json:"BlkioDeviceWriteIOps,omitempty" yaml:"BlkioDeviceWriteIOps"` + Ulimits []ULimit `json:"Ulimits,omitempty" yaml:"Ulimits,omitempty"` + VolumeDriver string `json:"VolumeDriver,omitempty" yaml:"VolumeDriver,omitempty"` + OomScoreAdj int `json:"OomScoreAdj,omitempty" yaml:"OomScoreAdj,omitempty"` } // StartContainer starts a container, returning an error in case of failure. diff --git a/vendor/github.com/fsouza/go-dockerclient/event.go b/vendor/github.com/fsouza/go-dockerclient/event.go index eaffddb825fc..6634e9231a60 100644 --- a/vendor/github.com/fsouza/go-dockerclient/event.go +++ b/vendor/github.com/fsouza/go-dockerclient/event.go @@ -18,12 +18,38 @@ import ( "time" ) -// APIEvents represents an event returned by the API. +// APIEvents represents events coming from the Docker API +// The fields in the Docker API changed in API version 1.22, and +// events for more than images and containers are now fired off. +// To maintain forward and backward compatibility, go-dockerclient +// replicates the event in both the new and old format as faithfully as possible. +// +// For events that only exist in 1.22 in later, `Status` is filled in as +// `"Type:Action"` instead of just `Action` to allow for older clients to +// differentiate and not break if they rely on the pre-1.22 Status types. +// +// The transformEvent method can be consulted for more information about how +// events are translated from new/old API formats type APIEvents struct { - Status string `json:"Status,omitempty" yaml:"Status,omitempty"` - ID string `json:"ID,omitempty" yaml:"ID,omitempty"` - From string `json:"From,omitempty" yaml:"From,omitempty"` - Time int64 `json:"Time,omitempty" yaml:"Time,omitempty"` + // New API Fields in 1.22 + Action string `json:"action,omitempty"` + Type string `json:"type,omitempty"` + Actor APIActor `json:"actor,omitempty"` + + // Old API fields for < 1.22 + Status string `json:"status,omitempty"` + ID string `json:"id,omitempty"` + From string `json:"from,omitempty"` + + // Fields in both + Time int64 `json:"time,omitempty"` + TimeNano int64 `json:"timeNano,omitempty"` +} + +// APIActor represents an actor that accomplishes something for an event +type APIActor struct { + ID string `json:"id,omitempty"` + Attributes map[string]string `json:"attributes,omitempty"` } type eventMonitoringState struct { @@ -52,6 +78,7 @@ var ( // EOFEvent is sent when the event listener receives an EOF error. EOFEvent = &APIEvents{ + Type: "EOF", Status: "EOF", } ) @@ -297,8 +324,41 @@ func (c *Client) eventHijack(startTime int64, eventChan chan *APIEvents, errChan if !c.eventMonitor.isEnabled() { return } + transformEvent(&event) eventChan <- &event } }(res, conn) return nil } + +// transformEvent takes an event and determines what version it is from +// then populates both versions of the event +func transformEvent(event *APIEvents) { + // if <= 1.21, `status` and `ID` will be populated + if event.Status != "" && event.ID != "" { + event.Action = event.Status + event.Actor.ID = event.ID + event.Actor.Attributes = map[string]string{} + switch event.Status { + case "delete", "import", "pull", "push", "tag", "untag": + event.Type = "image" + default: + event.Type = "container" + if event.From != "" { + event.Actor.Attributes["image"] = event.From + } + } + } else { + if event.Type == "image" || event.Type == "container" { + event.Status = event.Action + } else { + // Because just the Status has been overloaded with different Types + // if an event is not for an image or a container, we prepend the type + // to avoid problems for people relying on actions being only for + // images and containers + event.Status = event.Type + ":" + event.Action + } + event.ID = event.Actor.ID + event.From = event.Actor.Attributes["image"] + } +} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/context/LICENSE b/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/context/LICENSE deleted file mode 100644 index 0e5fb872800d..000000000000 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/context/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2012 Rodrigo Moraes. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/context/README.md b/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/context/README.md deleted file mode 100644 index c60a31b053bc..000000000000 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/context/README.md +++ /dev/null @@ -1,7 +0,0 @@ -context -======= -[![Build Status](https://travis-ci.org/gorilla/context.png?branch=master)](https://travis-ci.org/gorilla/context) - -gorilla/context is a general purpose registry for global request variables. - -Read the full documentation here: http://www.gorillatoolkit.org/pkg/context diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/context/context.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/context/context.go deleted file mode 100644 index 81cb128b19ca..000000000000 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/context/context.go +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2012 The Gorilla Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package context - -import ( - "net/http" - "sync" - "time" -) - -var ( - mutex sync.RWMutex - data = make(map[*http.Request]map[interface{}]interface{}) - datat = make(map[*http.Request]int64) -) - -// Set stores a value for a given key in a given request. -func Set(r *http.Request, key, val interface{}) { - mutex.Lock() - if data[r] == nil { - data[r] = make(map[interface{}]interface{}) - datat[r] = time.Now().Unix() - } - data[r][key] = val - mutex.Unlock() -} - -// Get returns a value stored for a given key in a given request. -func Get(r *http.Request, key interface{}) interface{} { - mutex.RLock() - if ctx := data[r]; ctx != nil { - value := ctx[key] - mutex.RUnlock() - return value - } - mutex.RUnlock() - return nil -} - -// GetOk returns stored value and presence state like multi-value return of map access. -func GetOk(r *http.Request, key interface{}) (interface{}, bool) { - mutex.RLock() - if _, ok := data[r]; ok { - value, ok := data[r][key] - mutex.RUnlock() - return value, ok - } - mutex.RUnlock() - return nil, false -} - -// GetAll returns all stored values for the request as a map. Nil is returned for invalid requests. -func GetAll(r *http.Request) map[interface{}]interface{} { - mutex.RLock() - if context, ok := data[r]; ok { - result := make(map[interface{}]interface{}, len(context)) - for k, v := range context { - result[k] = v - } - mutex.RUnlock() - return result - } - mutex.RUnlock() - return nil -} - -// GetAllOk returns all stored values for the request as a map and a boolean value that indicates if -// the request was registered. -func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) { - mutex.RLock() - context, ok := data[r] - result := make(map[interface{}]interface{}, len(context)) - for k, v := range context { - result[k] = v - } - mutex.RUnlock() - return result, ok -} - -// Delete removes a value stored for a given key in a given request. -func Delete(r *http.Request, key interface{}) { - mutex.Lock() - if data[r] != nil { - delete(data[r], key) - } - mutex.Unlock() -} - -// Clear removes all values stored for a given request. -// -// This is usually called by a handler wrapper to clean up request -// variables at the end of a request lifetime. See ClearHandler(). -func Clear(r *http.Request) { - mutex.Lock() - clear(r) - mutex.Unlock() -} - -// clear is Clear without the lock. -func clear(r *http.Request) { - delete(data, r) - delete(datat, r) -} - -// Purge removes request data stored for longer than maxAge, in seconds. -// It returns the amount of requests removed. -// -// If maxAge <= 0, all request data is removed. -// -// This is only used for sanity check: in case context cleaning was not -// properly set some request data can be kept forever, consuming an increasing -// amount of memory. In case this is detected, Purge() must be called -// periodically until the problem is fixed. -func Purge(maxAge int) int { - mutex.Lock() - count := 0 - if maxAge <= 0 { - count = len(data) - data = make(map[*http.Request]map[interface{}]interface{}) - datat = make(map[*http.Request]int64) - } else { - min := time.Now().Unix() - int64(maxAge) - for r := range data { - if datat[r] < min { - clear(r) - count++ - } - } - } - mutex.Unlock() - return count -} - -// ClearHandler wraps an http.Handler and clears request values at the end -// of a request lifetime. -func ClearHandler(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer Clear(r) - h.ServeHTTP(w, r) - }) -} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/context/doc.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/context/doc.go deleted file mode 100644 index 73c7400311e1..000000000000 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/context/doc.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2012 The Gorilla Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -/* -Package context stores values shared during a request lifetime. - -For example, a router can set variables extracted from the URL and later -application handlers can access those values, or it can be used to store -sessions values to be saved at the end of a request. There are several -others common uses. - -The idea was posted by Brad Fitzpatrick to the go-nuts mailing list: - - http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53 - -Here's the basic usage: first define the keys that you will need. The key -type is interface{} so a key can be of any type that supports equality. -Here we define a key using a custom int type to avoid name collisions: - - package foo - - import ( - "github.com/gorilla/context" - ) - - type key int - - const MyKey key = 0 - -Then set a variable. Variables are bound to an http.Request object, so you -need a request instance to set a value: - - context.Set(r, MyKey, "bar") - -The application can later access the variable using the same key you provided: - - func MyHandler(w http.ResponseWriter, r *http.Request) { - // val is "bar". - val := context.Get(r, foo.MyKey) - - // returns ("bar", true) - val, ok := context.GetOk(r, foo.MyKey) - // ... - } - -And that's all about the basic usage. We discuss some other ideas below. - -Any type can be stored in the context. To enforce a given type, make the key -private and wrap Get() and Set() to accept and return values of a specific -type: - - type key int - - const mykey key = 0 - - // GetMyKey returns a value for this package from the request values. - func GetMyKey(r *http.Request) SomeType { - if rv := context.Get(r, mykey); rv != nil { - return rv.(SomeType) - } - return nil - } - - // SetMyKey sets a value for this package in the request values. - func SetMyKey(r *http.Request, val SomeType) { - context.Set(r, mykey, val) - } - -Variables must be cleared at the end of a request, to remove all values -that were stored. This can be done in an http.Handler, after a request was -served. Just call Clear() passing the request: - - context.Clear(r) - -...or use ClearHandler(), which conveniently wraps an http.Handler to clear -variables at the end of a request lifetime. - -The Routers from the packages gorilla/mux and gorilla/pat call Clear() -so if you are using either of them you don't need to clear the context manually. -*/ -package context diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/LICENSE b/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/LICENSE deleted file mode 100644 index 0e5fb872800d..000000000000 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2012 Rodrigo Moraes. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/README.md b/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/README.md deleted file mode 100644 index b987c9e5d10e..000000000000 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/README.md +++ /dev/null @@ -1,240 +0,0 @@ -mux -=== -[![GoDoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux) -[![Build Status](https://travis-ci.org/gorilla/mux.svg?branch=master)](https://travis-ci.org/gorilla/mux) - -Package `gorilla/mux` implements a request router and dispatcher. - -The name mux stands for "HTTP request multiplexer". Like the standard `http.ServeMux`, `mux.Router` matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are: - -* Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers. -* URL hosts and paths can have variables with an optional regular expression. -* Registered URLs can be built, or "reversed", which helps maintaining references to resources. -* Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching. -* It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`. - -Let's start registering a couple of URL paths and handlers: - -```go -func main() { - r := mux.NewRouter() - r.HandleFunc("/", HomeHandler) - r.HandleFunc("/products", ProductsHandler) - r.HandleFunc("/articles", ArticlesHandler) - http.Handle("/", r) -} -``` - -Here we register three routes mapping URL paths to handlers. This is equivalent to how `http.HandleFunc()` works: if an incoming request URL matches one of the paths, the corresponding handler is called passing (`http.ResponseWriter`, `*http.Request`) as parameters. - -Paths can have variables. They are defined using the format `{name}` or `{name:pattern}`. If a regular expression pattern is not defined, the matched variable will be anything until the next slash. For example: - -```go -r := mux.NewRouter() -r.HandleFunc("/products/{key}", ProductHandler) -r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) -r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) -``` - -The names are used to create a map of route variables which can be retrieved calling `mux.Vars()`: - -```go -vars := mux.Vars(request) -category := vars["category"] -``` - -And this is all you need to know about the basic usage. More advanced options are explained below. - -Routes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables: - -```go -r := mux.NewRouter() -// Only matches if domain is "www.example.com". -r.Host("www.example.com") -// Matches a dynamic subdomain. -r.Host("{subdomain:[a-z]+}.domain.com") -``` - -There are several other matchers that can be added. To match path prefixes: - -```go -r.PathPrefix("/products/") -``` - -...or HTTP methods: - -```go -r.Methods("GET", "POST") -``` - -...or URL schemes: - -```go -r.Schemes("https") -``` - -...or header values: - -```go -r.Headers("X-Requested-With", "XMLHttpRequest") -``` - -...or query values: - -```go -r.Queries("key", "value") -``` - -...or to use a custom matcher function: - -```go -r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool { - return r.ProtoMajor == 0 -}) -``` - -...and finally, it is possible to combine several matchers in a single route: - -```go -r.HandleFunc("/products", ProductsHandler). - Host("www.example.com"). - Methods("GET"). - Schemes("http") -``` - -Setting the same matching conditions again and again can be boring, so we have a way to group several routes that share the same requirements. We call it "subrouting". - -For example, let's say we have several URLs that should only match when the host is `www.example.com`. Create a route for that host and get a "subrouter" from it: - -```go -r := mux.NewRouter() -s := r.Host("www.example.com").Subrouter() -``` - -Then register routes in the subrouter: - -```go -s.HandleFunc("/products/", ProductsHandler) -s.HandleFunc("/products/{key}", ProductHandler) -s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) -``` - -The three URL paths we registered above will only be tested if the domain is `www.example.com`, because the subrouter is tested first. This is not only convenient, but also optimizes request matching. You can create subrouters combining any attribute matchers accepted by a route. - -Subrouters can be used to create domain or path "namespaces": you define subrouters in a central place and then parts of the app can register its paths relatively to a given subrouter. - -There's one more thing about subroutes. When a subrouter has a path prefix, the inner routes use it as base for their paths: - -```go -r := mux.NewRouter() -s := r.PathPrefix("/products").Subrouter() -// "/products/" -s.HandleFunc("/", ProductsHandler) -// "/products/{key}/" -s.HandleFunc("/{key}/", ProductHandler) -// "/products/{key}/details" -s.HandleFunc("/{key}/details", ProductDetailsHandler) -``` - -Now let's see how to build registered URLs. - -Routes can be named. All routes that define a name can have their URLs built, or "reversed". We define a name calling `Name()` on a route. For example: - -```go -r := mux.NewRouter() -r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). - Name("article") -``` - -To build a URL, get the route and call the `URL()` method, passing a sequence of key/value pairs for the route variables. For the previous route, we would do: - -```go -url, err := r.Get("article").URL("category", "technology", "id", "42") -``` - -...and the result will be a `url.URL` with the following path: - -``` -"/articles/technology/42" -``` - -This also works for host variables: - -```go -r := mux.NewRouter() -r.Host("{subdomain}.domain.com"). - Path("/articles/{category}/{id:[0-9]+}"). - HandlerFunc(ArticleHandler). - Name("article") - -// url.String() will be "http://news.domain.com/articles/technology/42" -url, err := r.Get("article").URL("subdomain", "news", - "category", "technology", - "id", "42") -``` - -All variables defined in the route are required, and their values must conform to the corresponding patterns. These requirements guarantee that a generated URL will always match a registered route -- the only exception is for explicitly defined "build-only" routes which never match. - -Regex support also exists for matching Headers within a route. For example, we could do: - -```go -r.HeadersRegexp("Content-Type", "application/(text|json)") -``` - -...and the route will match both requests with a Content-Type of `application/json` as well as `application/text` - -There's also a way to build only the URL host or path for a route: use the methods `URLHost()` or `URLPath()` instead. For the previous route, we would do: - -```go -// "http://news.domain.com/" -host, err := r.Get("article").URLHost("subdomain", "news") - -// "/articles/technology/42" -path, err := r.Get("article").URLPath("category", "technology", "id", "42") -``` - -And if you use subrouters, host and path defined separately can be built as well: - -```go -r := mux.NewRouter() -s := r.Host("{subdomain}.domain.com").Subrouter() -s.Path("/articles/{category}/{id:[0-9]+}"). - HandlerFunc(ArticleHandler). - Name("article") - -// "http://news.domain.com/articles/technology/42" -url, err := r.Get("article").URL("subdomain", "news", - "category", "technology", - "id", "42") -``` - -## Full Example - -Here's a complete, runnable example of a small `mux` based server: - -```go -package main - -import ( - "net/http" - - "github.com/gorilla/mux" -) - -func YourHandler(w http.ResponseWriter, r *http.Request) { - w.Write([]byte("Gorilla!\n")) -} - -func main() { - r := mux.NewRouter() - // Routes consist of a path and a handler function. - r.HandleFunc("/", YourHandler) - - // Bind to a port and pass our router in - http.ListenAndServe(":8000", r) -} -``` - -## License - -BSD licensed. See the LICENSE file for details. diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/doc.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/doc.go deleted file mode 100644 index 49798cb5cf52..000000000000 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/doc.go +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright 2012 The Gorilla Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -/* -Package gorilla/mux implements a request router and dispatcher. - -The name mux stands for "HTTP request multiplexer". Like the standard -http.ServeMux, mux.Router matches incoming requests against a list of -registered routes and calls a handler for the route that matches the URL -or other conditions. The main features are: - - * Requests can be matched based on URL host, path, path prefix, schemes, - header and query values, HTTP methods or using custom matchers. - * URL hosts and paths can have variables with an optional regular - expression. - * Registered URLs can be built, or "reversed", which helps maintaining - references to resources. - * Routes can be used as subrouters: nested routes are only tested if the - parent route matches. This is useful to define groups of routes that - share common conditions like a host, a path prefix or other repeated - attributes. As a bonus, this optimizes request matching. - * It implements the http.Handler interface so it is compatible with the - standard http.ServeMux. - -Let's start registering a couple of URL paths and handlers: - - func main() { - r := mux.NewRouter() - r.HandleFunc("/", HomeHandler) - r.HandleFunc("/products", ProductsHandler) - r.HandleFunc("/articles", ArticlesHandler) - http.Handle("/", r) - } - -Here we register three routes mapping URL paths to handlers. This is -equivalent to how http.HandleFunc() works: if an incoming request URL matches -one of the paths, the corresponding handler is called passing -(http.ResponseWriter, *http.Request) as parameters. - -Paths can have variables. They are defined using the format {name} or -{name:pattern}. If a regular expression pattern is not defined, the matched -variable will be anything until the next slash. For example: - - r := mux.NewRouter() - r.HandleFunc("/products/{key}", ProductHandler) - r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) - r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) - -The names are used to create a map of route variables which can be retrieved -calling mux.Vars(): - - vars := mux.Vars(request) - category := vars["category"] - -And this is all you need to know about the basic usage. More advanced options -are explained below. - -Routes can also be restricted to a domain or subdomain. Just define a host -pattern to be matched. They can also have variables: - - r := mux.NewRouter() - // Only matches if domain is "www.example.com". - r.Host("www.example.com") - // Matches a dynamic subdomain. - r.Host("{subdomain:[a-z]+}.domain.com") - -There are several other matchers that can be added. To match path prefixes: - - r.PathPrefix("/products/") - -...or HTTP methods: - - r.Methods("GET", "POST") - -...or URL schemes: - - r.Schemes("https") - -...or header values: - - r.Headers("X-Requested-With", "XMLHttpRequest") - -...or query values: - - r.Queries("key", "value") - -...or to use a custom matcher function: - - r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool { - return r.ProtoMajor == 0 - }) - -...and finally, it is possible to combine several matchers in a single route: - - r.HandleFunc("/products", ProductsHandler). - Host("www.example.com"). - Methods("GET"). - Schemes("http") - -Setting the same matching conditions again and again can be boring, so we have -a way to group several routes that share the same requirements. -We call it "subrouting". - -For example, let's say we have several URLs that should only match when the -host is "www.example.com". Create a route for that host and get a "subrouter" -from it: - - r := mux.NewRouter() - s := r.Host("www.example.com").Subrouter() - -Then register routes in the subrouter: - - s.HandleFunc("/products/", ProductsHandler) - s.HandleFunc("/products/{key}", ProductHandler) - s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) - -The three URL paths we registered above will only be tested if the domain is -"www.example.com", because the subrouter is tested first. This is not -only convenient, but also optimizes request matching. You can create -subrouters combining any attribute matchers accepted by a route. - -Subrouters can be used to create domain or path "namespaces": you define -subrouters in a central place and then parts of the app can register its -paths relatively to a given subrouter. - -There's one more thing about subroutes. When a subrouter has a path prefix, -the inner routes use it as base for their paths: - - r := mux.NewRouter() - s := r.PathPrefix("/products").Subrouter() - // "/products/" - s.HandleFunc("/", ProductsHandler) - // "/products/{key}/" - s.HandleFunc("/{key}/", ProductHandler) - // "/products/{key}/details" - s.HandleFunc("/{key}/details", ProductDetailsHandler) - -Now let's see how to build registered URLs. - -Routes can be named. All routes that define a name can have their URLs built, -or "reversed". We define a name calling Name() on a route. For example: - - r := mux.NewRouter() - r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). - Name("article") - -To build a URL, get the route and call the URL() method, passing a sequence of -key/value pairs for the route variables. For the previous route, we would do: - - url, err := r.Get("article").URL("category", "technology", "id", "42") - -...and the result will be a url.URL with the following path: - - "/articles/technology/42" - -This also works for host variables: - - r := mux.NewRouter() - r.Host("{subdomain}.domain.com"). - Path("/articles/{category}/{id:[0-9]+}"). - HandlerFunc(ArticleHandler). - Name("article") - - // url.String() will be "http://news.domain.com/articles/technology/42" - url, err := r.Get("article").URL("subdomain", "news", - "category", "technology", - "id", "42") - -All variables defined in the route are required, and their values must -conform to the corresponding patterns. These requirements guarantee that a -generated URL will always match a registered route -- the only exception is -for explicitly defined "build-only" routes which never match. - -Regex support also exists for matching Headers within a route. For example, we could do: - - r.HeadersRegexp("Content-Type", "application/(text|json)") - -...and the route will match both requests with a Content-Type of `application/json` as well as -`application/text` - -There's also a way to build only the URL host or path for a route: -use the methods URLHost() or URLPath() instead. For the previous route, -we would do: - - // "http://news.domain.com/" - host, err := r.Get("article").URLHost("subdomain", "news") - - // "/articles/technology/42" - path, err := r.Get("article").URLPath("category", "technology", "id", "42") - -And if you use subrouters, host and path defined separately can be built -as well: - - r := mux.NewRouter() - s := r.Host("{subdomain}.domain.com").Subrouter() - s.Path("/articles/{category}/{id:[0-9]+}"). - HandlerFunc(ArticleHandler). - Name("article") - - // "http://news.domain.com/articles/technology/42" - url, err := r.Get("article").URL("subdomain", "news", - "category", "technology", - "id", "42") -*/ -package mux diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/mux.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/mux.go deleted file mode 100644 index cb03ddfe533d..000000000000 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/mux.go +++ /dev/null @@ -1,481 +0,0 @@ -// Copyright 2012 The Gorilla Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package mux - -import ( - "errors" - "fmt" - "net/http" - "path" - "regexp" - - "github.com/fsouza/go-dockerclient/external/github.com/gorilla/context" -) - -// NewRouter returns a new router instance. -func NewRouter() *Router { - return &Router{namedRoutes: make(map[string]*Route), KeepContext: false} -} - -// Router registers routes to be matched and dispatches a handler. -// -// It implements the http.Handler interface, so it can be registered to serve -// requests: -// -// var router = mux.NewRouter() -// -// func main() { -// http.Handle("/", router) -// } -// -// Or, for Google App Engine, register it in a init() function: -// -// func init() { -// http.Handle("/", router) -// } -// -// This will send all incoming requests to the router. -type Router struct { - // Configurable Handler to be used when no route matches. - NotFoundHandler http.Handler - // Parent route, if this is a subrouter. - parent parentRoute - // Routes to be matched, in order. - routes []*Route - // Routes by name for URL building. - namedRoutes map[string]*Route - // See Router.StrictSlash(). This defines the flag for new routes. - strictSlash bool - // If true, do not clear the request context after handling the request - KeepContext bool -} - -// Match matches registered routes against the request. -func (r *Router) Match(req *http.Request, match *RouteMatch) bool { - for _, route := range r.routes { - if route.Match(req, match) { - return true - } - } - - // Closest match for a router (includes sub-routers) - if r.NotFoundHandler != nil { - match.Handler = r.NotFoundHandler - return true - } - return false -} - -// ServeHTTP dispatches the handler registered in the matched route. -// -// When there is a match, the route variables can be retrieved calling -// mux.Vars(request). -func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { - // Clean path to canonical form and redirect. - if p := cleanPath(req.URL.Path); p != req.URL.Path { - - // Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query. - // This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue: - // http://code.google.com/p/go/issues/detail?id=5252 - url := *req.URL - url.Path = p - p = url.String() - - w.Header().Set("Location", p) - w.WriteHeader(http.StatusMovedPermanently) - return - } - var match RouteMatch - var handler http.Handler - if r.Match(req, &match) { - handler = match.Handler - setVars(req, match.Vars) - setCurrentRoute(req, match.Route) - } - if handler == nil { - handler = http.NotFoundHandler() - } - if !r.KeepContext { - defer context.Clear(req) - } - handler.ServeHTTP(w, req) -} - -// Get returns a route registered with the given name. -func (r *Router) Get(name string) *Route { - return r.getNamedRoutes()[name] -} - -// GetRoute returns a route registered with the given name. This method -// was renamed to Get() and remains here for backwards compatibility. -func (r *Router) GetRoute(name string) *Route { - return r.getNamedRoutes()[name] -} - -// StrictSlash defines the trailing slash behavior for new routes. The initial -// value is false. -// -// When true, if the route path is "/path/", accessing "/path" will redirect -// to the former and vice versa. In other words, your application will always -// see the path as specified in the route. -// -// When false, if the route path is "/path", accessing "/path/" will not match -// this route and vice versa. -// -// Special case: when a route sets a path prefix using the PathPrefix() method, -// strict slash is ignored for that route because the redirect behavior can't -// be determined from a prefix alone. However, any subrouters created from that -// route inherit the original StrictSlash setting. -func (r *Router) StrictSlash(value bool) *Router { - r.strictSlash = value - return r -} - -// ---------------------------------------------------------------------------- -// parentRoute -// ---------------------------------------------------------------------------- - -// getNamedRoutes returns the map where named routes are registered. -func (r *Router) getNamedRoutes() map[string]*Route { - if r.namedRoutes == nil { - if r.parent != nil { - r.namedRoutes = r.parent.getNamedRoutes() - } else { - r.namedRoutes = make(map[string]*Route) - } - } - return r.namedRoutes -} - -// getRegexpGroup returns regexp definitions from the parent route, if any. -func (r *Router) getRegexpGroup() *routeRegexpGroup { - if r.parent != nil { - return r.parent.getRegexpGroup() - } - return nil -} - -func (r *Router) buildVars(m map[string]string) map[string]string { - if r.parent != nil { - m = r.parent.buildVars(m) - } - return m -} - -// ---------------------------------------------------------------------------- -// Route factories -// ---------------------------------------------------------------------------- - -// NewRoute registers an empty route. -func (r *Router) NewRoute() *Route { - route := &Route{parent: r, strictSlash: r.strictSlash} - r.routes = append(r.routes, route) - return route -} - -// Handle registers a new route with a matcher for the URL path. -// See Route.Path() and Route.Handler(). -func (r *Router) Handle(path string, handler http.Handler) *Route { - return r.NewRoute().Path(path).Handler(handler) -} - -// HandleFunc registers a new route with a matcher for the URL path. -// See Route.Path() and Route.HandlerFunc(). -func (r *Router) HandleFunc(path string, f func(http.ResponseWriter, - *http.Request)) *Route { - return r.NewRoute().Path(path).HandlerFunc(f) -} - -// Headers registers a new route with a matcher for request header values. -// See Route.Headers(). -func (r *Router) Headers(pairs ...string) *Route { - return r.NewRoute().Headers(pairs...) -} - -// Host registers a new route with a matcher for the URL host. -// See Route.Host(). -func (r *Router) Host(tpl string) *Route { - return r.NewRoute().Host(tpl) -} - -// MatcherFunc registers a new route with a custom matcher function. -// See Route.MatcherFunc(). -func (r *Router) MatcherFunc(f MatcherFunc) *Route { - return r.NewRoute().MatcherFunc(f) -} - -// Methods registers a new route with a matcher for HTTP methods. -// See Route.Methods(). -func (r *Router) Methods(methods ...string) *Route { - return r.NewRoute().Methods(methods...) -} - -// Path registers a new route with a matcher for the URL path. -// See Route.Path(). -func (r *Router) Path(tpl string) *Route { - return r.NewRoute().Path(tpl) -} - -// PathPrefix registers a new route with a matcher for the URL path prefix. -// See Route.PathPrefix(). -func (r *Router) PathPrefix(tpl string) *Route { - return r.NewRoute().PathPrefix(tpl) -} - -// Queries registers a new route with a matcher for URL query values. -// See Route.Queries(). -func (r *Router) Queries(pairs ...string) *Route { - return r.NewRoute().Queries(pairs...) -} - -// Schemes registers a new route with a matcher for URL schemes. -// See Route.Schemes(). -func (r *Router) Schemes(schemes ...string) *Route { - return r.NewRoute().Schemes(schemes...) -} - -// BuildVars registers a new route with a custom function for modifying -// route variables before building a URL. -func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route { - return r.NewRoute().BuildVarsFunc(f) -} - -// Walk walks the router and all its sub-routers, calling walkFn for each route -// in the tree. The routes are walked in the order they were added. Sub-routers -// are explored depth-first. -func (r *Router) Walk(walkFn WalkFunc) error { - return r.walk(walkFn, []*Route{}) -} - -// SkipRouter is used as a return value from WalkFuncs to indicate that the -// router that walk is about to descend down to should be skipped. -var SkipRouter = errors.New("skip this router") - -// WalkFunc is the type of the function called for each route visited by Walk. -// At every invocation, it is given the current route, and the current router, -// and a list of ancestor routes that lead to the current route. -type WalkFunc func(route *Route, router *Router, ancestors []*Route) error - -func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error { - for _, t := range r.routes { - if t.regexp == nil || t.regexp.path == nil || t.regexp.path.template == "" { - continue - } - - err := walkFn(t, r, ancestors) - if err == SkipRouter { - continue - } - for _, sr := range t.matchers { - if h, ok := sr.(*Router); ok { - err := h.walk(walkFn, ancestors) - if err != nil { - return err - } - } - } - if h, ok := t.handler.(*Router); ok { - ancestors = append(ancestors, t) - err := h.walk(walkFn, ancestors) - if err != nil { - return err - } - ancestors = ancestors[:len(ancestors)-1] - } - } - return nil -} - -// ---------------------------------------------------------------------------- -// Context -// ---------------------------------------------------------------------------- - -// RouteMatch stores information about a matched route. -type RouteMatch struct { - Route *Route - Handler http.Handler - Vars map[string]string -} - -type contextKey int - -const ( - varsKey contextKey = iota - routeKey -) - -// Vars returns the route variables for the current request, if any. -func Vars(r *http.Request) map[string]string { - if rv := context.Get(r, varsKey); rv != nil { - return rv.(map[string]string) - } - return nil -} - -// CurrentRoute returns the matched route for the current request, if any. -// This only works when called inside the handler of the matched route -// because the matched route is stored in the request context which is cleared -// after the handler returns, unless the KeepContext option is set on the -// Router. -func CurrentRoute(r *http.Request) *Route { - if rv := context.Get(r, routeKey); rv != nil { - return rv.(*Route) - } - return nil -} - -func setVars(r *http.Request, val interface{}) { - if val != nil { - context.Set(r, varsKey, val) - } -} - -func setCurrentRoute(r *http.Request, val interface{}) { - if val != nil { - context.Set(r, routeKey, val) - } -} - -// ---------------------------------------------------------------------------- -// Helpers -// ---------------------------------------------------------------------------- - -// cleanPath returns the canonical path for p, eliminating . and .. elements. -// Borrowed from the net/http package. -func cleanPath(p string) string { - if p == "" { - return "/" - } - if p[0] != '/' { - p = "/" + p - } - np := path.Clean(p) - // path.Clean removes trailing slash except for root; - // put the trailing slash back if necessary. - if p[len(p)-1] == '/' && np != "/" { - np += "/" - } - return np -} - -// uniqueVars returns an error if two slices contain duplicated strings. -func uniqueVars(s1, s2 []string) error { - for _, v1 := range s1 { - for _, v2 := range s2 { - if v1 == v2 { - return fmt.Errorf("mux: duplicated route variable %q", v2) - } - } - } - return nil -} - -// checkPairs returns the count of strings passed in, and an error if -// the count is not an even number. -func checkPairs(pairs ...string) (int, error) { - length := len(pairs) - if length%2 != 0 { - return length, fmt.Errorf( - "mux: number of parameters must be multiple of 2, got %v", pairs) - } - return length, nil -} - -// mapFromPairsToString converts variadic string parameters to a -// string to string map. -func mapFromPairsToString(pairs ...string) (map[string]string, error) { - length, err := checkPairs(pairs...) - if err != nil { - return nil, err - } - m := make(map[string]string, length/2) - for i := 0; i < length; i += 2 { - m[pairs[i]] = pairs[i+1] - } - return m, nil -} - -// mapFromPairsToRegex converts variadic string paramers to a -// string to regex map. -func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) { - length, err := checkPairs(pairs...) - if err != nil { - return nil, err - } - m := make(map[string]*regexp.Regexp, length/2) - for i := 0; i < length; i += 2 { - regex, err := regexp.Compile(pairs[i+1]) - if err != nil { - return nil, err - } - m[pairs[i]] = regex - } - return m, nil -} - -// matchInArray returns true if the given string value is in the array. -func matchInArray(arr []string, value string) bool { - for _, v := range arr { - if v == value { - return true - } - } - return false -} - -// matchMapWithString returns true if the given key/value pairs exist in a given map. -func matchMapWithString(toCheck map[string]string, toMatch map[string][]string, canonicalKey bool) bool { - for k, v := range toCheck { - // Check if key exists. - if canonicalKey { - k = http.CanonicalHeaderKey(k) - } - if values := toMatch[k]; values == nil { - return false - } else if v != "" { - // If value was defined as an empty string we only check that the - // key exists. Otherwise we also check for equality. - valueExists := false - for _, value := range values { - if v == value { - valueExists = true - break - } - } - if !valueExists { - return false - } - } - } - return true -} - -// matchMapWithRegex returns true if the given key/value pairs exist in a given map compiled against -// the given regex -func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]string, canonicalKey bool) bool { - for k, v := range toCheck { - // Check if key exists. - if canonicalKey { - k = http.CanonicalHeaderKey(k) - } - if values := toMatch[k]; values == nil { - return false - } else if v != nil { - // If value was defined as an empty string we only check that the - // key exists. Otherwise we also check for equality. - valueExists := false - for _, value := range values { - if v.MatchString(value) { - valueExists = true - break - } - } - if !valueExists { - return false - } - } - } - return true -} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/regexp.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/regexp.go deleted file mode 100644 index 06728dd545e0..000000000000 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/regexp.go +++ /dev/null @@ -1,317 +0,0 @@ -// Copyright 2012 The Gorilla Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package mux - -import ( - "bytes" - "fmt" - "net/http" - "net/url" - "regexp" - "strconv" - "strings" -) - -// newRouteRegexp parses a route template and returns a routeRegexp, -// used to match a host, a path or a query string. -// -// It will extract named variables, assemble a regexp to be matched, create -// a "reverse" template to build URLs and compile regexps to validate variable -// values used in URL building. -// -// Previously we accepted only Python-like identifiers for variable -// names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that -// name and pattern can't be empty, and names can't contain a colon. -func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash bool) (*routeRegexp, error) { - // Check if it is well-formed. - idxs, errBraces := braceIndices(tpl) - if errBraces != nil { - return nil, errBraces - } - // Backup the original. - template := tpl - // Now let's parse it. - defaultPattern := "[^/]+" - if matchQuery { - defaultPattern = "[^?&]*" - } else if matchHost { - defaultPattern = "[^.]+" - matchPrefix = false - } - // Only match strict slash if not matching - if matchPrefix || matchHost || matchQuery { - strictSlash = false - } - // Set a flag for strictSlash. - endSlash := false - if strictSlash && strings.HasSuffix(tpl, "/") { - tpl = tpl[:len(tpl)-1] - endSlash = true - } - varsN := make([]string, len(idxs)/2) - varsR := make([]*regexp.Regexp, len(idxs)/2) - pattern := bytes.NewBufferString("") - pattern.WriteByte('^') - reverse := bytes.NewBufferString("") - var end int - var err error - for i := 0; i < len(idxs); i += 2 { - // Set all values we are interested in. - raw := tpl[end:idxs[i]] - end = idxs[i+1] - parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2) - name := parts[0] - patt := defaultPattern - if len(parts) == 2 { - patt = parts[1] - } - // Name or pattern can't be empty. - if name == "" || patt == "" { - return nil, fmt.Errorf("mux: missing name or pattern in %q", - tpl[idxs[i]:end]) - } - // Build the regexp pattern. - varIdx := i / 2 - fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(varIdx), patt) - // Build the reverse template. - fmt.Fprintf(reverse, "%s%%s", raw) - - // Append variable name and compiled pattern. - varsN[varIdx] = name - varsR[varIdx], err = regexp.Compile(fmt.Sprintf("^%s$", patt)) - if err != nil { - return nil, err - } - } - // Add the remaining. - raw := tpl[end:] - pattern.WriteString(regexp.QuoteMeta(raw)) - if strictSlash { - pattern.WriteString("[/]?") - } - if matchQuery { - // Add the default pattern if the query value is empty - if queryVal := strings.SplitN(template, "=", 2)[1]; queryVal == "" { - pattern.WriteString(defaultPattern) - } - } - if !matchPrefix { - pattern.WriteByte('$') - } - reverse.WriteString(raw) - if endSlash { - reverse.WriteByte('/') - } - // Compile full regexp. - reg, errCompile := regexp.Compile(pattern.String()) - if errCompile != nil { - return nil, errCompile - } - // Done! - return &routeRegexp{ - template: template, - matchHost: matchHost, - matchQuery: matchQuery, - strictSlash: strictSlash, - regexp: reg, - reverse: reverse.String(), - varsN: varsN, - varsR: varsR, - }, nil -} - -// routeRegexp stores a regexp to match a host or path and information to -// collect and validate route variables. -type routeRegexp struct { - // The unmodified template. - template string - // True for host match, false for path or query string match. - matchHost bool - // True for query string match, false for path and host match. - matchQuery bool - // The strictSlash value defined on the route, but disabled if PathPrefix was used. - strictSlash bool - // Expanded regexp. - regexp *regexp.Regexp - // Reverse template. - reverse string - // Variable names. - varsN []string - // Variable regexps (validators). - varsR []*regexp.Regexp -} - -// Match matches the regexp against the URL host or path. -func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool { - if !r.matchHost { - if r.matchQuery { - return r.matchQueryString(req) - } else { - return r.regexp.MatchString(req.URL.Path) - } - } - return r.regexp.MatchString(getHost(req)) -} - -// url builds a URL part using the given values. -func (r *routeRegexp) url(values map[string]string) (string, error) { - urlValues := make([]interface{}, len(r.varsN)) - for k, v := range r.varsN { - value, ok := values[v] - if !ok { - return "", fmt.Errorf("mux: missing route variable %q", v) - } - urlValues[k] = value - } - rv := fmt.Sprintf(r.reverse, urlValues...) - if !r.regexp.MatchString(rv) { - // The URL is checked against the full regexp, instead of checking - // individual variables. This is faster but to provide a good error - // message, we check individual regexps if the URL doesn't match. - for k, v := range r.varsN { - if !r.varsR[k].MatchString(values[v]) { - return "", fmt.Errorf( - "mux: variable %q doesn't match, expected %q", values[v], - r.varsR[k].String()) - } - } - } - return rv, nil -} - -// getUrlQuery returns a single query parameter from a request URL. -// For a URL with foo=bar&baz=ding, we return only the relevant key -// value pair for the routeRegexp. -func (r *routeRegexp) getUrlQuery(req *http.Request) string { - if !r.matchQuery { - return "" - } - templateKey := strings.SplitN(r.template, "=", 2)[0] - for key, vals := range req.URL.Query() { - if key == templateKey && len(vals) > 0 { - return key + "=" + vals[0] - } - } - return "" -} - -func (r *routeRegexp) matchQueryString(req *http.Request) bool { - return r.regexp.MatchString(r.getUrlQuery(req)) -} - -// braceIndices returns the first level curly brace indices from a string. -// It returns an error in case of unbalanced braces. -func braceIndices(s string) ([]int, error) { - var level, idx int - idxs := make([]int, 0) - for i := 0; i < len(s); i++ { - switch s[i] { - case '{': - if level++; level == 1 { - idx = i - } - case '}': - if level--; level == 0 { - idxs = append(idxs, idx, i+1) - } else if level < 0 { - return nil, fmt.Errorf("mux: unbalanced braces in %q", s) - } - } - } - if level != 0 { - return nil, fmt.Errorf("mux: unbalanced braces in %q", s) - } - return idxs, nil -} - -// varGroupName builds a capturing group name for the indexed variable. -func varGroupName(idx int) string { - return "v" + strconv.Itoa(idx) -} - -// ---------------------------------------------------------------------------- -// routeRegexpGroup -// ---------------------------------------------------------------------------- - -// routeRegexpGroup groups the route matchers that carry variables. -type routeRegexpGroup struct { - host *routeRegexp - path *routeRegexp - queries []*routeRegexp -} - -// setMatch extracts the variables from the URL once a route matches. -func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) { - // Store host variables. - if v.host != nil { - hostVars := v.host.regexp.FindStringSubmatch(getHost(req)) - if hostVars != nil { - subexpNames := v.host.regexp.SubexpNames() - varName := 0 - for i, name := range subexpNames[1:] { - if name != "" && name == varGroupName(varName) { - m.Vars[v.host.varsN[varName]] = hostVars[i+1] - varName++ - } - } - } - } - // Store path variables. - if v.path != nil { - pathVars := v.path.regexp.FindStringSubmatch(req.URL.Path) - if pathVars != nil { - subexpNames := v.path.regexp.SubexpNames() - varName := 0 - for i, name := range subexpNames[1:] { - if name != "" && name == varGroupName(varName) { - m.Vars[v.path.varsN[varName]] = pathVars[i+1] - varName++ - } - } - // Check if we should redirect. - if v.path.strictSlash { - p1 := strings.HasSuffix(req.URL.Path, "/") - p2 := strings.HasSuffix(v.path.template, "/") - if p1 != p2 { - u, _ := url.Parse(req.URL.String()) - if p1 { - u.Path = u.Path[:len(u.Path)-1] - } else { - u.Path += "/" - } - m.Handler = http.RedirectHandler(u.String(), 301) - } - } - } - } - // Store query string variables. - for _, q := range v.queries { - queryVars := q.regexp.FindStringSubmatch(q.getUrlQuery(req)) - if queryVars != nil { - subexpNames := q.regexp.SubexpNames() - varName := 0 - for i, name := range subexpNames[1:] { - if name != "" && name == varGroupName(varName) { - m.Vars[q.varsN[varName]] = queryVars[i+1] - varName++ - } - } - } - } -} - -// getHost tries its best to return the request host. -func getHost(r *http.Request) string { - if r.URL.IsAbs() { - return r.URL.Host - } - host := r.Host - // Slice off any port information. - if i := strings.Index(host, ":"); i != -1 { - host = host[:i] - } - return host - -} diff --git a/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/route.go b/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/route.go deleted file mode 100644 index 913432c1c0d1..000000000000 --- a/vendor/github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux/route.go +++ /dev/null @@ -1,595 +0,0 @@ -// Copyright 2012 The Gorilla Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package mux - -import ( - "errors" - "fmt" - "net/http" - "net/url" - "regexp" - "strings" -) - -// Route stores information to match a request and build URLs. -type Route struct { - // Parent where the route was registered (a Router). - parent parentRoute - // Request handler for the route. - handler http.Handler - // List of matchers. - matchers []matcher - // Manager for the variables from host and path. - regexp *routeRegexpGroup - // If true, when the path pattern is "/path/", accessing "/path" will - // redirect to the former and vice versa. - strictSlash bool - // If true, this route never matches: it is only used to build URLs. - buildOnly bool - // The name used to build URLs. - name string - // Error resulted from building a route. - err error - - buildVarsFunc BuildVarsFunc -} - -// Match matches the route against the request. -func (r *Route) Match(req *http.Request, match *RouteMatch) bool { - if r.buildOnly || r.err != nil { - return false - } - // Match everything. - for _, m := range r.matchers { - if matched := m.Match(req, match); !matched { - return false - } - } - // Yay, we have a match. Let's collect some info about it. - if match.Route == nil { - match.Route = r - } - if match.Handler == nil { - match.Handler = r.handler - } - if match.Vars == nil { - match.Vars = make(map[string]string) - } - // Set variables. - if r.regexp != nil { - r.regexp.setMatch(req, match, r) - } - return true -} - -// ---------------------------------------------------------------------------- -// Route attributes -// ---------------------------------------------------------------------------- - -// GetError returns an error resulted from building the route, if any. -func (r *Route) GetError() error { - return r.err -} - -// BuildOnly sets the route to never match: it is only used to build URLs. -func (r *Route) BuildOnly() *Route { - r.buildOnly = true - return r -} - -// Handler -------------------------------------------------------------------- - -// Handler sets a handler for the route. -func (r *Route) Handler(handler http.Handler) *Route { - if r.err == nil { - r.handler = handler - } - return r -} - -// HandlerFunc sets a handler function for the route. -func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route { - return r.Handler(http.HandlerFunc(f)) -} - -// GetHandler returns the handler for the route, if any. -func (r *Route) GetHandler() http.Handler { - return r.handler -} - -// Name ----------------------------------------------------------------------- - -// Name sets the name for the route, used to build URLs. -// If the name was registered already it will be overwritten. -func (r *Route) Name(name string) *Route { - if r.name != "" { - r.err = fmt.Errorf("mux: route already has name %q, can't set %q", - r.name, name) - } - if r.err == nil { - r.name = name - r.getNamedRoutes()[name] = r - } - return r -} - -// GetName returns the name for the route, if any. -func (r *Route) GetName() string { - return r.name -} - -// ---------------------------------------------------------------------------- -// Matchers -// ---------------------------------------------------------------------------- - -// matcher types try to match a request. -type matcher interface { - Match(*http.Request, *RouteMatch) bool -} - -// addMatcher adds a matcher to the route. -func (r *Route) addMatcher(m matcher) *Route { - if r.err == nil { - r.matchers = append(r.matchers, m) - } - return r -} - -// addRegexpMatcher adds a host or path matcher and builder to a route. -func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery bool) error { - if r.err != nil { - return r.err - } - r.regexp = r.getRegexpGroup() - if !matchHost && !matchQuery { - if len(tpl) == 0 || tpl[0] != '/' { - return fmt.Errorf("mux: path must start with a slash, got %q", tpl) - } - if r.regexp.path != nil { - tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl - } - } - rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash) - if err != nil { - return err - } - for _, q := range r.regexp.queries { - if err = uniqueVars(rr.varsN, q.varsN); err != nil { - return err - } - } - if matchHost { - if r.regexp.path != nil { - if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil { - return err - } - } - r.regexp.host = rr - } else { - if r.regexp.host != nil { - if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil { - return err - } - } - if matchQuery { - r.regexp.queries = append(r.regexp.queries, rr) - } else { - r.regexp.path = rr - } - } - r.addMatcher(rr) - return nil -} - -// Headers -------------------------------------------------------------------- - -// headerMatcher matches the request against header values. -type headerMatcher map[string]string - -func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool { - return matchMapWithString(m, r.Header, true) -} - -// Headers adds a matcher for request header values. -// It accepts a sequence of key/value pairs to be matched. For example: -// -// r := mux.NewRouter() -// r.Headers("Content-Type", "application/json", -// "X-Requested-With", "XMLHttpRequest") -// -// The above route will only match if both request header values match. -// If the value is an empty string, it will match any value if the key is set. -func (r *Route) Headers(pairs ...string) *Route { - if r.err == nil { - var headers map[string]string - headers, r.err = mapFromPairsToString(pairs...) - return r.addMatcher(headerMatcher(headers)) - } - return r -} - -// headerRegexMatcher matches the request against the route given a regex for the header -type headerRegexMatcher map[string]*regexp.Regexp - -func (m headerRegexMatcher) Match(r *http.Request, match *RouteMatch) bool { - return matchMapWithRegex(m, r.Header, true) -} - -// Regular expressions can be used with headers as well. -// It accepts a sequence of key/value pairs, where the value has regex support. For example -// r := mux.NewRouter() -// r.HeadersRegexp("Content-Type", "application/(text|json)", -// "X-Requested-With", "XMLHttpRequest") -// -// The above route will only match if both the request header matches both regular expressions. -// It the value is an empty string, it will match any value if the key is set. -func (r *Route) HeadersRegexp(pairs ...string) *Route { - if r.err == nil { - var headers map[string]*regexp.Regexp - headers, r.err = mapFromPairsToRegex(pairs...) - return r.addMatcher(headerRegexMatcher(headers)) - } - return r -} - -// Host ----------------------------------------------------------------------- - -// Host adds a matcher for the URL host. -// It accepts a template with zero or more URL variables enclosed by {}. -// Variables can define an optional regexp pattern to be matched: -// -// - {name} matches anything until the next dot. -// -// - {name:pattern} matches the given regexp pattern. -// -// For example: -// -// r := mux.NewRouter() -// r.Host("www.example.com") -// r.Host("{subdomain}.domain.com") -// r.Host("{subdomain:[a-z]+}.domain.com") -// -// Variable names must be unique in a given route. They can be retrieved -// calling mux.Vars(request). -func (r *Route) Host(tpl string) *Route { - r.err = r.addRegexpMatcher(tpl, true, false, false) - return r -} - -// MatcherFunc ---------------------------------------------------------------- - -// MatcherFunc is the function signature used by custom matchers. -type MatcherFunc func(*http.Request, *RouteMatch) bool - -func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool { - return m(r, match) -} - -// MatcherFunc adds a custom function to be used as request matcher. -func (r *Route) MatcherFunc(f MatcherFunc) *Route { - return r.addMatcher(f) -} - -// Methods -------------------------------------------------------------------- - -// methodMatcher matches the request against HTTP methods. -type methodMatcher []string - -func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool { - return matchInArray(m, r.Method) -} - -// Methods adds a matcher for HTTP methods. -// It accepts a sequence of one or more methods to be matched, e.g.: -// "GET", "POST", "PUT". -func (r *Route) Methods(methods ...string) *Route { - for k, v := range methods { - methods[k] = strings.ToUpper(v) - } - return r.addMatcher(methodMatcher(methods)) -} - -// Path ----------------------------------------------------------------------- - -// Path adds a matcher for the URL path. -// It accepts a template with zero or more URL variables enclosed by {}. The -// template must start with a "/". -// Variables can define an optional regexp pattern to be matched: -// -// - {name} matches anything until the next slash. -// -// - {name:pattern} matches the given regexp pattern. -// -// For example: -// -// r := mux.NewRouter() -// r.Path("/products/").Handler(ProductsHandler) -// r.Path("/products/{key}").Handler(ProductsHandler) -// r.Path("/articles/{category}/{id:[0-9]+}"). -// Handler(ArticleHandler) -// -// Variable names must be unique in a given route. They can be retrieved -// calling mux.Vars(request). -func (r *Route) Path(tpl string) *Route { - r.err = r.addRegexpMatcher(tpl, false, false, false) - return r -} - -// PathPrefix ----------------------------------------------------------------- - -// PathPrefix adds a matcher for the URL path prefix. This matches if the given -// template is a prefix of the full URL path. See Route.Path() for details on -// the tpl argument. -// -// Note that it does not treat slashes specially ("/foobar/" will be matched by -// the prefix "/foo") so you may want to use a trailing slash here. -// -// Also note that the setting of Router.StrictSlash() has no effect on routes -// with a PathPrefix matcher. -func (r *Route) PathPrefix(tpl string) *Route { - r.err = r.addRegexpMatcher(tpl, false, true, false) - return r -} - -// Query ---------------------------------------------------------------------- - -// Queries adds a matcher for URL query values. -// It accepts a sequence of key/value pairs. Values may define variables. -// For example: -// -// r := mux.NewRouter() -// r.Queries("foo", "bar", "id", "{id:[0-9]+}") -// -// The above route will only match if the URL contains the defined queries -// values, e.g.: ?foo=bar&id=42. -// -// It the value is an empty string, it will match any value if the key is set. -// -// Variables can define an optional regexp pattern to be matched: -// -// - {name} matches anything until the next slash. -// -// - {name:pattern} matches the given regexp pattern. -func (r *Route) Queries(pairs ...string) *Route { - length := len(pairs) - if length%2 != 0 { - r.err = fmt.Errorf( - "mux: number of parameters must be multiple of 2, got %v", pairs) - return nil - } - for i := 0; i < length; i += 2 { - if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], false, false, true); r.err != nil { - return r - } - } - - return r -} - -// Schemes -------------------------------------------------------------------- - -// schemeMatcher matches the request against URL schemes. -type schemeMatcher []string - -func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool { - return matchInArray(m, r.URL.Scheme) -} - -// Schemes adds a matcher for URL schemes. -// It accepts a sequence of schemes to be matched, e.g.: "http", "https". -func (r *Route) Schemes(schemes ...string) *Route { - for k, v := range schemes { - schemes[k] = strings.ToLower(v) - } - return r.addMatcher(schemeMatcher(schemes)) -} - -// BuildVarsFunc -------------------------------------------------------------- - -// BuildVarsFunc is the function signature used by custom build variable -// functions (which can modify route variables before a route's URL is built). -type BuildVarsFunc func(map[string]string) map[string]string - -// BuildVarsFunc adds a custom function to be used to modify build variables -// before a route's URL is built. -func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route { - r.buildVarsFunc = f - return r -} - -// Subrouter ------------------------------------------------------------------ - -// Subrouter creates a subrouter for the route. -// -// It will test the inner routes only if the parent route matched. For example: -// -// r := mux.NewRouter() -// s := r.Host("www.example.com").Subrouter() -// s.HandleFunc("/products/", ProductsHandler) -// s.HandleFunc("/products/{key}", ProductHandler) -// s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler) -// -// Here, the routes registered in the subrouter won't be tested if the host -// doesn't match. -func (r *Route) Subrouter() *Router { - router := &Router{parent: r, strictSlash: r.strictSlash} - r.addMatcher(router) - return router -} - -// ---------------------------------------------------------------------------- -// URL building -// ---------------------------------------------------------------------------- - -// URL builds a URL for the route. -// -// It accepts a sequence of key/value pairs for the route variables. For -// example, given this route: -// -// r := mux.NewRouter() -// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). -// Name("article") -// -// ...a URL for it can be built using: -// -// url, err := r.Get("article").URL("category", "technology", "id", "42") -// -// ...which will return an url.URL with the following path: -// -// "/articles/technology/42" -// -// This also works for host variables: -// -// r := mux.NewRouter() -// r.Host("{subdomain}.domain.com"). -// HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler). -// Name("article") -// -// // url.String() will be "http://news.domain.com/articles/technology/42" -// url, err := r.Get("article").URL("subdomain", "news", -// "category", "technology", -// "id", "42") -// -// All variables defined in the route are required, and their values must -// conform to the corresponding patterns. -func (r *Route) URL(pairs ...string) (*url.URL, error) { - if r.err != nil { - return nil, r.err - } - if r.regexp == nil { - return nil, errors.New("mux: route doesn't have a host or path") - } - values, err := r.prepareVars(pairs...) - if err != nil { - return nil, err - } - var scheme, host, path string - if r.regexp.host != nil { - // Set a default scheme. - scheme = "http" - if host, err = r.regexp.host.url(values); err != nil { - return nil, err - } - } - if r.regexp.path != nil { - if path, err = r.regexp.path.url(values); err != nil { - return nil, err - } - } - return &url.URL{ - Scheme: scheme, - Host: host, - Path: path, - }, nil -} - -// URLHost builds the host part of the URL for a route. See Route.URL(). -// -// The route must have a host defined. -func (r *Route) URLHost(pairs ...string) (*url.URL, error) { - if r.err != nil { - return nil, r.err - } - if r.regexp == nil || r.regexp.host == nil { - return nil, errors.New("mux: route doesn't have a host") - } - values, err := r.prepareVars(pairs...) - if err != nil { - return nil, err - } - host, err := r.regexp.host.url(values) - if err != nil { - return nil, err - } - return &url.URL{ - Scheme: "http", - Host: host, - }, nil -} - -// URLPath builds the path part of the URL for a route. See Route.URL(). -// -// The route must have a path defined. -func (r *Route) URLPath(pairs ...string) (*url.URL, error) { - if r.err != nil { - return nil, r.err - } - if r.regexp == nil || r.regexp.path == nil { - return nil, errors.New("mux: route doesn't have a path") - } - values, err := r.prepareVars(pairs...) - if err != nil { - return nil, err - } - path, err := r.regexp.path.url(values) - if err != nil { - return nil, err - } - return &url.URL{ - Path: path, - }, nil -} - -// prepareVars converts the route variable pairs into a map. If the route has a -// BuildVarsFunc, it is invoked. -func (r *Route) prepareVars(pairs ...string) (map[string]string, error) { - m, err := mapFromPairsToString(pairs...) - if err != nil { - return nil, err - } - return r.buildVars(m), nil -} - -func (r *Route) buildVars(m map[string]string) map[string]string { - if r.parent != nil { - m = r.parent.buildVars(m) - } - if r.buildVarsFunc != nil { - m = r.buildVarsFunc(m) - } - return m -} - -// ---------------------------------------------------------------------------- -// parentRoute -// ---------------------------------------------------------------------------- - -// parentRoute allows routes to know about parent host and path definitions. -type parentRoute interface { - getNamedRoutes() map[string]*Route - getRegexpGroup() *routeRegexpGroup - buildVars(map[string]string) map[string]string -} - -// getNamedRoutes returns the map where named routes are registered. -func (r *Route) getNamedRoutes() map[string]*Route { - if r.parent == nil { - // During tests router is not always set. - r.parent = NewRouter() - } - return r.parent.getNamedRoutes() -} - -// getRegexpGroup returns regexp definitions from this route. -func (r *Route) getRegexpGroup() *routeRegexpGroup { - if r.regexp == nil { - if r.parent == nil { - // During tests router is not always set. - r.parent = NewRouter() - } - regexp := r.parent.getRegexpGroup() - if regexp == nil { - r.regexp = new(routeRegexpGroup) - } else { - // Copy. - r.regexp = &routeRegexpGroup{ - host: regexp.host, - path: regexp.path, - queries: regexp.queries, - } - } - } - return r.regexp -} diff --git a/vendor/github.com/fsouza/go-dockerclient/image.go b/vendor/github.com/fsouza/go-dockerclient/image.go index 47da77dbeb35..f65665a4c8e3 100644 --- a/vendor/github.com/fsouza/go-dockerclient/image.go +++ b/vendor/github.com/fsouza/go-dockerclient/image.go @@ -421,6 +421,17 @@ type BuildImageOptions struct { AuthConfigs AuthConfigurations `qs:"-"` // for newer docker X-Registry-Config header ContextDir string `qs:"-"` Ulimits []ULimit `qs:"-"` + BuildArgs []BuildArg `qs:"-"` +} + +// BuildArg represents arguments that can be passed to the image when building +// it from a Dockerfile. +// +// For more details about the Docker building process, see +// http://goo.gl/tlPXPu. +type BuildArg struct { + Name string `json:"Name,omitempty" yaml:"Name,omitempty"` + Value string `json:"Value,omitempty" yaml:"Value,omitempty"` } // BuildImage builds an image from a tarball's url or a Dockerfile in the input @@ -463,6 +474,18 @@ func (c *Client) BuildImage(opts BuildImageOptions) error { } } + if len(opts.BuildArgs) > 0 { + v := make(map[string]string) + for _, arg := range opts.BuildArgs { + v[arg.Name] = arg.Value + } + if b, err := json.Marshal(v); err == nil { + item := url.Values(map[string][]string{}) + item.Add("buildargs", string(b)) + qs = fmt.Sprintf("%s&%s", qs, item.Encode()) + } + } + return c.stream("POST", fmt.Sprintf("/build?%s", qs), streamOptions{ setRawTerminal: true, rawJSONStream: opts.RawJSONStream, diff --git a/vendor/github.com/fsouza/go-dockerclient/misc.go b/vendor/github.com/fsouza/go-dockerclient/misc.go index 34c96531ad90..ce9e9750b086 100644 --- a/vendor/github.com/fsouza/go-dockerclient/misc.go +++ b/vendor/github.com/fsouza/go-dockerclient/misc.go @@ -4,7 +4,10 @@ package docker -import "strings" +import ( + "encoding/json" + "strings" +) // Version returns version information about the docker server. // @@ -22,17 +25,81 @@ func (c *Client) Version() (*Env, error) { return &env, nil } +// DockerInfo contains information about the Docker server +// +// See https://goo.gl/bHUoz9 for more details. +type DockerInfo struct { + ID string + Containers int + ContainersRunning int + ContainersPaused int + ContainersStopped int + Images int + Driver string + DriverStatus [][2]string + SystemStatus [][2]string + Plugins PluginsInfo + MemoryLimit bool + SwapLimit bool + KernelMemory bool + CPUCfsPeriod bool `json:"CpuCfsPeriod"` + CPUCfsQuota bool `json:"CpuCfsQuota"` + CPUShares bool + CPUSet bool + IPv4Forwarding bool + BridgeNfIptables bool + BridgeNfIP6tables bool `json:"BridgeNfIp6tables"` + Debug bool + NFd int + OomKillDisable bool + NGoroutines int + SystemTime string + ExecutionDriver string + LoggingDriver string + CgroupDriver string + NEventsListener int + KernelVersion string + OperatingSystem string + OSType string + Architecture string + IndexServerAddress string + NCPU int + MemTotal int64 + DockerRootDir string + HTTPProxy string `json:"HttpProxy"` + HTTPSProxy string `json:"HttpsProxy"` + NoProxy string + Name string + Labels []string + ExperimentalBuild bool + ServerVersion string + ClusterStore string + ClusterAdvertise string +} + +// PluginsInfo is a struct with the plugins registered with the docker daemon +// +// for more information, see: https://goo.gl/bHUoz9 +type PluginsInfo struct { + // List of Volume plugins registered + Volume []string + // List of Network plugins registered + Network []string + // List of Authorization plugins registered + Authorization []string +} + // Info returns system-wide information about the Docker server. // // See https://goo.gl/ElTHi2 for more details. -func (c *Client) Info() (*Env, error) { +func (c *Client) Info() (*DockerInfo, error) { resp, err := c.do("GET", "/info", doOptions{}) if err != nil { return nil, err } defer resp.Body.Close() - var info Env - if err := info.Decode(resp.Body); err != nil { + var info DockerInfo + if err := json.NewDecoder(resp.Body).Decode(&info); err != nil { return nil, err } return &info, nil diff --git a/vendor/github.com/fsouza/go-dockerclient/network.go b/vendor/github.com/fsouza/go-dockerclient/network.go index 30d54230a432..b72e91a07ca3 100644 --- a/vendor/github.com/fsouza/go-dockerclient/network.go +++ b/vendor/github.com/fsouza/go-dockerclient/network.go @@ -5,6 +5,7 @@ package docker import ( + "bytes" "encoding/json" "errors" "fmt" @@ -26,6 +27,7 @@ type Network struct { IPAM IPAMOptions Containers map[string]Endpoint Options map[string]string + Internal bool } // Endpoint contains network resources allocated and used for a container in a network @@ -55,6 +57,31 @@ func (c *Client) ListNetworks() ([]Network, error) { return networks, nil } +// NetworkFilterOpts is an aggregation of key=value that Docker +// uses to filter networks +type NetworkFilterOpts map[string]map[string]bool + +// FilteredListNetworks returns all networks with the filters applied +// +// See goo.gl/zd2mx4 for more details. +func (c *Client) FilteredListNetworks(opts NetworkFilterOpts) ([]Network, error) { + params := bytes.NewBuffer(nil) + if err := json.NewEncoder(params).Encode(&opts); err != nil { + return nil, err + } + path := "/networks?filters=" + params.String() + resp, err := c.do("GET", path, doOptions{}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var networks []Network + if err := json.NewDecoder(resp.Body).Decode(&networks); err != nil { + return nil, err + } + return networks, nil +} + // NetworkInfo returns information about a network by its ID. // // See https://goo.gl/6GugX3 for more details. @@ -158,14 +185,40 @@ func (c *Client) RemoveNetwork(id string) error { return nil } -// NetworkConnectionOptions specify parameters to the ConnectNetwork and DisconnectNetwork function. +// NetworkConnectionOptions specify parameters to the ConnectNetwork and +// DisconnectNetwork function. // -// See https://goo.gl/6GugX3 for more details. +// See https://goo.gl/RV7BJU for more details. type NetworkConnectionOptions struct { Container string + + // EndpointConfig is only applicable to the ConnectNetwork call + EndpointConfig *EndpointConfig `json:"EndpointConfig,omitempty"` + + // Force is only applicable to the DisconnectNetwork call + Force bool +} + +// EndpointConfig stores network endpoint details +// +// See https://goo.gl/RV7BJU for more details. +type EndpointConfig struct { + IPAMConfig *EndpointIPAMConfig + Links []string + Aliases []string +} + +// EndpointIPAMConfig represents IPAM configurations for an +// endpoint +// +// See https://goo.gl/RV7BJU for more details. +type EndpointIPAMConfig struct { + IPv4Address string `json:",omitempty"` + IPv6Address string `json:",omitempty"` } -// ConnectNetwork adds a container to a network or returns an error in case of failure. +// ConnectNetwork adds a container to a network or returns an error in case of +// failure. // // See https://goo.gl/6GugX3 for more details. func (c *Client) ConnectNetwork(id string, opts NetworkConnectionOptions) error { @@ -180,7 +233,8 @@ func (c *Client) ConnectNetwork(id string, opts NetworkConnectionOptions) error return nil } -// DisconnectNetwork removes a container from a network or returns an error in case of failure. +// DisconnectNetwork removes a container from a network or returns an error in +// case of failure. // // See https://goo.gl/6GugX3 for more details. func (c *Client) DisconnectNetwork(id string, opts NetworkConnectionOptions) error { @@ -204,7 +258,8 @@ func (err *NoSuchNetwork) Error() string { return fmt.Sprintf("No such network: %s", err.ID) } -// NoSuchNetwork is the error returned when a given network or container does not exist. +// NoSuchNetworkOrContainer is the error returned when a given network or +// container does not exist. type NoSuchNetworkOrContainer struct { NetworkID string ContainerID string diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/data/.dockerignore b/vendor/github.com/fsouza/go-dockerclient/testing/data/.dockerignore deleted file mode 100644 index 027e8c20e616..000000000000 --- a/vendor/github.com/fsouza/go-dockerclient/testing/data/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -container.tar -dockerfile.tar -foofile diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/data/Dockerfile b/vendor/github.com/fsouza/go-dockerclient/testing/data/Dockerfile deleted file mode 100644 index 0948dcfa8cc5..000000000000 --- a/vendor/github.com/fsouza/go-dockerclient/testing/data/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -# this file describes how to build tsuru python image -# to run it: -# 1- install docker -# 2- run: $ docker build -t tsuru/python https://raw.github.com/tsuru/basebuilder/master/python/Dockerfile - -from base:ubuntu-quantal -run apt-get install wget -y --force-yes -run wget http://github.com/tsuru/basebuilder/tarball/master -O basebuilder.tar.gz --no-check-certificate -run mkdir /var/lib/tsuru -run tar -xvf basebuilder.tar.gz -C /var/lib/tsuru --strip 1 -run cp /var/lib/tsuru/python/deploy /var/lib/tsuru -run cp /var/lib/tsuru/base/restart /var/lib/tsuru -run cp /var/lib/tsuru/base/start /var/lib/tsuru -run /var/lib/tsuru/base/install -run /var/lib/tsuru/base/setup diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/data/barfile b/vendor/github.com/fsouza/go-dockerclient/testing/data/barfile deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/data/ca.pem b/vendor/github.com/fsouza/go-dockerclient/testing/data/ca.pem deleted file mode 100644 index 8e38bba13c69..000000000000 --- a/vendor/github.com/fsouza/go-dockerclient/testing/data/ca.pem +++ /dev/null @@ -1,18 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIC1TCCAb+gAwIBAgIQJ9MsNxrUxumNbAytGi3GEDALBgkqhkiG9w0BAQswFjEU -MBIGA1UEChMLQm9vdDJEb2NrZXIwHhcNMTQxMDE2MjAyMTM4WhcNMTcwOTMwMjAy -MTM4WjAWMRQwEgYDVQQKEwtCb290MkRvY2tlcjCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBALpFCSARjG+5yXoqr7UMzuE0df7RRZfeRZI06lJ02ZqV4Iii -rgL7ML9yPxX50NbLnjiilSDTUhnyocYFItokzUzz8qpX/nlYhuN2Iqwh4d0aWS8z -f5y248F+H1z+HY2W8NPl/6DVlVwYaNW1/k+RPMlHS0INLR6j+3Ievew7RNE0NnM2 -znELW6NetekDt3GUcz0Z95vDUDfdPnIk1eIFMmYvLxZh23xOca4Q37a3S8F3d+dN -+OOpwjdgY9Qme0NQUaXpgp58jWuQfB8q7mZrdnLlLqRa8gx1HeDSotX7UmWtWPkb -vd9EdlKLYw5PVpxMV1rkwf2t4TdgD5NfkpXlXkkCAwEAAaMjMCEwDgYDVR0PAQH/ -BAQDAgCkMA8GA1UdEwEB/wQFMAMBAf8wCwYJKoZIhvcNAQELA4IBAQBxYjHVSKqE -MJw7CW0GddesULtXXVWGJuZdWJLQlPvPMfIfjIvlcZyS4cdVNiQ3sREFIZz8TpII -CT0/Pg3sgv/FcOQe1CN0xZYZcyiAZHK1z0fJQq2qVpdv7+tJcjI2vvU6NI24iQCo -W1wz25trJz9QbdB2MRLMjyz7TSWuafztIvcfEzaIdQ0Whqund/cSuPGQx5IwF83F -rvlkOyJSH2+VIEBTCIuykJeL0DLTt8cePBQR5L1ISXb4RUMK9ZtqRscBRv8sn7o2 -ixG3wtL0gYF4xLtsQWVxI3iFVrU3WzOH/3c5shVRkWBd+AQRSwCJI4mKH7penJCF -i3/zzlkvOnjV ------END CERTIFICATE----- diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/data/cert.pem b/vendor/github.com/fsouza/go-dockerclient/testing/data/cert.pem deleted file mode 100644 index 5e7244b24fff..000000000000 --- a/vendor/github.com/fsouza/go-dockerclient/testing/data/cert.pem +++ /dev/null @@ -1,18 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIC6DCCAdKgAwIBAgIRANO6ymxQAjp66KmEka1G6b0wCwYJKoZIhvcNAQELMBYx -FDASBgNVBAoTC0Jvb3QyRG9ja2VyMB4XDTE0MTAxNjIwMjE1MloXDTE3MDkzMDIw -MjE1MlowFjEUMBIGA1UEChMLQm9vdDJEb2NrZXIwggEiMA0GCSqGSIb3DQEBAQUA -A4IBDwAwggEKAoIBAQDGA1mAhSOpZspD1dpZ7qVEQrIJw4Xo8252jHaORnEdDiFm -b6brEmr6jw8t4P3IGxbqBc/TqRV+SSXxwYEVvfpeQKH+SmqStoMNtD3Ura161az4 -V0BcxMtSlsUGpoz+//QCAq8qiaxMwgiyc5253mkQm88anj2cNt7xbewiu/KFWuf7 -BVpNK1+ltpJmlukfcj/G+I1bw7j1KxBjDrFqe5cyDuuZcDL2tmUXP/ZWDyXwSv+H -AOckqn44z6aXlBkVvOXDBZJqY76d/vWVDNCuZeXRnqlhP3t1kH4V0RQXo+JD2tgt -JgdU0unzyoFOSWNUBPm73tqmjUGGAmGHBmeegJr/AgMBAAGjNTAzMA4GA1UdDwEB -/wQEAwIAgDATBgNVHSUEDDAKBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMAsGCSqG -SIb3DQEBCwOCAQEABVTWl5SmBP+j5He5bQsgnIXjviSKqe40/10V4LJAOmilycRF -zLrzM+YMwfjg6PLIs8CldAMWHw9y9ktZY4MxkgCktaiaN/QmMTMwFWEcN4wy5IpM -U5l93eAg7xsnY430h3QBBADujX4wdF3fs8rSL8zAAQFL0ihurwU124K3yXKsrwpb -CiVUGfIN4sPwjy8Ws9oxHFDC9/P8lgjHZ1nBIf8KSHnMzlxDGj7isQfhtH+7mcCL -cM1qO2NirS2v7uaEPPY+MJstAz+W7EJCW9dfMSmHna2SDC37Xkin7uEY9z+qaKFL -8d/XxOB/L8Ucy8VZhdsv0dsBq5KfJntITM0ksQ== ------END CERTIFICATE----- diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/data/container.tar b/vendor/github.com/fsouza/go-dockerclient/testing/data/container.tar deleted file mode 100644 index e4b066e3b6df8cb78ac445a34234f3780d164cf4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2048 zcmeH_Q3``F42FH)DgF~kTC`qZ7s*`9%A^%r$Bu89Fp<6NMew1akmheFe?H>)Y5N#5 z`(UT)m>?q4G^iwZ#(XmAwH8Ujv`|_rQd)Ig3sQ!(szArs+5bAH%#&Di1HU}iJx_zp z+3uU9k~Zgl)J<3?S%)LS_Hgc7e)t4AX&%Rz>>WAcX2Ec>82D}md=O1Y)p%bo=N_rJ OD+CIGLZA@%gTMmt=q{T8 diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/data/dockerfile.tar b/vendor/github.com/fsouza/go-dockerclient/testing/data/dockerfile.tar deleted file mode 100644 index 32c9ce64704835cd096b85ac44c35b5087b5ccdd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2560 zcmeHGy>8<$49;3V1%d0TNOs}`$a>xT46-c8LTt+?QB8ACf0XPiQll-h0~9$I?_v`_`p)qp;@ z0OJK)JAmosQD=m*-~y?5ASGvD1{zS;L7n!AYz2z}2Y8%Kb25fgK0fDb5l4UE+{yF$ zXs`{{TG^hbn!J);Cl1>2UV0=k!T8hL+GbhfZ2u5L51|SJ2KFb&fyiW3|3Qw(jvC+i zouk4oz*u9Q((Iyric9uLhPZsmgZ8ANMrS_2p5cn+n!M}dU&=mMrdq8|OlgOvF-oFN zh5A!%9Pk(EcxS4q(c~Z~u-BL7!+gIN2&&-GnGy1YRpY|{e@?X?J9}9;KY_$PxYO}H o;5QJT#=q||{Y*ZuNn-Gk-)jtGb|Y`+PV+v2`vmS2xaA4_1I+dVl>h($ diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/data/foofile b/vendor/github.com/fsouza/go-dockerclient/testing/data/foofile deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/data/key.pem b/vendor/github.com/fsouza/go-dockerclient/testing/data/key.pem deleted file mode 100644 index a9346bcf45ad..000000000000 --- a/vendor/github.com/fsouza/go-dockerclient/testing/data/key.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAxgNZgIUjqWbKQ9XaWe6lREKyCcOF6PNudox2jkZxHQ4hZm+m -6xJq+o8PLeD9yBsW6gXP06kVfkkl8cGBFb36XkCh/kpqkraDDbQ91K2tetWs+FdA -XMTLUpbFBqaM/v/0AgKvKomsTMIIsnOdud5pEJvPGp49nDbe8W3sIrvyhVrn+wVa -TStfpbaSZpbpH3I/xviNW8O49SsQYw6xanuXMg7rmXAy9rZlFz/2Vg8l8Er/hwDn -JKp+OM+ml5QZFbzlwwWSamO+nf71lQzQrmXl0Z6pYT97dZB+FdEUF6PiQ9rYLSYH -VNLp88qBTkljVAT5u97apo1BhgJhhwZnnoCa/wIDAQABAoIBAQCaGy9EC9pmU95l -DwGh7k5nIrUnTilg1FwLHWSDdCVCZKXv8ENrPelOWZqJrUo1u4eI2L8XTsewgkNq -tJu/DRzWz9yDaO0qg6rZNobMh+K076lvmZA44twOydJLS8H+D7ua+PXU2FLlZjmY -kMyXRJZmW6zCXZc7haTbJx6ZJccoquk/DkS4FcFurJP177u1YrWS9TTw9kensUtU -jQ63uf56UTN1i+0+Rxl7OW1TZlqwlri5I4njg5249+FxwwHzIq8+l7zD7K9pl8c/ -nG1HuulvU2bVlDlRdyslMPAH34vw9Sku1BD8furrJLr1na5lRSLKJODEaIPEsLwv -CdEUwP9JAoGBAO76ZW80RyNB2fA+wbTq70Sr8CwrXxYemXrez5LKDC7SsohKFCPE -IedpO/n+nmymiiJvMm874EExoG6BVrbkWkeb+2vinEfOQNlDMsDx7WLjPekP3t6i -rXHO3CjFooVFq2z3mZa/Nc5NZqu8fNWNCKJxZDJphdoj6sORNJIUvZVjAoGBANQd -++J+ITcu3/+A6JrGcgLunBFQYPqkiItk0J4QKYKuX5ik9rWcQDN8TTtfW2mDuiQ4 -NrCwuVPq1V1kB16JzH017SsYLo9g8I20YjnBZge9pKTeUaLVTb3C50LW8FBylop0 -Bnm597dNbtSjphjoTMg0XyC19o3Esf2YeWG0QNS1AoGAWWDfFRNJU99qIldmXULM -0DM6NVrXSk+ReYnhunXEzrJQwXZrR+EwCPurydk36Uz0NuK9yypquhdUeF/5TZfk -SAoHo5byekyipl9imRUigqyY2BTudvgCxKDoaHtaSFwBPFTyZZYICquaLbrmOXxw -8UhVgCFFRYvPXuts7QHC0h8CgYBWEvy9gfU0kV7wLX02IUTuj6jhFb7ktpN6DSTi -nyhZES1VoctDEu6ydcRZTW6ouH12aSE4Pd5WgTqntQmQgVZrkNB25k8ue2Xh+srJ -KQOgLIJ9LIHwE6KCWG7DnrjRzE3uTPq7to0g4tkQjH/AJ7PQof/gJDayfJjFkXPg -A+cy6QKBgEPbKpiqscm03gT2QanBut5pg4dqPOxp0SlErA3kSFNTRK3oYBQPC+LH -qA5nD5brdkeNBB58Rll8Zpzxiff50bcvLP/7/Sb3NjaXFTEY0gVbdRof3n6N0YP3 -Hu5XDNJ9RNkNzE5RIG1g86KE+aKlcrKMaigqAiuIy2PSnjkQeGk8 ------END RSA PRIVATE KEY----- diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/data/server.pem b/vendor/github.com/fsouza/go-dockerclient/testing/data/server.pem deleted file mode 100644 index 89cc445e1ba1..000000000000 --- a/vendor/github.com/fsouza/go-dockerclient/testing/data/server.pem +++ /dev/null @@ -1,18 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIC/DCCAeagAwIBAgIQMUILcXtvmSOK63zEBo0VXzALBgkqhkiG9w0BAQswFjEU -MBIGA1UEChMLQm9vdDJEb2NrZXIwHhcNMTQxMDE2MjAyMTQ2WhcNMTcwOTMwMjAy -MTQ2WjAWMRQwEgYDVQQKEwtCb290MkRvY2tlcjCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBANxUOUhNnqFnrTlLsBYzfFRZWQo268l+4K4lOJCVbfDonP3g -Mz0vGi9fcyFqEWSA8Y+ShXna625HTnReCwFdsu0861qCIq7v95hFFCyOe0iIxpd0 -AKLnl90d+1vonE7andgFgoobbTiMly4UK4H6z8D148fFNIihoteOG3PIF89TFxP7 -CJ/3wXnx/IKpdlO8PAnub3tBPJHvGDj7KORLy4IBxRX5VBAdfGNybE66fcrehEva -rLA4m9pgiaR/Nnr9FdKhPyqYdjflLNvzydxNvMIV4M0hFlhXmYvpMjA5/XsTnsyV -t9JHJa5Upwqsbne08t7rsm7liZNxZlko8xPOTQcCAwEAAaNKMEgwDgYDVR0PAQH/ -BAQDAgCgMAwGA1UdEwEB/wQCMAAwKAYDVR0RBCEwH4ILYm9vdDJkb2NrZXKHBH8A -AAGHBAoAAg+HBMCoO2cwCwYJKoZIhvcNAQELA4IBAQAYoYcDkDWkl73FZ0WnPmAj -LiF7HU95Qg3KyEpFsAJeShSLPPbQntmwhdekEzY4tQ3eKQB/+zHFjzsCr/lmDUmH -Ea/ryQ17C+jyH+Ykg0IWW6L6veZhvRDg6Z9focVtPVBRxPTqC/Qhb54blWRASV+W -UreMuXQ5+1dQptAM7ixOeLVHjBi/bd9TL3jvwBVCr9QedteMjjK4TCF9Tbcou+MF -2w3OJJZMDhcD+YwoK9uJDqlKmcTm/vVMbSsp/pTMcnQ7jxCeR8/XyX+VwTZwaHAa -o92Q/eg3THAiWhvyT/SzyH9dHHBAyXynUwGCggKawHktfvW4QXRPuLxLrJ7iB5cy ------END CERTIFICATE----- diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/data/serverkey.pem b/vendor/github.com/fsouza/go-dockerclient/testing/data/serverkey.pem deleted file mode 100644 index c897e5da5501..000000000000 --- a/vendor/github.com/fsouza/go-dockerclient/testing/data/serverkey.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEoAIBAAKCAQEA3FQ5SE2eoWetOUuwFjN8VFlZCjbryX7griU4kJVt8Oic/eAz -PS8aL19zIWoRZIDxj5KFedrrbkdOdF4LAV2y7TzrWoIiru/3mEUULI57SIjGl3QA -oueX3R37W+icTtqd2AWCihttOIyXLhQrgfrPwPXjx8U0iKGi144bc8gXz1MXE/sI -n/fBefH8gql2U7w8Ce5ve0E8ke8YOPso5EvLggHFFflUEB18Y3JsTrp9yt6ES9qs -sDib2mCJpH82ev0V0qE/Kph2N+Us2/PJ3E28whXgzSEWWFeZi+kyMDn9exOezJW3 -0kclrlSnCqxud7Ty3uuybuWJk3FmWSjzE85NBwIDAQABAoIBAG0ak+cW8LeShHf7 -3+2Of0GxoOLrAWWdG5uAuPr31CJYve0FybnBimDtDjD8ujIfm/7xmoEWBEFutA3x -x9dcU88gvJbsHEqub9gKVQwfXjMz78tt2SbSMiR/xUnk7QorPcCMMfE71aEMFYzu -1gCed6Rg3vO81t/V0rKVH0j9S7UQz5v/oX15eVDV5LOqyCHwAi6K0eXXbqnbI0TH -SOQ/nexM2msVXWbO9t6ra6f5V7FXziDK5Xi+rPxRbX9mkrDzxDAevfuRqYBx5vtL -W2Q2hKjUAHFgXFniNSZBS7dCdAtz0el/3ct+cNmpuTMhhs7M6wC1CuYiZ/DxLiFh -Si73VckCgYEA+/ceh3+VjtQ0rgEw8sD9bqYEA8IaBiObjneIoFnKBYRG7yZd8JMm -HD4M/aQ1qhcRLPN7GR03YQULgQJURbKSjJHnhfTXHyeHC3NN4gMVHQXewu2MHCh6 -7FCQ9CfK0KcYLgegVVvL3PrF3hyWGnmTu+G0UkDQRYVnaNrB7snrW6UCgYEA39tq -+MCQdu0moJ5szSZf02undg9EeW6isk9qzi7TId3/MLci2eH7PEnipipPUK3+DERq -aba0y0TKgBR2EXvXLFJA/+kfdo2loIEHOfox85HVfxgUaFRti63ZI0uF8D0QT2Yy -oJal+RFghVoSnv4LjhRKEPbIkScTXGjdK+7wFjsCfz79iKRXQQx0ALd/lL0bgkAn -QNmvrNHcFQeI2p8700WNzC39aX67SsvEt3qxkrjzC1gxhpTAuReIK1gVPPwvqHN8 -BmV20FD5kMlMCix2mNCopwgUWvKvLAvoGFTxncKMA39+aJbuXAjiqJTekKgNvOE7 -i9kEWw0GTNPp3JHV6QECgYAPwb0M11kT1euDIMOdyRazpf86kyaJuZzgGjD1ZFxe -JOcigbGFTp/FhZnbglzk2+pm6KXo3QBq0mPCki4hWusxZnTGzpz1VlETNCHTFeZQ -M7KoaIR/N3oie9Et59H8r/+m5xWnMhNqratyl316DX24uXrhKM3DUdHODl+LCR2D -IwKBgE1MbHuwolUPEw3HeO4R7NMFVTFei7E/fpUsimPfArGg8UydwvloNT1myJos -N2JzfGGjN2KPVcBk9fOs71mJ6VcK3C3g5JIccplk6h9VNaw55+zdQvKPTzoBoTvy -A+Fwx2AlF61KeRF87DL2YTRJ6B9MHmWgf7+GVZOxomLgEAcZ ------END RSA PRIVATE KEY----- diff --git a/vendor/github.com/fsouza/go-dockerclient/testing/server.go b/vendor/github.com/fsouza/go-dockerclient/testing/server.go deleted file mode 100644 index b16e713676eb..000000000000 --- a/vendor/github.com/fsouza/go-dockerclient/testing/server.go +++ /dev/null @@ -1,1246 +0,0 @@ -// Copyright 2015 go-dockerclient authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package testing provides a fake implementation of the Docker API, useful for -// testing purpose. -package testing - -import ( - "archive/tar" - "crypto/rand" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - mathrand "math/rand" - "net" - "net/http" - "regexp" - "strconv" - "strings" - "sync" - "time" - - "github.com/fsouza/go-dockerclient" - "github.com/fsouza/go-dockerclient/external/github.com/docker/docker/pkg/stdcopy" - "github.com/fsouza/go-dockerclient/external/github.com/gorilla/mux" -) - -var nameRegexp = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_.-]+$`) - -// DockerServer represents a programmable, concurrent (not much), HTTP server -// implementing a fake version of the Docker remote API. -// -// It can used in standalone mode, listening for connections or as an arbitrary -// HTTP handler. -// -// For more details on the remote API, check http://goo.gl/G3plxW. -type DockerServer struct { - containers []*docker.Container - uploadedFiles map[string]string - execs []*docker.ExecInspect - execMut sync.RWMutex - cMut sync.RWMutex - images []docker.Image - iMut sync.RWMutex - imgIDs map[string]string - networks []*docker.Network - netMut sync.RWMutex - listener net.Listener - mux *mux.Router - hook func(*http.Request) - failures map[string]string - multiFailures []map[string]string - execCallbacks map[string]func() - statsCallbacks map[string]func(string) docker.Stats - customHandlers map[string]http.Handler - handlerMutex sync.RWMutex - cChan chan<- *docker.Container - volStore map[string]*volumeCounter - volMut sync.RWMutex -} - -type volumeCounter struct { - volume docker.Volume - count int -} - -// NewServer returns a new instance of the fake server, in standalone mode. Use -// the method URL to get the URL of the server. -// -// It receives the bind address (use 127.0.0.1:0 for getting an available port -// on the host), a channel of containers and a hook function, that will be -// called on every request. -// -// The fake server will send containers in the channel whenever the container -// changes its state, via the HTTP API (i.e.: create, start and stop). This -// channel may be nil, which means that the server won't notify on state -// changes. -func NewServer(bind string, containerChan chan<- *docker.Container, hook func(*http.Request)) (*DockerServer, error) { - listener, err := net.Listen("tcp", bind) - if err != nil { - return nil, err - } - server := DockerServer{ - listener: listener, - imgIDs: make(map[string]string), - hook: hook, - failures: make(map[string]string), - execCallbacks: make(map[string]func()), - statsCallbacks: make(map[string]func(string) docker.Stats), - customHandlers: make(map[string]http.Handler), - uploadedFiles: make(map[string]string), - cChan: containerChan, - } - server.buildMuxer() - go http.Serve(listener, &server) - return &server, nil -} - -func (s *DockerServer) notify(container *docker.Container) { - if s.cChan != nil { - s.cChan <- container - } -} - -func (s *DockerServer) buildMuxer() { - s.mux = mux.NewRouter() - s.mux.Path("/commit").Methods("POST").HandlerFunc(s.handlerWrapper(s.commitContainer)) - s.mux.Path("/containers/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.listContainers)) - s.mux.Path("/containers/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.createContainer)) - s.mux.Path("/containers/{id:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectContainer)) - s.mux.Path("/containers/{id:.*}/rename").Methods("POST").HandlerFunc(s.handlerWrapper(s.renameContainer)) - s.mux.Path("/containers/{id:.*}/top").Methods("GET").HandlerFunc(s.handlerWrapper(s.topContainer)) - s.mux.Path("/containers/{id:.*}/start").Methods("POST").HandlerFunc(s.handlerWrapper(s.startContainer)) - s.mux.Path("/containers/{id:.*}/kill").Methods("POST").HandlerFunc(s.handlerWrapper(s.stopContainer)) - s.mux.Path("/containers/{id:.*}/stop").Methods("POST").HandlerFunc(s.handlerWrapper(s.stopContainer)) - s.mux.Path("/containers/{id:.*}/pause").Methods("POST").HandlerFunc(s.handlerWrapper(s.pauseContainer)) - s.mux.Path("/containers/{id:.*}/unpause").Methods("POST").HandlerFunc(s.handlerWrapper(s.unpauseContainer)) - s.mux.Path("/containers/{id:.*}/wait").Methods("POST").HandlerFunc(s.handlerWrapper(s.waitContainer)) - s.mux.Path("/containers/{id:.*}/attach").Methods("POST").HandlerFunc(s.handlerWrapper(s.attachContainer)) - s.mux.Path("/containers/{id:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeContainer)) - s.mux.Path("/containers/{id:.*}/exec").Methods("POST").HandlerFunc(s.handlerWrapper(s.createExecContainer)) - s.mux.Path("/containers/{id:.*}/stats").Methods("GET").HandlerFunc(s.handlerWrapper(s.statsContainer)) - s.mux.Path("/containers/{id:.*}/archive").Methods("PUT").HandlerFunc(s.handlerWrapper(s.uploadToContainer)) - s.mux.Path("/exec/{id:.*}/resize").Methods("POST").HandlerFunc(s.handlerWrapper(s.resizeExecContainer)) - s.mux.Path("/exec/{id:.*}/start").Methods("POST").HandlerFunc(s.handlerWrapper(s.startExecContainer)) - s.mux.Path("/exec/{id:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectExecContainer)) - s.mux.Path("/images/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.pullImage)) - s.mux.Path("/build").Methods("POST").HandlerFunc(s.handlerWrapper(s.buildImage)) - s.mux.Path("/images/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.listImages)) - s.mux.Path("/images/{id:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeImage)) - s.mux.Path("/images/{name:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectImage)) - s.mux.Path("/images/{name:.*}/push").Methods("POST").HandlerFunc(s.handlerWrapper(s.pushImage)) - s.mux.Path("/images/{name:.*}/tag").Methods("POST").HandlerFunc(s.handlerWrapper(s.tagImage)) - s.mux.Path("/events").Methods("GET").HandlerFunc(s.listEvents) - s.mux.Path("/_ping").Methods("GET").HandlerFunc(s.handlerWrapper(s.pingDocker)) - s.mux.Path("/images/load").Methods("POST").HandlerFunc(s.handlerWrapper(s.loadImage)) - s.mux.Path("/images/{id:.*}/get").Methods("GET").HandlerFunc(s.handlerWrapper(s.getImage)) - s.mux.Path("/networks").Methods("GET").HandlerFunc(s.handlerWrapper(s.listNetworks)) - s.mux.Path("/networks/{id:.*}").Methods("GET").HandlerFunc(s.handlerWrapper(s.networkInfo)) - s.mux.Path("/networks").Methods("POST").HandlerFunc(s.handlerWrapper(s.createNetwork)) - s.mux.Path("/volumes").Methods("GET").HandlerFunc(s.handlerWrapper(s.listVolumes)) - s.mux.Path("/volumes/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.createVolume)) - s.mux.Path("/volumes/{name:.*}").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectVolume)) - s.mux.Path("/volumes/{name:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeVolume)) -} - -// SetHook changes the hook function used by the server. -// -// The hook function is a function called on every request. -func (s *DockerServer) SetHook(hook func(*http.Request)) { - s.hook = hook -} - -// PrepareExec adds a callback to a container exec in the fake server. -// -// This function will be called whenever the given exec id is started, and the -// given exec id will remain in the "Running" start while the function is -// running, so it's useful for emulating an exec that runs for two seconds, for -// example: -// -// opts := docker.CreateExecOptions{ -// AttachStdin: true, -// AttachStdout: true, -// AttachStderr: true, -// Tty: true, -// Cmd: []string{"/bin/bash", "-l"}, -// } -// // Client points to a fake server. -// exec, err := client.CreateExec(opts) -// // handle error -// server.PrepareExec(exec.ID, func() {time.Sleep(2 * time.Second)}) -// err = client.StartExec(exec.ID, docker.StartExecOptions{Tty: true}) // will block for 2 seconds -// // handle error -func (s *DockerServer) PrepareExec(id string, callback func()) { - s.execCallbacks[id] = callback -} - -// PrepareStats adds a callback that will be called for each container stats -// call. -// -// This callback function will be called multiple times if stream is set to -// true when stats is called. -func (s *DockerServer) PrepareStats(id string, callback func(string) docker.Stats) { - s.statsCallbacks[id] = callback -} - -// PrepareFailure adds a new expected failure based on a URL regexp it receives -// an id for the failure. -func (s *DockerServer) PrepareFailure(id string, urlRegexp string) { - s.failures[id] = urlRegexp -} - -// PrepareMultiFailures enqueues a new expected failure based on a URL regexp -// it receives an id for the failure. -func (s *DockerServer) PrepareMultiFailures(id string, urlRegexp string) { - s.multiFailures = append(s.multiFailures, map[string]string{"error": id, "url": urlRegexp}) -} - -// ResetFailure removes an expected failure identified by the given id. -func (s *DockerServer) ResetFailure(id string) { - delete(s.failures, id) -} - -// ResetMultiFailures removes all enqueued failures. -func (s *DockerServer) ResetMultiFailures() { - s.multiFailures = []map[string]string{} -} - -// CustomHandler registers a custom handler for a specific path. -// -// For example: -// -// server.CustomHandler("/containers/json", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { -// http.Error(w, "Something wrong is not right", http.StatusInternalServerError) -// })) -func (s *DockerServer) CustomHandler(path string, handler http.Handler) { - s.handlerMutex.Lock() - s.customHandlers[path] = handler - s.handlerMutex.Unlock() -} - -// MutateContainer changes the state of a container, returning an error if the -// given id does not match to any container "running" in the server. -func (s *DockerServer) MutateContainer(id string, state docker.State) error { - for _, container := range s.containers { - if container.ID == id { - container.State = state - return nil - } - } - return errors.New("container not found") -} - -// Stop stops the server. -func (s *DockerServer) Stop() { - if s.listener != nil { - s.listener.Close() - } -} - -// URL returns the HTTP URL of the server. -func (s *DockerServer) URL() string { - if s.listener == nil { - return "" - } - return "http://" + s.listener.Addr().String() + "/" -} - -// ServeHTTP handles HTTP requests sent to the server. -func (s *DockerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { - s.handlerMutex.RLock() - defer s.handlerMutex.RUnlock() - for re, handler := range s.customHandlers { - if m, _ := regexp.MatchString(re, r.URL.Path); m { - handler.ServeHTTP(w, r) - return - } - } - s.mux.ServeHTTP(w, r) - if s.hook != nil { - s.hook(r) - } -} - -// DefaultHandler returns default http.Handler mux, it allows customHandlers to -// call the default behavior if wanted. -func (s *DockerServer) DefaultHandler() http.Handler { - return s.mux -} - -func (s *DockerServer) handlerWrapper(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - for errorID, urlRegexp := range s.failures { - matched, err := regexp.MatchString(urlRegexp, r.URL.Path) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - if !matched { - continue - } - http.Error(w, errorID, http.StatusBadRequest) - return - } - for i, failure := range s.multiFailures { - matched, err := regexp.MatchString(failure["url"], r.URL.Path) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - if !matched { - continue - } - http.Error(w, failure["error"], http.StatusBadRequest) - s.multiFailures = append(s.multiFailures[:i], s.multiFailures[i+1:]...) - return - } - f(w, r) - } -} - -func (s *DockerServer) listContainers(w http.ResponseWriter, r *http.Request) { - all := r.URL.Query().Get("all") - s.cMut.RLock() - result := make([]docker.APIContainers, 0, len(s.containers)) - for _, container := range s.containers { - if all == "1" || container.State.Running { - result = append(result, docker.APIContainers{ - ID: container.ID, - Image: container.Image, - Command: fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")), - Created: container.Created.Unix(), - Status: container.State.String(), - Ports: container.NetworkSettings.PortMappingAPI(), - Names: []string{fmt.Sprintf("/%s", container.Name)}, - }) - } - } - s.cMut.RUnlock() - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(result) -} - -func (s *DockerServer) listImages(w http.ResponseWriter, r *http.Request) { - s.cMut.RLock() - result := make([]docker.APIImages, len(s.images)) - for i, image := range s.images { - result[i] = docker.APIImages{ - ID: image.ID, - Created: image.Created.Unix(), - } - for tag, id := range s.imgIDs { - if id == image.ID { - result[i].RepoTags = append(result[i].RepoTags, tag) - } - } - } - s.cMut.RUnlock() - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(result) -} - -func (s *DockerServer) findImage(id string) (string, error) { - s.iMut.RLock() - defer s.iMut.RUnlock() - image, ok := s.imgIDs[id] - if ok { - return image, nil - } - image, _, err := s.findImageByID(id) - return image, err -} - -func (s *DockerServer) findImageByID(id string) (string, int, error) { - s.iMut.RLock() - defer s.iMut.RUnlock() - for i, image := range s.images { - if image.ID == id { - return image.ID, i, nil - } - } - return "", -1, errors.New("No such image") -} - -func (s *DockerServer) createContainer(w http.ResponseWriter, r *http.Request) { - var config struct { - *docker.Config - HostConfig *docker.HostConfig - } - defer r.Body.Close() - err := json.NewDecoder(r.Body).Decode(&config) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - name := r.URL.Query().Get("name") - if name != "" && !nameRegexp.MatchString(name) { - http.Error(w, "Invalid container name", http.StatusInternalServerError) - return - } - if _, err := s.findImage(config.Image); err != nil { - http.Error(w, err.Error(), http.StatusNotFound) - return - } - ports := map[docker.Port][]docker.PortBinding{} - for port := range config.ExposedPorts { - ports[port] = []docker.PortBinding{{ - HostIP: "0.0.0.0", - HostPort: strconv.Itoa(mathrand.Int() % 0xffff), - }} - } - - //the container may not have cmd when using a Dockerfile - var path string - var args []string - if len(config.Cmd) == 1 { - path = config.Cmd[0] - } else if len(config.Cmd) > 1 { - path = config.Cmd[0] - args = config.Cmd[1:] - } - - generatedID := s.generateID() - config.Config.Hostname = generatedID[:12] - container := docker.Container{ - Name: name, - ID: generatedID, - Created: time.Now(), - Path: path, - Args: args, - Config: config.Config, - HostConfig: config.HostConfig, - State: docker.State{ - Running: false, - Pid: mathrand.Int() % 50000, - ExitCode: 0, - StartedAt: time.Now(), - }, - Image: config.Image, - NetworkSettings: &docker.NetworkSettings{ - IPAddress: fmt.Sprintf("172.16.42.%d", mathrand.Int()%250+2), - IPPrefixLen: 24, - Gateway: "172.16.42.1", - Bridge: "docker0", - Ports: ports, - }, - } - s.cMut.Lock() - if container.Name != "" { - for _, c := range s.containers { - if c.Name == container.Name { - defer s.cMut.Unlock() - http.Error(w, "there's already a container with this name", http.StatusConflict) - return - } - } - } - s.containers = append(s.containers, &container) - s.cMut.Unlock() - w.WriteHeader(http.StatusCreated) - s.notify(&container) - - json.NewEncoder(w).Encode(container) -} - -func (s *DockerServer) generateID() string { - var buf [16]byte - rand.Read(buf[:]) - return fmt.Sprintf("%x", buf) -} - -func (s *DockerServer) renameContainer(w http.ResponseWriter, r *http.Request) { - id := mux.Vars(r)["id"] - container, index, err := s.findContainer(id) - if err != nil { - http.Error(w, err.Error(), http.StatusNotFound) - return - } - copy := *container - copy.Name = r.URL.Query().Get("name") - s.cMut.Lock() - defer s.cMut.Unlock() - if s.containers[index].ID == copy.ID { - s.containers[index] = © - } - w.WriteHeader(http.StatusNoContent) -} - -func (s *DockerServer) inspectContainer(w http.ResponseWriter, r *http.Request) { - id := mux.Vars(r)["id"] - container, _, err := s.findContainer(id) - if err != nil { - http.Error(w, err.Error(), http.StatusNotFound) - return - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(container) -} - -func (s *DockerServer) statsContainer(w http.ResponseWriter, r *http.Request) { - id := mux.Vars(r)["id"] - _, _, err := s.findContainer(id) - if err != nil { - http.Error(w, err.Error(), http.StatusNotFound) - return - } - stream, _ := strconv.ParseBool(r.URL.Query().Get("stream")) - callback := s.statsCallbacks[id] - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - encoder := json.NewEncoder(w) - for { - var stats docker.Stats - if callback != nil { - stats = callback(id) - } - encoder.Encode(stats) - if !stream { - break - } - } -} - -func (s *DockerServer) uploadToContainer(w http.ResponseWriter, r *http.Request) { - id := mux.Vars(r)["id"] - container, _, err := s.findContainer(id) - if err != nil { - http.Error(w, err.Error(), http.StatusNotFound) - return - } - if !container.State.Running { - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintf(w, "Container %s is not running", id) - return - } - path := r.URL.Query().Get("path") - s.uploadedFiles[id] = path - w.WriteHeader(http.StatusOK) -} - -func (s *DockerServer) topContainer(w http.ResponseWriter, r *http.Request) { - id := mux.Vars(r)["id"] - container, _, err := s.findContainer(id) - if err != nil { - http.Error(w, err.Error(), http.StatusNotFound) - return - } - if !container.State.Running { - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintf(w, "Container %s is not running", id) - return - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - result := docker.TopResult{ - Titles: []string{"UID", "PID", "PPID", "C", "STIME", "TTY", "TIME", "CMD"}, - Processes: [][]string{ - {"root", "7535", "7516", "0", "03:20", "?", "00:00:00", container.Path + " " + strings.Join(container.Args, " ")}, - }, - } - json.NewEncoder(w).Encode(result) -} - -func (s *DockerServer) startContainer(w http.ResponseWriter, r *http.Request) { - id := mux.Vars(r)["id"] - container, _, err := s.findContainer(id) - if err != nil { - http.Error(w, err.Error(), http.StatusNotFound) - return - } - s.cMut.Lock() - defer s.cMut.Unlock() - defer r.Body.Close() - var hostConfig docker.HostConfig - err = json.NewDecoder(r.Body).Decode(&hostConfig) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - container.HostConfig = &hostConfig - if len(hostConfig.PortBindings) > 0 { - ports := map[docker.Port][]docker.PortBinding{} - for key, items := range hostConfig.PortBindings { - bindings := make([]docker.PortBinding, len(items)) - for i := range items { - binding := docker.PortBinding{ - HostIP: items[i].HostIP, - HostPort: items[i].HostPort, - } - if binding.HostIP == "" { - binding.HostIP = "0.0.0.0" - } - if binding.HostPort == "" { - binding.HostPort = strconv.Itoa(mathrand.Int() % 0xffff) - } - bindings[i] = binding - } - ports[key] = bindings - } - container.NetworkSettings.Ports = ports - } - if container.State.Running { - http.Error(w, "", http.StatusNotModified) - return - } - container.State.Running = true - s.notify(container) -} - -func (s *DockerServer) stopContainer(w http.ResponseWriter, r *http.Request) { - id := mux.Vars(r)["id"] - container, _, err := s.findContainer(id) - if err != nil { - http.Error(w, err.Error(), http.StatusNotFound) - return - } - s.cMut.Lock() - defer s.cMut.Unlock() - if !container.State.Running { - http.Error(w, "Container not running", http.StatusBadRequest) - return - } - w.WriteHeader(http.StatusNoContent) - container.State.Running = false - s.notify(container) -} - -func (s *DockerServer) pauseContainer(w http.ResponseWriter, r *http.Request) { - id := mux.Vars(r)["id"] - container, _, err := s.findContainer(id) - if err != nil { - http.Error(w, err.Error(), http.StatusNotFound) - return - } - s.cMut.Lock() - defer s.cMut.Unlock() - if container.State.Paused { - http.Error(w, "Container already paused", http.StatusBadRequest) - return - } - w.WriteHeader(http.StatusNoContent) - container.State.Paused = true -} - -func (s *DockerServer) unpauseContainer(w http.ResponseWriter, r *http.Request) { - id := mux.Vars(r)["id"] - container, _, err := s.findContainer(id) - if err != nil { - http.Error(w, err.Error(), http.StatusNotFound) - return - } - s.cMut.Lock() - defer s.cMut.Unlock() - if !container.State.Paused { - http.Error(w, "Container not paused", http.StatusBadRequest) - return - } - w.WriteHeader(http.StatusNoContent) - container.State.Paused = false -} - -func (s *DockerServer) attachContainer(w http.ResponseWriter, r *http.Request) { - id := mux.Vars(r)["id"] - container, _, err := s.findContainer(id) - if err != nil { - http.Error(w, err.Error(), http.StatusNotFound) - return - } - hijacker, ok := w.(http.Hijacker) - if !ok { - http.Error(w, "cannot hijack connection", http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "application/vnd.docker.raw-stream") - w.WriteHeader(http.StatusOK) - conn, _, err := hijacker.Hijack() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - wg := sync.WaitGroup{} - if r.URL.Query().Get("stdin") == "1" { - wg.Add(1) - go func() { - ioutil.ReadAll(conn) - wg.Done() - }() - } - outStream := stdcopy.NewStdWriter(conn, stdcopy.Stdout) - if container.State.Running { - fmt.Fprintf(outStream, "Container is running\n") - } else { - fmt.Fprintf(outStream, "Container is not running\n") - } - fmt.Fprintln(outStream, "What happened?") - fmt.Fprintln(outStream, "Something happened") - wg.Wait() - if r.URL.Query().Get("stream") == "1" { - for { - time.Sleep(1e6) - s.cMut.RLock() - if !container.State.Running { - s.cMut.RUnlock() - break - } - s.cMut.RUnlock() - } - } - conn.Close() -} - -func (s *DockerServer) waitContainer(w http.ResponseWriter, r *http.Request) { - id := mux.Vars(r)["id"] - container, _, err := s.findContainer(id) - if err != nil { - http.Error(w, err.Error(), http.StatusNotFound) - return - } - for { - time.Sleep(1e6) - s.cMut.RLock() - if !container.State.Running { - s.cMut.RUnlock() - break - } - s.cMut.RUnlock() - } - result := map[string]int{"StatusCode": container.State.ExitCode} - json.NewEncoder(w).Encode(result) -} - -func (s *DockerServer) removeContainer(w http.ResponseWriter, r *http.Request) { - id := mux.Vars(r)["id"] - force := r.URL.Query().Get("force") - container, index, err := s.findContainer(id) - if err != nil { - http.Error(w, err.Error(), http.StatusNotFound) - return - } - if container.State.Running && force != "1" { - msg := "Error: API error (406): Impossible to remove a running container, please stop it first" - http.Error(w, msg, http.StatusInternalServerError) - return - } - w.WriteHeader(http.StatusNoContent) - s.cMut.Lock() - defer s.cMut.Unlock() - if s.containers[index].ID == id || s.containers[index].Name == id { - s.containers[index] = s.containers[len(s.containers)-1] - s.containers = s.containers[:len(s.containers)-1] - } -} - -func (s *DockerServer) commitContainer(w http.ResponseWriter, r *http.Request) { - id := r.URL.Query().Get("container") - container, _, err := s.findContainer(id) - if err != nil { - http.Error(w, err.Error(), http.StatusNotFound) - return - } - var config *docker.Config - runConfig := r.URL.Query().Get("run") - if runConfig != "" { - config = new(docker.Config) - err = json.Unmarshal([]byte(runConfig), config) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - } - w.WriteHeader(http.StatusOK) - image := docker.Image{ - ID: "img-" + container.ID, - Parent: container.Image, - Container: container.ID, - Comment: r.URL.Query().Get("m"), - Author: r.URL.Query().Get("author"), - Config: config, - } - repository := r.URL.Query().Get("repo") - tag := r.URL.Query().Get("tag") - s.iMut.Lock() - s.images = append(s.images, image) - if repository != "" { - if tag != "" { - repository += ":" + tag - } - s.imgIDs[repository] = image.ID - } - s.iMut.Unlock() - fmt.Fprintf(w, `{"ID":%q}`, image.ID) -} - -func (s *DockerServer) findContainer(idOrName string) (*docker.Container, int, error) { - s.cMut.RLock() - defer s.cMut.RUnlock() - for i, container := range s.containers { - if container.ID == idOrName || container.Name == idOrName { - return container, i, nil - } - } - return nil, -1, errors.New("No such container") -} - -func (s *DockerServer) buildImage(w http.ResponseWriter, r *http.Request) { - if ct := r.Header.Get("Content-Type"); ct == "application/tar" { - gotDockerFile := false - tr := tar.NewReader(r.Body) - for { - header, err := tr.Next() - if err != nil { - break - } - if header.Name == "Dockerfile" { - gotDockerFile = true - } - } - if !gotDockerFile { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("miss Dockerfile")) - return - } - } - //we did not use that Dockerfile to build image cause we are a fake Docker daemon - image := docker.Image{ - ID: s.generateID(), - Created: time.Now(), - } - - query := r.URL.Query() - repository := image.ID - if t := query.Get("t"); t != "" { - repository = t - } - s.iMut.Lock() - s.images = append(s.images, image) - s.imgIDs[repository] = image.ID - s.iMut.Unlock() - w.Write([]byte(fmt.Sprintf("Successfully built %s", image.ID))) -} - -func (s *DockerServer) pullImage(w http.ResponseWriter, r *http.Request) { - fromImageName := r.URL.Query().Get("fromImage") - tag := r.URL.Query().Get("tag") - image := docker.Image{ - ID: s.generateID(), - } - s.iMut.Lock() - s.images = append(s.images, image) - if fromImageName != "" { - if tag != "" { - fromImageName = fmt.Sprintf("%s:%s", fromImageName, tag) - } - s.imgIDs[fromImageName] = image.ID - } - s.iMut.Unlock() -} - -func (s *DockerServer) pushImage(w http.ResponseWriter, r *http.Request) { - name := mux.Vars(r)["name"] - tag := r.URL.Query().Get("tag") - if tag != "" { - name += ":" + tag - } - s.iMut.RLock() - if _, ok := s.imgIDs[name]; !ok { - s.iMut.RUnlock() - http.Error(w, "No such image", http.StatusNotFound) - return - } - s.iMut.RUnlock() - fmt.Fprintln(w, "Pushing...") - fmt.Fprintln(w, "Pushed") -} - -func (s *DockerServer) tagImage(w http.ResponseWriter, r *http.Request) { - name := mux.Vars(r)["name"] - s.iMut.RLock() - if _, ok := s.imgIDs[name]; !ok { - s.iMut.RUnlock() - http.Error(w, "No such image", http.StatusNotFound) - return - } - s.iMut.RUnlock() - s.iMut.Lock() - defer s.iMut.Unlock() - newRepo := r.URL.Query().Get("repo") - newTag := r.URL.Query().Get("tag") - if newTag != "" { - newRepo += ":" + newTag - } - s.imgIDs[newRepo] = s.imgIDs[name] - w.WriteHeader(http.StatusCreated) -} - -func (s *DockerServer) removeImage(w http.ResponseWriter, r *http.Request) { - id := mux.Vars(r)["id"] - s.iMut.RLock() - var tag string - if img, ok := s.imgIDs[id]; ok { - id, tag = img, id - } - var tags []string - for tag, taggedID := range s.imgIDs { - if taggedID == id { - tags = append(tags, tag) - } - } - s.iMut.RUnlock() - _, index, err := s.findImageByID(id) - if err != nil { - http.Error(w, err.Error(), http.StatusNotFound) - return - } - w.WriteHeader(http.StatusNoContent) - s.iMut.Lock() - defer s.iMut.Unlock() - if len(tags) < 2 { - s.images[index] = s.images[len(s.images)-1] - s.images = s.images[:len(s.images)-1] - } - if tag != "" { - delete(s.imgIDs, tag) - } -} - -func (s *DockerServer) inspectImage(w http.ResponseWriter, r *http.Request) { - name := mux.Vars(r)["name"] - s.iMut.RLock() - defer s.iMut.RUnlock() - if id, ok := s.imgIDs[name]; ok { - for _, img := range s.images { - if img.ID == id { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(img) - return - } - } - } - http.Error(w, "not found", http.StatusNotFound) -} - -func (s *DockerServer) listEvents(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - var events [][]byte - count := mathrand.Intn(20) - for i := 0; i < count; i++ { - data, err := json.Marshal(s.generateEvent()) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - events = append(events, data) - } - w.WriteHeader(http.StatusOK) - for _, d := range events { - fmt.Fprintln(w, d) - time.Sleep(time.Duration(mathrand.Intn(200)) * time.Millisecond) - } -} - -func (s *DockerServer) pingDocker(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) -} - -func (s *DockerServer) generateEvent() *docker.APIEvents { - var eventType string - switch mathrand.Intn(4) { - case 0: - eventType = "create" - case 1: - eventType = "start" - case 2: - eventType = "stop" - case 3: - eventType = "destroy" - } - return &docker.APIEvents{ - ID: s.generateID(), - Status: eventType, - From: "mybase:latest", - Time: time.Now().Unix(), - } -} - -func (s *DockerServer) loadImage(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) -} - -func (s *DockerServer) getImage(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Header().Set("Content-Type", "application/tar") -} - -func (s *DockerServer) createExecContainer(w http.ResponseWriter, r *http.Request) { - id := mux.Vars(r)["id"] - container, _, err := s.findContainer(id) - if err != nil { - http.Error(w, err.Error(), http.StatusNotFound) - return - } - - execID := s.generateID() - container.ExecIDs = append(container.ExecIDs, execID) - - exec := docker.ExecInspect{ - ID: execID, - Container: *container, - } - - var params docker.CreateExecOptions - err = json.NewDecoder(r.Body).Decode(¶ms) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - if len(params.Cmd) > 0 { - exec.ProcessConfig.EntryPoint = params.Cmd[0] - if len(params.Cmd) > 1 { - exec.ProcessConfig.Arguments = params.Cmd[1:] - } - } - - exec.ProcessConfig.User = params.User - exec.ProcessConfig.Tty = params.Tty - - s.execMut.Lock() - s.execs = append(s.execs, &exec) - s.execMut.Unlock() - w.WriteHeader(http.StatusOK) - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(map[string]string{"Id": exec.ID}) -} - -func (s *DockerServer) startExecContainer(w http.ResponseWriter, r *http.Request) { - id := mux.Vars(r)["id"] - if exec, err := s.getExec(id, false); err == nil { - s.execMut.Lock() - exec.Running = true - s.execMut.Unlock() - if callback, ok := s.execCallbacks[id]; ok { - callback() - delete(s.execCallbacks, id) - } else if callback, ok := s.execCallbacks["*"]; ok { - callback() - delete(s.execCallbacks, "*") - } - s.execMut.Lock() - exec.Running = false - s.execMut.Unlock() - w.WriteHeader(http.StatusOK) - return - } - w.WriteHeader(http.StatusNotFound) -} - -func (s *DockerServer) resizeExecContainer(w http.ResponseWriter, r *http.Request) { - id := mux.Vars(r)["id"] - if _, err := s.getExec(id, false); err == nil { - w.WriteHeader(http.StatusOK) - return - } - w.WriteHeader(http.StatusNotFound) -} - -func (s *DockerServer) inspectExecContainer(w http.ResponseWriter, r *http.Request) { - id := mux.Vars(r)["id"] - if exec, err := s.getExec(id, true); err == nil { - w.WriteHeader(http.StatusOK) - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(exec) - return - } - w.WriteHeader(http.StatusNotFound) -} - -func (s *DockerServer) getExec(id string, copy bool) (*docker.ExecInspect, error) { - s.execMut.RLock() - defer s.execMut.RUnlock() - for _, exec := range s.execs { - if exec.ID == id { - if copy { - cp := *exec - exec = &cp - } - return exec, nil - } - } - return nil, errors.New("exec not found") -} - -func (s *DockerServer) findNetwork(idOrName string) (*docker.Network, int, error) { - s.netMut.RLock() - defer s.netMut.RUnlock() - for i, network := range s.networks { - if network.ID == idOrName || network.Name == idOrName { - return network, i, nil - } - } - return nil, -1, errors.New("No such network") -} - -func (s *DockerServer) listNetworks(w http.ResponseWriter, r *http.Request) { - s.netMut.RLock() - result := make([]docker.Network, 0, len(s.networks)) - for _, network := range s.networks { - result = append(result, *network) - } - s.netMut.RUnlock() - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(result) -} - -func (s *DockerServer) networkInfo(w http.ResponseWriter, r *http.Request) { - id := mux.Vars(r)["id"] - network, _, err := s.findNetwork(id) - if err != nil { - http.Error(w, err.Error(), http.StatusNotFound) - return - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(network) -} - -// isValidName validates configuration objects supported by libnetwork -func isValidName(name string) bool { - if name == "" || strings.Contains(name, ".") { - return false - } - return true -} - -func (s *DockerServer) createNetwork(w http.ResponseWriter, r *http.Request) { - var config *docker.CreateNetworkOptions - defer r.Body.Close() - err := json.NewDecoder(r.Body).Decode(&config) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - if !isValidName(config.Name) { - http.Error(w, "Invalid network name", http.StatusBadRequest) - return - } - if n, _, _ := s.findNetwork(config.Name); n != nil { - http.Error(w, "network already exists", http.StatusForbidden) - return - } - - generatedID := s.generateID() - network := docker.Network{ - Name: config.Name, - ID: generatedID, - Driver: config.Driver, - } - s.netMut.Lock() - s.networks = append(s.networks, &network) - s.netMut.Unlock() - w.WriteHeader(http.StatusCreated) - var c = struct{ ID string }{ID: network.ID} - json.NewEncoder(w).Encode(c) -} - -func (s *DockerServer) listVolumes(w http.ResponseWriter, r *http.Request) { - s.volMut.RLock() - result := make([]docker.Volume, 0, len(s.volStore)) - for _, volumeCounter := range s.volStore { - result = append(result, volumeCounter.volume) - } - s.volMut.RUnlock() - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(result) -} - -func (s *DockerServer) createVolume(w http.ResponseWriter, r *http.Request) { - var data struct { - *docker.CreateVolumeOptions - } - defer r.Body.Close() - err := json.NewDecoder(r.Body).Decode(&data) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - volume := &docker.Volume{ - Name: data.CreateVolumeOptions.Name, - Driver: data.CreateVolumeOptions.Driver, - } - // If the name is not specified, generate one. Just using generateID for now - if len(volume.Name) == 0 { - volume.Name = s.generateID() - } - // If driver is not specified, use local - if len(volume.Driver) == 0 { - volume.Driver = "local" - } - // Mount point is a default one with name - volume.Mountpoint = "/var/lib/docker/volumes/" + volume.Name - - // If the volume already exists, don't re-add it. - exists := false - s.volMut.Lock() - if s.volStore != nil { - _, exists = s.volStore[volume.Name] - } else { - // No volumes, create volStore - s.volStore = make(map[string]*volumeCounter) - } - if !exists { - s.volStore[volume.Name] = &volumeCounter{ - volume: *volume, - count: 0, - } - } - s.volMut.Unlock() - w.WriteHeader(http.StatusCreated) - json.NewEncoder(w).Encode(volume) -} - -func (s *DockerServer) inspectVolume(w http.ResponseWriter, r *http.Request) { - s.volMut.RLock() - defer s.volMut.RUnlock() - name := mux.Vars(r)["name"] - vol, err := s.findVolume(name) - if err != nil { - http.Error(w, err.Error(), http.StatusNotFound) - return - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(vol.volume) -} - -func (s *DockerServer) findVolume(name string) (*volumeCounter, error) { - vol, ok := s.volStore[name] - if !ok { - return nil, errors.New("no such volume") - } - return vol, nil -} - -func (s *DockerServer) removeVolume(w http.ResponseWriter, r *http.Request) { - s.volMut.Lock() - defer s.volMut.Unlock() - name := mux.Vars(r)["name"] - vol, err := s.findVolume(name) - if err != nil { - http.Error(w, err.Error(), http.StatusNotFound) - return - } - if vol.count != 0 { - http.Error(w, "volume in use and cannot be removed", http.StatusConflict) - return - } - s.volStore[vol.volume.Name] = nil - w.WriteHeader(http.StatusNoContent) -} From 62249fe79f4add61438ead08ce926f9c7bdb0390 Mon Sep 17 00:00:00 2001 From: Diptanu Choudhury Date: Thu, 24 Mar 2016 19:00:24 -0700 Subject: [PATCH 10/25] Added an impl for Nomad Checks --- api/tasks.go | 3 +- client/client.go | 6 +- client/consul/check.go | 163 +++++++++----------------- client/consul/sync.go | 57 ++++----- client/driver/executor/checks.go | 24 ++-- client/driver/executor/checks_test.go | 21 ++++ client/driver/executor/executor.go | 2 + jobspec/parse.go | 2 + 8 files changed, 130 insertions(+), 148 deletions(-) diff --git a/api/tasks.go b/api/tasks.go index 3c18269eadb9..63ced39e469b 100644 --- a/api/tasks.go +++ b/api/tasks.go @@ -19,7 +19,8 @@ type ServiceCheck struct { Id string Name string Type string - Script string + Cmd string + Args []string Path string Protocol string Interval time.Duration diff --git a/client/client.go b/client/client.go index cf1dbbf76bba..25472c3428c0 100644 --- a/client/client.go +++ b/client/client.go @@ -1204,9 +1204,9 @@ func (c *Client) syncConsul() { } } } - if err := c.consulService.KeepServices(runningTasks); err != nil { - c.logger.Printf("[DEBUG] client: error removing services from non-running tasks: %v", err) - } + //if err := c.consulService.KeepServices(runningTasks); err != nil { + // c.logger.Printf("[DEBUG] client: error removing services from non-running tasks: %v", err) + //} case <-c.shutdownCh: c.logger.Printf("[INFO] client: shutting down consul sync") return diff --git a/client/consul/check.go b/client/consul/check.go index 37516b23da4b..d39dbce17090 100644 --- a/client/consul/check.go +++ b/client/consul/check.go @@ -1,131 +1,84 @@ package consul import ( - "container/heap" - "fmt" + "log" + "math/rand" + "sync" "time" cstructs "github.com/hashicorp/nomad/client/driver/structs" ) -type Check interface { - Run() *cstructs.CheckResult - ID() string -} - -type consulCheck struct { - check Check - next time.Time - index int -} - -type checkHeap struct { - index map[string]*consulCheck - heap checksHeapImp -} - -func NewConsulChecksHeap() *checkHeap { - return &checkHeap{ - index: make(map[string]*consulCheck), - heap: make(checksHeapImp, 0), - } -} - -func (c *checkHeap) Push(check Check, next time.Time) error { - if _, ok := c.index[check.ID()]; ok { - return fmt.Errorf("check %v already exists", check.ID()) - } - - cCheck := &consulCheck{check, next, 0} - - c.index[check.ID()] = cCheck - heap.Push(&c.heap, cCheck) - return nil -} - -func (c *checkHeap) Pop() *consulCheck { - if len(c.heap) == 0 { - return nil - } +type NomadCheck struct { + check Check + runCheck func(Check) + logger *log.Logger + stop bool + stopCh chan struct{} + stopLock sync.Mutex - cCheck := heap.Pop(&c.heap).(*consulCheck) - delete(c.index, cCheck.check.ID()) - return cCheck + started bool + startedLock sync.Mutex } -func (c *checkHeap) Peek() *consulCheck { - if len(c.heap) == 0 { - return nil +func NewNomadCheck(check Check, runCheck func(Check), logger *log.Logger) *NomadCheck { + nc := NomadCheck{ + check: check, + runCheck: runCheck, + logger: logger, + stopCh: make(chan struct{}), } - return c.heap[0] -} - -func (c *checkHeap) Contains(check Check) bool { - _, ok := c.index[check.ID()] - return ok + return &nc } -func (c *checkHeap) Update(check Check, next time.Time) error { - if cCheck, ok := c.index[check.ID()]; ok { - cCheck.check = check - cCheck.next = next - heap.Fix(&c.heap, cCheck.index) - return nil +// Start is used to start a check monitor. Monitor runs until stop is called +func (n *NomadCheck) Start() { + n.startedLock.Lock() + if n.started { + return } - - return fmt.Errorf("heap doesn't contain check %v", check.ID()) + n.started = true + n.stopLock.Lock() + defer n.stopLock.Unlock() + n.stop = false + n.stopCh = make(chan struct{}) + go n.run() } -func (c *checkHeap) Remove(id string) error { - if cCheck, ok := c.index[id]; ok { - heap.Remove(&c.heap, cCheck.index) - delete(c.index, id) - return nil +// Stop is used to stop a check monitor. +func (n *NomadCheck) Stop() { + n.stopLock.Lock() + defer n.stopLock.Unlock() + if !n.stop { + n.stop = true + close(n.stopCh) } - return fmt.Errorf("heap doesn't contain check %v", id) } -func (c *checkHeap) Len() int { return len(c.heap) } - -type checksHeapImp []*consulCheck - -func (h checksHeapImp) Len() int { return len(h) } - -func (h checksHeapImp) Less(i, j int) bool { - // Two zero times should return false. - // Otherwise, zero is "greater" than any other time. - // (To sort it at the end of the list.) - // Sort such that zero times are at the end of the list. - iZero, jZero := h[i].next.IsZero(), h[j].next.IsZero() - if iZero && jZero { - return false - } else if iZero { - return false - } else if jZero { - return true +// run is invoked by a goroutine to run until Stop() is called +func (n *NomadCheck) run() { + // Get the randomized initial pause time + initialPauseTime := randomStagger(n.check.Interval()) + n.logger.Printf("[DEBUG] agent: pausing %v before first invocation of %s", initialPauseTime, n.check.ID()) + next := time.After(initialPauseTime) + for { + select { + case <-next: + n.runCheck(n.check) + next = time.After(n.check.Interval()) + case <-n.stopCh: + return + } } - - return h[i].next.Before(h[j].next) -} - -func (h checksHeapImp) Swap(i, j int) { - h[i], h[j] = h[j], h[i] - h[i].index = i - h[j].index = j } -func (h *checksHeapImp) Push(x interface{}) { - n := len(*h) - check := x.(*consulCheck) - check.index = n - *h = append(*h, check) +type Check interface { + Run() *cstructs.CheckResult + ID() string + Interval() time.Duration } -func (h *checksHeapImp) Pop() interface{} { - old := *h - n := len(old) - check := old[n-1] - check.index = -1 // for safety - *h = old[0 : n-1] - return check +// Returns a random stagger interval between 0 and the duration +func randomStagger(intv time.Duration) time.Duration { + return time.Duration(uint64(rand.Int63()) % uint64(intv)) } diff --git a/client/consul/sync.go b/client/consul/sync.go index fb34aa79d4c1..2418ecda2694 100644 --- a/client/consul/sync.go +++ b/client/consul/sync.go @@ -28,11 +28,10 @@ type ConsulService struct { trackedServices map[string]*consul.AgentService trackedChecks map[string]*consul.AgentCheckRegistration - execChecks *checkHeap + nomadChecks map[string]*NomadCheck logger *log.Logger - updateCh chan struct{} shutdownCh chan struct{} shutdown bool shutdownLock sync.Mutex @@ -97,10 +96,9 @@ func NewConsulService(config *ConsulConfig, logger *log.Logger, allocID string) logger: logger, trackedServices: make(map[string]*consul.AgentService), trackedChecks: make(map[string]*consul.AgentCheckRegistration), - execChecks: NewConsulChecksHeap(), + nomadChecks: make(map[string]*NomadCheck), shutdownCh: make(chan struct{}), - updateCh: make(chan struct{}), } return &consulService, nil } @@ -199,6 +197,9 @@ func (c *ConsulService) KeepServices(tasks []*structs.Task) error { // Indexing the services in the tasks for _, task := range tasks { for _, service := range task.Services { + fmt.Printf("DIPTANU SERVICE %#v\n", service) + fmt.Printf("DIPTANU TASK %#v\n", c.task) + fmt.Printf("DIPTANU SERVICES %#v\n", services) services[service.ID(c.allocID, c.task.Name)] = struct{}{} } } @@ -223,6 +224,9 @@ func (c *ConsulService) KeepServices(tasks []*structs.Task) error { // registerCheck registers a check definition with Consul func (c *ConsulService) registerCheck(chkReg *consul.AgentCheckRegistration) error { + if nc, ok := c.nomadChecks[chkReg.ID]; ok { + nc.Start() + } return c.client.Agent().CheckRegister(chkReg) } @@ -257,13 +261,8 @@ func (c *ConsulService) createCheckReg(check *structs.ServiceCheck, service *con if err != nil { return nil, err } - if err := c.execChecks.Push(chk, time.Now().Add(check.Interval)); err != nil { - c.logger.Printf("[ERR] consulservice: unable to add check %q to heap", chk.ID()) - } - select { - case c.updateCh <- struct{}{}: - default: - } + nc := NewNomadCheck(chk, c.runCheck, c.logger) + c.nomadChecks[chk.ID()] = nc } return &chkReg, nil } @@ -307,31 +306,27 @@ func (c *ConsulService) deregisterService(ID string) error { // deregisterCheck de-registers a check with a given ID from Consul. func (c *ConsulService) deregisterCheck(ID string) error { - if err := c.execChecks.Remove(ID); err != nil { - c.logger.Printf("[DEBUG] consulservice: unable to remove check with ID %q from heap", ID) + // Deleting the nomad check + if nc, ok := c.nomadChecks[ID]; ok { + nc.Stop() + delete(c.nomadChecks, ID) } + + // Deleteting from consul return c.client.Agent().CheckDeregister(ID) } // PeriodicSync triggers periodic syncing of services and checks with Consul. // This is a long lived go-routine which is stopped during shutdown func (c *ConsulService) PeriodicSync() { - var runCheck <-chan time.Time sync := time.After(syncInterval) for { - runCheck = c.sleepBeforeRunningCheck() select { case <-sync: if err := c.performSync(); err != nil { c.logger.Printf("[DEBUG] consul: error in syncing task %q: %v", c.task.Name, err) } sync = time.After(syncInterval) - case <-c.updateCh: - continue - case <-runCheck: - chk := c.execChecks.heap.Pop().(consulCheck) - runCheck = c.sleepBeforeRunningCheck() - c.runCheck(chk.check) case <-c.shutdownCh: c.logger.Printf("[INFO] consul: shutting down sync for task %q", c.task.Name) return @@ -339,13 +334,6 @@ func (c *ConsulService) PeriodicSync() { } } -func (c *ConsulService) sleepBeforeRunningCheck() <-chan time.Time { - if c := c.execChecks.Peek(); c != nil { - return time.After(time.Now().Sub(c.next)) - } - return nil -} - // performSync sync the services and checks we are tracking with Consul. func (c *ConsulService) performSync() error { var mErr multierror.Error @@ -411,14 +399,19 @@ func (c *ConsulService) consulPresent() bool { // runCheck runs a check and updates the corresponding ttl check in consul func (c *ConsulService) runCheck(check Check) { res := check.Run() + state := consul.HealthCritical + output := res.Output if res.Err != nil { - c.client.Agent().UpdateTTL(check.ID(), res.Output, consul.HealthCritical) + output = res.Err.Error() } if res.ExitCode == 0 { - c.client.Agent().UpdateTTL(check.ID(), res.Output, consul.HealthPassing) + state = consul.HealthPassing } if res.ExitCode == 1 { - c.client.Agent().UpdateTTL(check.ID(), res.Output, consul.HealthWarning) + state = consul.HealthWarning + } + + if err := c.client.Agent().UpdateTTL(check.ID(), output, state); err != nil { + c.logger.Printf("[DEBUG] error updating ttl check for check %q: %v", check.ID(), err) } - c.client.Agent().UpdateTTL(check.ID(), res.Output, consul.HealthCritical) } diff --git a/client/driver/executor/checks.go b/client/driver/executor/checks.go index 40cf3681dd74..ee84a18c5b27 100644 --- a/client/driver/executor/checks.go +++ b/client/driver/executor/checks.go @@ -22,6 +22,7 @@ var ( type DockerScriptCheck struct { id string + interval time.Duration containerID string logger *log.Logger cmd string @@ -42,16 +43,16 @@ func (d *DockerScriptCheck) dockerClient() (*docker.Client, error) { createClient.Do(func() { if d.dockerEndpoint != "" { if d.tlsCert+d.tlsKey+d.tlsCa != "" { - d.logger.Printf("[DEBUG] driver.docker: using TLS client connection to %s", d.dockerEndpoint) + d.logger.Printf("[DEBUG] executor.checks: using TLS client connection to %s", d.dockerEndpoint) client, err = docker.NewTLSClient(d.dockerEndpoint, d.tlsCert, d.tlsKey, d.tlsCa) } else { - d.logger.Printf("[DEBUG] driver.docker: using standard client connection to %s", d.dockerEndpoint) + d.logger.Printf("[DEBUG] executor.checks: using standard client connection to %s", d.dockerEndpoint) client, err = docker.NewClient(d.dockerEndpoint) } return } - d.logger.Println("[DEBUG] driver.docker: using client connection initialized from environment") + d.logger.Println("[DEBUG] executor.checks: using client connection initialized from environment") client, err = docker.NewClientFromEnv() }) return client, err @@ -106,11 +107,16 @@ func (d *DockerScriptCheck) ID() string { return d.id } +func (d *DockerScriptCheck) Interval() time.Duration { + return d.interval +} + type ExecScriptCheck struct { - id string - cmd string - args []string - taskDir string + id string + interval time.Duration + cmd string + args []string + taskDir string FSIsolation bool } @@ -152,3 +158,7 @@ func (e *ExecScriptCheck) Run() *cstructs.CheckResult { func (e *ExecScriptCheck) ID() string { return e.id } + +func (e *ExecScriptCheck) Interval() time.Duration { + return e.interval +} diff --git a/client/driver/executor/checks_test.go b/client/driver/executor/checks_test.go index 15f49265ad3a..9526b6330a22 100644 --- a/client/driver/executor/checks_test.go +++ b/client/driver/executor/checks_test.go @@ -3,8 +3,29 @@ package executor import ( "strings" "testing" + + docker "github.com/fsouza/go-dockerclient" ) +// dockerIsConnected checks to see if a docker daemon is available (local or remote) +func dockerIsConnected(t *testing.T) bool { + client, err := docker.NewClientFromEnv() + if err != nil { + return false + } + + // Creating a client doesn't actually connect, so make sure we do something + // like call Version() on it. + env, err := client.Version() + if err != nil { + t.Logf("Failed to connect to docker daemon: %s", err) + return false + } + + t.Logf("Successfully connected to docker daemon running version %s", env.Get("Version")) + return true +} + func TestExecScriptCheckNoIsolation(t *testing.T) { check := &ExecScriptCheck{ id: "foo", diff --git a/client/driver/executor/executor.go b/client/driver/executor/executor.go index 0f98f18daac8..4089632fe917 100644 --- a/client/driver/executor/executor.go +++ b/client/driver/executor/executor.go @@ -515,6 +515,7 @@ func (e *UniversalExecutor) createCheck(check *structs.ServiceCheck, checkID str if check.Type == structs.ServiceCheckScript && e.ctx.Driver == "docker" { return &DockerScriptCheck{ id: checkID, + interval: check.Interval, containerID: e.consulCtx.ContainerID, logger: e.logger, cmd: check.Cmd, @@ -525,6 +526,7 @@ func (e *UniversalExecutor) createCheck(check *structs.ServiceCheck, checkID str if check.Type == structs.ServiceCheckScript && e.ctx.Driver == "exec" { return &ExecScriptCheck{ id: checkID, + interval: check.Interval, cmd: check.Cmd, args: check.Args, taskDir: e.taskDir, diff --git a/jobspec/parse.go b/jobspec/parse.go index c94950428039..acc9cc4af461 100644 --- a/jobspec/parse.go +++ b/jobspec/parse.go @@ -750,6 +750,8 @@ func parseChecks(service *structs.Service, checkObjs *ast.ObjectList) error { "timeout", "path", "protocol", + "cmd", + "args", } if err := checkHCLKeys(co.Val, valid); err != nil { return multierror.Prefix(err, "check ->") From 12dc439949144be1e6b4c4de1e2c76b646c0d90d Mon Sep 17 00:00:00 2001 From: Diptanu Choudhury Date: Thu, 24 Mar 2016 19:19:13 -0700 Subject: [PATCH 11/25] Changing the logic of keep services --- client/client.go | 13 ++++++++----- client/consul/sync.go | 20 ++++++++------------ 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/client/client.go b/client/client.go index 25472c3428c0..6f1c953f28eb 100644 --- a/client/client.go +++ b/client/client.go @@ -1184,7 +1184,7 @@ func (c *Client) syncConsul() { for { select { case <-sync: - var runningTasks []*structs.Task + services := make(map[string]struct{}) // Get the existing allocs c.allocLock.RLock() allocs := make([]*AllocRunner, 0, len(c.allocs)) @@ -1199,14 +1199,17 @@ func (c *Client) syncConsul() { for taskName, taskState := range taskStates { if taskState.State == structs.TaskStateRunning { if tr, ok := ar.tasks[taskName]; ok { - runningTasks = append(runningTasks, tr.task) + for _, service := range tr.task.Services { + services[service.ID(ar.alloc.ID, tr.task.Name)] = struct{}{} + } } } } } - //if err := c.consulService.KeepServices(runningTasks); err != nil { - // c.logger.Printf("[DEBUG] client: error removing services from non-running tasks: %v", err) - //} + if err := c.consulService.KeepServices(services); err != nil { + c.logger.Printf("[DEBUG] client: error removing services from non-running tasks: %v", err) + } + sync = time.After(consulSyncInterval) case <-c.shutdownCh: c.logger.Printf("[INFO] client: shutting down consul sync") return diff --git a/client/consul/sync.go b/client/consul/sync.go index 2418ecda2694..85f97f3a24bc 100644 --- a/client/consul/sync.go +++ b/client/consul/sync.go @@ -180,6 +180,13 @@ func (c *ConsulService) Shutdown() error { c.shutdown = true } c.shutdownLock.Unlock() + + // Stop all the checks that nomad is running + for _, nc := range c.nomadChecks { + nc.Stop() + } + + // de-register all the services from consul for _, service := range c.trackedServices { if err := c.client.Agent().ServiceDeregister(service.ID); err != nil { mErr.Errors = append(mErr.Errors, err) @@ -190,19 +197,8 @@ func (c *ConsulService) Shutdown() error { // KeepServices removes services from consul which are not present in the list // of tasks passed to it -func (c *ConsulService) KeepServices(tasks []*structs.Task) error { +func (c *ConsulService) KeepServices(services map[string]struct{}) error { var mErr multierror.Error - services := make(map[string]struct{}) - - // Indexing the services in the tasks - for _, task := range tasks { - for _, service := range task.Services { - fmt.Printf("DIPTANU SERVICE %#v\n", service) - fmt.Printf("DIPTANU TASK %#v\n", c.task) - fmt.Printf("DIPTANU SERVICES %#v\n", services) - services[service.ID(c.allocID, c.task.Name)] = struct{}{} - } - } // Get the services from Consul cServices, err := c.client.Agent().Services() From 52f7f93a0998e0d2e32dd1ee4fbcf868cd0e12e7 Mon Sep 17 00:00:00 2001 From: Diptanu Choudhury Date: Thu, 24 Mar 2016 19:30:02 -0700 Subject: [PATCH 12/25] Added some docs --- client/consul/check.go | 9 ++++++--- client/consul/sync.go | 12 +++++++++++- client/driver/executor/checks.go | 10 ++++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/client/consul/check.go b/client/consul/check.go index d39dbce17090..fa8ea3b0368f 100644 --- a/client/consul/check.go +++ b/client/consul/check.go @@ -9,6 +9,8 @@ import ( cstructs "github.com/hashicorp/nomad/client/driver/structs" ) +// NomadCheck runs a given check in a specific interval and update a +// corresponding Consul TTL check type NomadCheck struct { check Check runCheck func(Check) @@ -21,6 +23,7 @@ type NomadCheck struct { startedLock sync.Mutex } +// NewNomadCheck configures and returns a NomadCheck func NewNomadCheck(check Check, runCheck func(Check), logger *log.Logger) *NomadCheck { nc := NomadCheck{ check: check, @@ -31,7 +34,7 @@ func NewNomadCheck(check Check, runCheck func(Check), logger *log.Logger) *Nomad return &nc } -// Start is used to start a check monitor. Monitor runs until stop is called +// Start is used to start the check. The check runs until stop is called func (n *NomadCheck) Start() { n.startedLock.Lock() if n.started { @@ -40,12 +43,11 @@ func (n *NomadCheck) Start() { n.started = true n.stopLock.Lock() defer n.stopLock.Unlock() - n.stop = false n.stopCh = make(chan struct{}) go n.run() } -// Stop is used to stop a check monitor. +// Stop is used to stop the check. func (n *NomadCheck) Stop() { n.stopLock.Lock() defer n.stopLock.Unlock() @@ -72,6 +74,7 @@ func (n *NomadCheck) run() { } } +// Check is an interface which check providers can implement for Nomad to run type Check interface { Run() *cstructs.CheckResult ID() string diff --git a/client/consul/sync.go b/client/consul/sync.go index 85f97f3a24bc..a88dc74fc5d5 100644 --- a/client/consul/sync.go +++ b/client/consul/sync.go @@ -49,6 +49,10 @@ type ConsulConfig struct { const ( // The periodic time interval for syncing services and checks with Consul syncInterval = 5 * time.Second + + // ttlCheckBuffer is the time interval that Nomad can take to report Consul + // the check result + ttlCheckBuffer = 31 * time.Second ) // NewConsulService returns a new ConsulService @@ -103,6 +107,8 @@ func NewConsulService(config *ConsulConfig, logger *log.Logger, allocID string) return &consulService, nil } +// SetDelegatedChecks sets the checks that nomad is going to run and report the +// result back to consul func (c *ConsulService) SetDelegatedChecks(delegateChecks map[string]struct{}, createCheck func(*structs.ServiceCheck, string) (Check, error)) *ConsulService { c.delegateChecks = delegateChecks c.createCheck = createCheck @@ -226,6 +232,8 @@ func (c *ConsulService) registerCheck(chkReg *consul.AgentCheckRegistration) err return c.client.Agent().CheckRegister(chkReg) } +// createCheckReg creates a Check that can be registered with Nomad. It also +// creates a Nomad check for the check types that it can handle. func (c *ConsulService) createCheckReg(check *structs.ServiceCheck, service *consul.AgentService) (*consul.AgentCheckRegistration, error) { chkReg := consul.AgentCheckRegistration{ ID: check.Hash(service.ID), @@ -248,10 +256,12 @@ func (c *ConsulService) createCheckReg(check *structs.ServiceCheck, service *con case structs.ServiceCheckTCP: chkReg.TCP = fmt.Sprintf("%s:%d", service.Address, service.Port) case structs.ServiceCheckScript: - chkReg.TTL = (check.Interval + 31*time.Second).String() + chkReg.TTL = (check.Interval + ttlCheckBuffer).String() default: return nil, fmt.Errorf("check type %q not valid", check.Type) } + + // creating a nomad check if we have to handle this particular check type if _, ok := c.delegateChecks[check.Type]; ok { chk, err := c.createCheck(check, chkReg.ID) if err != nil { diff --git a/client/driver/executor/checks.go b/client/driver/executor/checks.go index ee84a18c5b27..faa33dbb82d5 100644 --- a/client/driver/executor/checks.go +++ b/client/driver/executor/checks.go @@ -20,6 +20,8 @@ var ( client *docker.Client ) +// DockerScriptCheck runs nagios compatible scripts in a docker container and +// provides the check result type DockerScriptCheck struct { id string interval time.Duration @@ -34,6 +36,7 @@ type DockerScriptCheck struct { tlsKey string } +// dockerClient creates the client to interact with the docker daemon func (d *DockerScriptCheck) dockerClient() (*docker.Client, error) { if client != nil { return client, nil @@ -58,6 +61,7 @@ func (d *DockerScriptCheck) dockerClient() (*docker.Client, error) { return client, err } +// Run runs a script check inside a docker container func (d *DockerScriptCheck) Run() *cstructs.CheckResult { var ( exec *docker.Exec @@ -103,14 +107,17 @@ func (d *DockerScriptCheck) Run() *cstructs.CheckResult { } } +// ID returns the check id func (d *DockerScriptCheck) ID() string { return d.id } +// Interval returns the interval at which the check has to run func (d *DockerScriptCheck) Interval() time.Duration { return d.interval } +// ExecScriptCheck runs a nagios compatible script and returns the check result type ExecScriptCheck struct { id string interval time.Duration @@ -121,6 +128,7 @@ type ExecScriptCheck struct { FSIsolation bool } +// Run runs an exec script check func (e *ExecScriptCheck) Run() *cstructs.CheckResult { buf, _ := circbuf.NewBuffer(int64(cstructs.CheckBufSize)) cmd := exec.Command(e.cmd, e.args...) @@ -155,10 +163,12 @@ func (e *ExecScriptCheck) Run() *cstructs.CheckResult { return nil } +// ID returns the check id func (e *ExecScriptCheck) ID() string { return e.id } +// Interval returns the interval at which the check has to run func (e *ExecScriptCheck) Interval() time.Duration { return e.interval } From 0fed52cb151b789e6e370ea79ec1aa1277456d58 Mon Sep 17 00:00:00 2001 From: Diptanu Choudhury Date: Thu, 24 Mar 2016 19:31:24 -0700 Subject: [PATCH 13/25] Removing non relevant tests --- client/consul/checks_test.go | 37 ------------------------------------ 1 file changed, 37 deletions(-) delete mode 100644 client/consul/checks_test.go diff --git a/client/consul/checks_test.go b/client/consul/checks_test.go deleted file mode 100644 index 6354084f94f8..000000000000 --- a/client/consul/checks_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package consul - -import ( - "reflect" - "testing" - "time" -) - -func TestCheckHeapOrder(t *testing.T) { - h := NewConsulChecksHeap() - - c1 := ExecScriptCheck{id: "a"} - c2 := ExecScriptCheck{id: "b"} - c3 := ExecScriptCheck{id: "c"} - - lookup := map[Check]string{ - &c1: "c1", - &c2: "c2", - &c3: "c3", - } - - h.Push(&c1, time.Time{}) - h.Push(&c2, time.Unix(10, 0)) - h.Push(&c3, time.Unix(11, 0)) - - expected := []string{"c2", "c3", "c1"} - var actual []string - for i := 0; i < 3; i++ { - cCheck := h.Pop() - - actual = append(actual, lookup[cCheck.check]) - } - - if !reflect.DeepEqual(actual, expected) { - t.Fatalf("Wrong ordering; got %v; want %v", actual, expected) - } -} From 9a27bf4c310b4c55faced2a8ad285770a4bec6a2 Mon Sep 17 00:00:00 2001 From: Diptanu Choudhury Date: Thu, 24 Mar 2016 19:34:22 -0700 Subject: [PATCH 14/25] Added some more docs to the executor --- client/driver/executor/executor.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/driver/executor/executor.go b/client/driver/executor/executor.go index 4089632fe917..383b33db807b 100644 --- a/client/driver/executor/executor.go +++ b/client/driver/executor/executor.go @@ -504,6 +504,8 @@ func (e *UniversalExecutor) listenerUnix() (net.Listener, error) { return net.Listen("unix", path) } +// createCheckMap creates a map of checks that the executor will handle on it's +// own func (e *UniversalExecutor) createCheckMap() map[string]struct{} { checks := map[string]struct{}{ "script": struct{}{}, @@ -511,6 +513,7 @@ func (e *UniversalExecutor) createCheckMap() map[string]struct{} { return checks } +// createCheck creates NomadCheck from a ServiceCheck func (e *UniversalExecutor) createCheck(check *structs.ServiceCheck, checkID string) (consul.Check, error) { if check.Type == structs.ServiceCheckScript && e.ctx.Driver == "docker" { return &DockerScriptCheck{ From e866d3be7cc500d0bea6ea3c8ac20f8b1c1373d5 Mon Sep 17 00:00:00 2001 From: Diptanu Choudhury Date: Thu, 24 Mar 2016 21:05:05 -0700 Subject: [PATCH 15/25] Vendoring circbuf --- Godeps/Godeps.json | 4 + vendor/github.com/armon/circbuf/.gitignore | 22 ++++++ vendor/github.com/armon/circbuf/LICENSE | 20 +++++ vendor/github.com/armon/circbuf/README.md | 28 +++++++ vendor/github.com/armon/circbuf/circbuf.go | 92 ++++++++++++++++++++++ 5 files changed, 166 insertions(+) create mode 100644 vendor/github.com/armon/circbuf/.gitignore create mode 100644 vendor/github.com/armon/circbuf/LICENSE create mode 100644 vendor/github.com/armon/circbuf/README.md create mode 100644 vendor/github.com/armon/circbuf/circbuf.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index f66d38a3110a..d412582c3487 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -6,6 +6,10 @@ "ImportPath": "github.com/StackExchange/wmi", "Rev": "f3e2bae1e0cb5aef83e319133eabfee30013a4a5" }, + { + "ImportPath": "github.com/armon/circbuf", + "Rev": "bbbad097214e2918d8543d5201d12bfd7bca254d" + }, { "ImportPath": "github.com/armon/go-metrics", "Rev": "06b60999766278efd6d2b5d8418a58c3d5b99e87" diff --git a/vendor/github.com/armon/circbuf/.gitignore b/vendor/github.com/armon/circbuf/.gitignore new file mode 100644 index 000000000000..00268614f045 --- /dev/null +++ b/vendor/github.com/armon/circbuf/.gitignore @@ -0,0 +1,22 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe diff --git a/vendor/github.com/armon/circbuf/LICENSE b/vendor/github.com/armon/circbuf/LICENSE new file mode 100644 index 000000000000..106569e542b0 --- /dev/null +++ b/vendor/github.com/armon/circbuf/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 Armon Dadgar + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/armon/circbuf/README.md b/vendor/github.com/armon/circbuf/README.md new file mode 100644 index 000000000000..f2e356b8d7a0 --- /dev/null +++ b/vendor/github.com/armon/circbuf/README.md @@ -0,0 +1,28 @@ +circbuf +======= + +This repository provides the `circbuf` package. This provides a `Buffer` object +which is a circular (or ring) buffer. It has a fixed size, but can be written +to infinitely. Only the last `size` bytes are ever retained. The buffer implements +the `io.Writer` interface. + +Documentation +============= + +Full documentation can be found on [Godoc](http://godoc.org/github.com/armon/circbuf) + +Usage +===== + +The `circbuf` package is very easy to use: + +```go +buf, _ := NewBuffer(6) +buf.Write([]byte("hello world")) + +if string(buf.Bytes()) != " world" { + panic("should only have last 6 bytes!") +} + +``` + diff --git a/vendor/github.com/armon/circbuf/circbuf.go b/vendor/github.com/armon/circbuf/circbuf.go new file mode 100644 index 000000000000..de3cb94a3906 --- /dev/null +++ b/vendor/github.com/armon/circbuf/circbuf.go @@ -0,0 +1,92 @@ +package circbuf + +import ( + "fmt" +) + +// Buffer implements a circular buffer. It is a fixed size, +// and new writes overwrite older data, such that for a buffer +// of size N, for any amount of writes, only the last N bytes +// are retained. +type Buffer struct { + data []byte + size int64 + writeCursor int64 + written int64 +} + +// NewBuffer creates a new buffer of a given size. The size +// must be greater than 0. +func NewBuffer(size int64) (*Buffer, error) { + if size <= 0 { + return nil, fmt.Errorf("Size must be positive") + } + + b := &Buffer{ + size: size, + data: make([]byte, size), + } + return b, nil +} + +// Write writes up to len(buf) bytes to the internal ring, +// overriding older data if necessary. +func (b *Buffer) Write(buf []byte) (int, error) { + // Account for total bytes written + n := len(buf) + b.written += int64(n) + + // If the buffer is larger than ours, then we only care + // about the last size bytes anyways + if int64(n) > b.size { + buf = buf[int64(n)-b.size:] + } + + // Copy in place + remain := b.size - b.writeCursor + copy(b.data[b.writeCursor:], buf) + if int64(len(buf)) > remain { + copy(b.data, buf[remain:]) + } + + // Update location of the cursor + b.writeCursor = ((b.writeCursor + int64(len(buf))) % b.size) + return n, nil +} + +// Size returns the size of the buffer +func (b *Buffer) Size() int64 { + return b.size +} + +// TotalWritten provides the total number of bytes written +func (b *Buffer) TotalWritten() int64 { + return b.written +} + +// Bytes provides a slice of the bytes written. This +// slice should not be written to. +func (b *Buffer) Bytes() []byte { + switch { + case b.written >= b.size && b.writeCursor == 0: + return b.data + case b.written > b.size: + out := make([]byte, b.size) + copy(out, b.data[b.writeCursor:]) + copy(out[b.size-b.writeCursor:], b.data[:b.writeCursor]) + return out + default: + return b.data[:b.writeCursor] + } +} + +// Reset resets the buffer so it has no content. +func (b *Buffer) Reset() { + b.writeCursor = 0 + b.written = 0 +} + +// String returns the contents of the buffer as a string +func (b *Buffer) String() string { + return string(b.Bytes()) +} From 7454ffd88b5b5538e5f3e0815b83d149023736c2 Mon Sep 17 00:00:00 2001 From: Diptanu Choudhury Date: Thu, 24 Mar 2016 21:17:33 -0700 Subject: [PATCH 16/25] Renamed NomadChecks to CheckRunner --- client/consul/check.go | 48 +++++++++++++++++++++--------------------- client/consul/sync.go | 20 +++++++++--------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/client/consul/check.go b/client/consul/check.go index fa8ea3b0368f..1740bed24c27 100644 --- a/client/consul/check.go +++ b/client/consul/check.go @@ -9,9 +9,9 @@ import ( cstructs "github.com/hashicorp/nomad/client/driver/structs" ) -// NomadCheck runs a given check in a specific interval and update a +// CheckRunner runs a given check in a specific interval and update a // corresponding Consul TTL check -type NomadCheck struct { +type CheckRunner struct { check Check runCheck func(Check) logger *log.Logger @@ -23,8 +23,8 @@ type NomadCheck struct { startedLock sync.Mutex } -// NewNomadCheck configures and returns a NomadCheck -func NewNomadCheck(check Check, runCheck func(Check), logger *log.Logger) *NomadCheck { +// NewCheckRunner configures and returns a CheckRunner +func NewCheckRunner(check Check, runCheck func(Check), logger *log.Logger) *CheckRunner { nc := NomadCheck{ check: check, runCheck: runCheck, @@ -35,40 +35,40 @@ func NewNomadCheck(check Check, runCheck func(Check), logger *log.Logger) *Nomad } // Start is used to start the check. The check runs until stop is called -func (n *NomadCheck) Start() { - n.startedLock.Lock() - if n.started { +func (r *CheckRunner) Start() { + r.startedLock.Lock() + if r.started { return } - n.started = true - n.stopLock.Lock() - defer n.stopLock.Unlock() - n.stopCh = make(chan struct{}) - go n.run() + r.started = true + r.stopLock.Lock() + defer r.stopLock.Unlock() + r.stopCh = make(chan struct{}) + go r.run() } // Stop is used to stop the check. -func (n *NomadCheck) Stop() { - n.stopLock.Lock() - defer n.stopLock.Unlock() - if !n.stop { - n.stop = true - close(n.stopCh) +func (r *CheckRunner) Stop() { + r.stopLock.Lock() + defer r.stopLock.Unlock() + if !r.stop { + r.stop = true + close(r.stopCh) } } // run is invoked by a goroutine to run until Stop() is called -func (n *NomadCheck) run() { +func (r *CheckRunner) run() { // Get the randomized initial pause time - initialPauseTime := randomStagger(n.check.Interval()) - n.logger.Printf("[DEBUG] agent: pausing %v before first invocation of %s", initialPauseTime, n.check.ID()) + initialPauseTime := randomStagger(r.check.Interval()) + r.logger.Printf("[DEBUG] agent: pausing %v before first invocation of %s", initialPauseTime, r.check.ID()) next := time.After(initialPauseTime) for { select { case <-next: - n.runCheck(n.check) - next = time.After(n.check.Interval()) - case <-n.stopCh: + r.runCheck(r.check) + next = time.After(r.check.Interval()) + case <-r.stopCh: return } } diff --git a/client/consul/sync.go b/client/consul/sync.go index a88dc74fc5d5..f7ebf5ea8888 100644 --- a/client/consul/sync.go +++ b/client/consul/sync.go @@ -28,7 +28,7 @@ type ConsulService struct { trackedServices map[string]*consul.AgentService trackedChecks map[string]*consul.AgentCheckRegistration - nomadChecks map[string]*NomadCheck + checkRunners map[string]*CheckRunner logger *log.Logger @@ -188,8 +188,8 @@ func (c *ConsulService) Shutdown() error { c.shutdownLock.Unlock() // Stop all the checks that nomad is running - for _, nc := range c.nomadChecks { - nc.Stop() + for _, cr := range c.checkRunners { + cr.Stop() } // de-register all the services from consul @@ -226,8 +226,8 @@ func (c *ConsulService) KeepServices(services map[string]struct{}) error { // registerCheck registers a check definition with Consul func (c *ConsulService) registerCheck(chkReg *consul.AgentCheckRegistration) error { - if nc, ok := c.nomadChecks[chkReg.ID]; ok { - nc.Start() + if cr, ok := c.checkRunners[chkReg.ID]; ok { + cr.Start() } return c.client.Agent().CheckRegister(chkReg) } @@ -267,8 +267,8 @@ func (c *ConsulService) createCheckReg(check *structs.ServiceCheck, service *con if err != nil { return nil, err } - nc := NewNomadCheck(chk, c.runCheck, c.logger) - c.nomadChecks[chk.ID()] = nc + cr := NewCheckRunner(chk, c.runCheck, c.logger) + c.checkRunners[chk.ID()] = cr } return &chkReg, nil } @@ -313,9 +313,9 @@ func (c *ConsulService) deregisterService(ID string) error { // deregisterCheck de-registers a check with a given ID from Consul. func (c *ConsulService) deregisterCheck(ID string) error { // Deleting the nomad check - if nc, ok := c.nomadChecks[ID]; ok { - nc.Stop() - delete(c.nomadChecks, ID) + if cr, ok := c.checkRunners[ID]; ok { + cr.Stop() + delete(c.checkRunners, ID) } // Deleteting from consul From 4c2766085a636042d0a971fd89bb89010972f627 Mon Sep 17 00:00:00 2001 From: Diptanu Choudhury Date: Fri, 25 Mar 2016 10:36:31 -0700 Subject: [PATCH 17/25] Renamed NomadChecks to CheckRunner and a fix for checkrunner start --- client/consul/check.go | 8 ++++---- client/consul/sync.go | 24 +++++++++++++----------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/client/consul/check.go b/client/consul/check.go index 1740bed24c27..858aa42046f0 100644 --- a/client/consul/check.go +++ b/client/consul/check.go @@ -25,26 +25,26 @@ type CheckRunner struct { // NewCheckRunner configures and returns a CheckRunner func NewCheckRunner(check Check, runCheck func(Check), logger *log.Logger) *CheckRunner { - nc := NomadCheck{ + cr := CheckRunner{ check: check, runCheck: runCheck, logger: logger, stopCh: make(chan struct{}), } - return &nc + return &cr } // Start is used to start the check. The check runs until stop is called func (r *CheckRunner) Start() { r.startedLock.Lock() + defer r.startedLock.Unlock() if r.started { return } - r.started = true r.stopLock.Lock() defer r.stopLock.Unlock() - r.stopCh = make(chan struct{}) go r.run() + r.started = true } // Stop is used to stop the check. diff --git a/client/consul/sync.go b/client/consul/sync.go index f7ebf5ea8888..0b6ca4df876b 100644 --- a/client/consul/sync.go +++ b/client/consul/sync.go @@ -100,7 +100,7 @@ func NewConsulService(config *ConsulConfig, logger *log.Logger, allocID string) logger: logger, trackedServices: make(map[string]*consul.AgentService), trackedChecks: make(map[string]*consul.AgentCheckRegistration), - nomadChecks: make(map[string]*NomadCheck), + checkRunners: make(map[string]*CheckRunner), shutdownCh: make(chan struct{}), } @@ -139,11 +139,23 @@ func (c *ConsulService) SyncTask(task *structs.Task) error { taskServices[srv.ID] = srv for _, chk := range service.Checks { + // Create a consul check registration chkReg, err := c.createCheckReg(chk, srv) if err != nil { mErr.Errors = append(mErr.Errors, err) continue } + // creating a nomad check if we have to handle this particular check type + if _, ok := c.delegateChecks[chk.Type]; ok { + nc, err := c.createCheck(chk, chkReg.ID) + if err != nil { + mErr.Errors = append(mErr.Errors, err) + continue + } + cr := NewCheckRunner(nc, c.runCheck, c.logger) + c.checkRunners[nc.ID()] = cr + } + if _, ok := c.trackedChecks[chkReg.ID]; !ok { if err := c.registerCheck(chkReg); err != nil { mErr.Errors = append(mErr.Errors, err) @@ -260,16 +272,6 @@ func (c *ConsulService) createCheckReg(check *structs.ServiceCheck, service *con default: return nil, fmt.Errorf("check type %q not valid", check.Type) } - - // creating a nomad check if we have to handle this particular check type - if _, ok := c.delegateChecks[check.Type]; ok { - chk, err := c.createCheck(check, chkReg.ID) - if err != nil { - return nil, err - } - cr := NewCheckRunner(chk, c.runCheck, c.logger) - c.checkRunners[chk.ID()] = cr - } return &chkReg, nil } From f8db6a433f8b4d8e811182bd93b789cab8cf4d8d Mon Sep 17 00:00:00 2001 From: Diptanu Choudhury Date: Fri, 25 Mar 2016 14:18:04 -0700 Subject: [PATCH 18/25] Using tickers instead of creating new timers --- client/client.go | 6 +++--- client/consul/sync.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/client.go b/client/client.go index 6f1c953f28eb..3c1da6a42d60 100644 --- a/client/client.go +++ b/client/client.go @@ -1180,10 +1180,10 @@ func (c *Client) setupConsulClient() error { // syncConsul removes services of tasks which are no longer in running state func (c *Client) syncConsul() { - sync := time.After(consulSyncInterval) + sync := time.NewTicker(consulSyncInterval) for { select { - case <-sync: + case <-sync.C: services := make(map[string]struct{}) // Get the existing allocs c.allocLock.RLock() @@ -1209,8 +1209,8 @@ func (c *Client) syncConsul() { if err := c.consulService.KeepServices(services); err != nil { c.logger.Printf("[DEBUG] client: error removing services from non-running tasks: %v", err) } - sync = time.After(consulSyncInterval) case <-c.shutdownCh: + sync.Stop() c.logger.Printf("[INFO] client: shutting down consul sync") return } diff --git a/client/consul/sync.go b/client/consul/sync.go index 0b6ca4df876b..5b9e96ea2ec1 100644 --- a/client/consul/sync.go +++ b/client/consul/sync.go @@ -327,15 +327,15 @@ func (c *ConsulService) deregisterCheck(ID string) error { // PeriodicSync triggers periodic syncing of services and checks with Consul. // This is a long lived go-routine which is stopped during shutdown func (c *ConsulService) PeriodicSync() { - sync := time.After(syncInterval) + sync := time.NewTicker(syncInterval) for { select { - case <-sync: + case <-sync.C: if err := c.performSync(); err != nil { c.logger.Printf("[DEBUG] consul: error in syncing task %q: %v", c.task.Name, err) } - sync = time.After(syncInterval) case <-c.shutdownCh: + sync.Stop() c.logger.Printf("[INFO] consul: shutting down sync for task %q", c.task.Name) return } From bfc2a0d716b50b4d7057e7bdbc6f9fe05bd73d4e Mon Sep 17 00:00:00 2001 From: Diptanu Choudhury Date: Fri, 25 Mar 2016 14:26:56 -0700 Subject: [PATCH 19/25] using switch to determine the state of checks --- client/consul/sync.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/client/consul/sync.go b/client/consul/sync.go index 5b9e96ea2ec1..4d20ba15c9ea 100644 --- a/client/consul/sync.go +++ b/client/consul/sync.go @@ -412,13 +412,14 @@ func (c *ConsulService) runCheck(check Check) { if res.Err != nil { output = res.Err.Error() } - if res.ExitCode == 0 { + switch res.ExitCode { + case 0: state = consul.HealthPassing - } - if res.ExitCode == 1 { + case 1: state = consul.HealthWarning + default: + state = consul.HealthCritical } - if err := c.client.Agent().UpdateTTL(check.ID(), output, state); err != nil { c.logger.Printf("[DEBUG] error updating ttl check for check %q: %v", check.ID(), err) } From e0b9f038b0a25f8fe881fddf64d6acf738e0d48c Mon Sep 17 00:00:00 2001 From: Diptanu Choudhury Date: Fri, 25 Mar 2016 16:11:45 -0700 Subject: [PATCH 20/25] Using a single timer to run checks --- client/consul/check.go | 7 ++++--- client/consul/sync.go | 4 ++-- client/driver/executor/checks.go | 12 ++++++++++-- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/client/consul/check.go b/client/consul/check.go index 858aa42046f0..f068863ee5e6 100644 --- a/client/consul/check.go +++ b/client/consul/check.go @@ -62,13 +62,14 @@ func (r *CheckRunner) run() { // Get the randomized initial pause time initialPauseTime := randomStagger(r.check.Interval()) r.logger.Printf("[DEBUG] agent: pausing %v before first invocation of %s", initialPauseTime, r.check.ID()) - next := time.After(initialPauseTime) + next := time.NewTimer(initialPauseTime) for { select { - case <-next: + case <-next.C: r.runCheck(r.check) - next = time.After(r.check.Interval()) + next.Reset(r.check.Interval()) case <-r.stopCh: + next.Stop() return } } diff --git a/client/consul/sync.go b/client/consul/sync.go index 4d20ba15c9ea..fd4f022c9016 100644 --- a/client/consul/sync.go +++ b/client/consul/sync.go @@ -204,7 +204,7 @@ func (c *ConsulService) Shutdown() error { cr.Stop() } - // de-register all the services from consul + // De-register all the services from consul for _, service := range c.trackedServices { if err := c.client.Agent().ServiceDeregister(service.ID); err != nil { mErr.Errors = append(mErr.Errors, err) @@ -320,7 +320,7 @@ func (c *ConsulService) deregisterCheck(ID string) error { delete(c.checkRunners, ID) } - // Deleteting from consul + // Deleting from consul return c.client.Agent().CheckDeregister(ID) } diff --git a/client/driver/executor/checks.go b/client/driver/executor/checks.go index faa33dbb82d5..0d7e6eb51e4b 100644 --- a/client/driver/executor/checks.go +++ b/client/driver/executor/checks.go @@ -147,7 +147,11 @@ func (e *ExecScriptCheck) Run() *cstructs.CheckResult { select { case err := <-errCh: if err == nil { - return &cstructs.CheckResult{ExitCode: 0, Output: string(buf.Bytes()), Timestamp: ts} + return &cstructs.CheckResult{ + ExitCode: 0, + Output: string(buf.Bytes()), + Timestamp: ts, + } } exitCode := 1 if exitErr, ok := err.(*exec.ExitError); ok { @@ -155,7 +159,11 @@ func (e *ExecScriptCheck) Run() *cstructs.CheckResult { exitCode = status.ExitStatus() } } - return &cstructs.CheckResult{ExitCode: exitCode, Output: string(buf.Bytes()), Timestamp: ts} + return &cstructs.CheckResult{ + ExitCode: exitCode, + Output: string(buf.Bytes()), + Timestamp: ts, + } case <-time.After(30 * time.Second): errCh <- fmt.Errorf("timed out after waiting 30s") } From 644710a739d2a4c554e18415bb0741e1604836ed Mon Sep 17 00:00:00 2001 From: Diptanu Choudhury Date: Fri, 25 Mar 2016 16:56:40 -0700 Subject: [PATCH 21/25] Added more tests for the checks --- client/driver/executor/checks_test.go | 98 +++++++++++++++++++++++++++ client/driver/executor/executor.go | 2 + 2 files changed, 100 insertions(+) diff --git a/client/driver/executor/checks_test.go b/client/driver/executor/checks_test.go index 9526b6330a22..edeebf825fbd 100644 --- a/client/driver/executor/checks_test.go +++ b/client/driver/executor/checks_test.go @@ -1,10 +1,16 @@ package executor import ( + "log" + "os" "strings" "testing" + "time" docker "github.com/fsouza/go-dockerclient" + + cstructs "github.com/hashicorp/nomad/client/driver/structs" + "github.com/hashicorp/nomad/client/testutil" ) // dockerIsConnected checks to see if a docker daemon is available (local or remote) @@ -49,3 +55,95 @@ func TestExecScriptCheckNoIsolation(t *testing.T) { t.Fatalf("exitcode expected: %v, actual: %v", expectedExitCode, res.ExitCode) } } + +func TestExecScriptCheckWithIsolation(t *testing.T) { + testutil.ExecCompatible(t) + + execCmd := ExecCommand{Cmd: "/bin/echo", Args: []string{"hello world"}} + ctx := testExecutorContext(t) + defer ctx.AllocDir.Destroy() + + execCmd.FSIsolation = true + execCmd.ResourceLimits = true + execCmd.User = cstructs.DefaultUnpriviledgedUser + + executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags)) + _, err := executor.LaunchCmd(&execCmd, ctx) + if err != nil { + t.Fatalf("error in launching command: %v", err) + } + + check := &ExecScriptCheck{ + id: "foo", + cmd: "/bin/echo", + args: []string{"hello", "world"}, + taskDir: "/tmp", + FSIsolation: true, + } + + res := check.Run() + expectedOutput := "hello world" + expectedExitCode := 0 + if res.Err != nil { + t.Fatalf("err: %v", res.Err) + } + if strings.TrimSpace(res.Output) != expectedOutput { + t.Fatalf("output expected: %v, actual: %v", expectedOutput, res.Output) + } + + if res.ExitCode != expectedExitCode { + t.Fatalf("exitcode expected: %v, actual: %v", expectedExitCode, res.ExitCode) + } +} + +func TestDockerScriptCheck(t *testing.T) { + if !dockerIsConnected(t) { + return + } + client, err := docker.NewClientFromEnv() + if err != nil { + t.Fatalf("error creating docker client: %v", err) + } + + if err := client.PullImage(docker.PullImageOptions{Repository: "busybox", Tag: "1-uclibc"}, + docker.AuthConfiguration{}); err != nil { + t.Fatalf("error pulling redis: %v", err) + } + + container, err := client.CreateContainer(docker.CreateContainerOptions{ + Config: &docker.Config{ + Image: "busybox", + Cmd: []string{"/bin/sleep", "1000"}, + }, + }) + if err != nil { + t.Fatalf("error creating container: %v", err) + } + + if err := client.StartContainer(container.ID, &docker.HostConfig{}); err != nil { + t.Fatalf("error starting container", err) + } + + check := &DockerScriptCheck{ + id: "1", + interval: 5 * time.Second, + containerID: container.ID, + logger: log.New(os.Stdout, "", log.LstdFlags), + cmd: "/bin/echo", + args: []string{"hello", "world"}, + } + + res := check.Run() + expectedOutput := "hello world" + expectedExitCode := 0 + if res.Err != nil { + t.Fatalf("err: %v", res.Err) + } + if strings.TrimSpace(res.Output) != expectedOutput { + t.Fatalf("output expected: %v, actual: %v", expectedOutput, res.Output) + } + + if res.ExitCode != expectedExitCode { + t.Fatalf("exitcode expected: %v, actual: %v", expectedExitCode, res.ExitCode) + } +} diff --git a/client/driver/executor/executor.go b/client/driver/executor/executor.go index 383b33db807b..06371b724c01 100644 --- a/client/driver/executor/executor.go +++ b/client/driver/executor/executor.go @@ -55,6 +55,8 @@ type ConsulContext struct { // daeemon over TLS TLSCa string + // TLSKey is the TLS key which the docker client uses while interacting with + // the docker daemon TLSKey string // DockerEndpoint is the endpoint of the docker daemon From f1d9b2cb657e56ec0751b69362a1ed78dd2d6488 Mon Sep 17 00:00:00 2001 From: Diptanu Choudhury Date: Fri, 25 Mar 2016 17:02:53 -0700 Subject: [PATCH 22/25] Removing the container after running script check --- client/driver/executor/checks_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/client/driver/executor/checks_test.go b/client/driver/executor/checks_test.go index edeebf825fbd..0bdaa2a7096c 100644 --- a/client/driver/executor/checks_test.go +++ b/client/driver/executor/checks_test.go @@ -119,6 +119,7 @@ func TestDockerScriptCheck(t *testing.T) { if err != nil { t.Fatalf("error creating container: %v", err) } + defer removeContainer(client, container.ID) if err := client.StartContainer(container.ID, &docker.HostConfig{}); err != nil { t.Fatalf("error starting container", err) @@ -147,3 +148,9 @@ func TestDockerScriptCheck(t *testing.T) { t.Fatalf("exitcode expected: %v, actual: %v", expectedExitCode, res.ExitCode) } } + +// removeContainer kills and removes a container +func removeContainer(client *docker.Client, containerID string) { + client.KillContainer(docker.KillContainerOptions{ID: containerID}) + client.RemoveContainer(docker.RemoveContainerOptions{ID: containerID, Force: true}) +} From d4a5f075ddc391aa26e6905cb85a17787c6a5217 Mon Sep 17 00:00:00 2001 From: Diptanu Choudhury Date: Fri, 25 Mar 2016 17:10:08 -0700 Subject: [PATCH 23/25] Moved the dockerIsConnected to testutils --- client/driver/docker_test.go | 42 ++++++++------------------- client/driver/executor/checks_test.go | 21 +------------- client/testutil/docker.go | 25 ++++++++++++++++ 3 files changed, 38 insertions(+), 50 deletions(-) create mode 100644 client/testutil/docker.go diff --git a/client/driver/docker_test.go b/client/driver/docker_test.go index 05f2606d9d12..589adf349058 100644 --- a/client/driver/docker_test.go +++ b/client/driver/docker_test.go @@ -18,30 +18,12 @@ import ( "github.com/hashicorp/nomad/client/config" "github.com/hashicorp/nomad/client/driver/env" cstructs "github.com/hashicorp/nomad/client/driver/structs" + "github.com/hashicorp/nomad/client/testutil" "github.com/hashicorp/nomad/helper/discover" "github.com/hashicorp/nomad/nomad/structs" - "github.com/hashicorp/nomad/testutil" + tu "github.com/hashicorp/nomad/testutil" ) -// dockerIsConnected checks to see if a docker daemon is available (local or remote) -func dockerIsConnected(t *testing.T) bool { - client, err := docker.NewClientFromEnv() - if err != nil { - return false - } - - // Creating a client doesn't actually connect, so make sure we do something - // like call Version() on it. - env, err := client.Version() - if err != nil { - t.Logf("Failed to connect to docker daemon: %s", err) - return false - } - - t.Logf("Successfully connected to docker daemon running version %s", env.Get("Version")) - return true -} - func dockerIsRemote(t *testing.T) bool { client, err := docker.NewClientFromEnv() if err != nil { @@ -102,7 +84,7 @@ func dockerTask() (*structs.Task, int, int) { // If there is a problem during setup this function will abort or skip the test // and indicate the reason. func dockerSetup(t *testing.T, task *structs.Task) (*docker.Client, DriverHandle, func()) { - if !dockerIsConnected(t) { + if !testutil.DockerIsConnected(t) { t.SkipNow() } @@ -184,7 +166,7 @@ func TestDockerDriver_Fingerprint(t *testing.T) { if err != nil { t.Fatalf("err: %v", err) } - if apply != dockerIsConnected(t) { + if apply != testutil.DockerIsConnected(t) { t.Fatalf("Fingerprinter should detect when docker is available") } if node.Attributes["driver.docker"] != "1" { @@ -195,7 +177,7 @@ func TestDockerDriver_Fingerprint(t *testing.T) { func TestDockerDriver_StartOpen_Wait(t *testing.T) { t.Parallel() - if !dockerIsConnected(t) { + if !testutil.DockerIsConnected(t) { t.SkipNow() } @@ -267,7 +249,7 @@ func TestDockerDriver_Start_Wait(t *testing.T) { if !res.Successful() { t.Fatalf("err: %v", res) } - case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second): + case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second): t.Fatalf("timeout") } } @@ -277,7 +259,7 @@ func TestDockerDriver_Start_Wait_AllocDir(t *testing.T) { // This test requires that the alloc dir be mounted into docker as a volume. // Because this cannot happen when docker is run remotely, e.g. when running // docker in a VM, we skip this when we detect Docker is being run remotely. - if !dockerIsConnected(t) || dockerIsRemote(t) { + if !testutil.DockerIsConnected(t) || dockerIsRemote(t) { t.SkipNow() } @@ -322,7 +304,7 @@ func TestDockerDriver_Start_Wait_AllocDir(t *testing.T) { if !res.Successful() { t.Fatalf("err: %v", res) } - case <-time.After(time.Duration(testutil.TestMultiplier()*5) * time.Second): + case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second): t.Fatalf("timeout") } @@ -370,14 +352,14 @@ func TestDockerDriver_Start_Kill_Wait(t *testing.T) { if res.Successful() { t.Fatalf("should err: %v", res) } - case <-time.After(time.Duration(testutil.TestMultiplier()*10) * time.Second): + case <-time.After(time.Duration(tu.TestMultiplier()*10) * time.Second): t.Fatalf("timeout") } } func TestDocker_StartN(t *testing.T) { t.Parallel() - if !dockerIsConnected(t) { + if !testutil.DockerIsConnected(t) { t.SkipNow() } @@ -422,7 +404,7 @@ func TestDocker_StartN(t *testing.T) { func TestDocker_StartNVersions(t *testing.T) { t.Parallel() - if !dockerIsConnected(t) { + if !testutil.DockerIsConnected(t) { t.SkipNow() } @@ -692,7 +674,7 @@ func TestDockerUser(t *testing.T) { }, } - if !dockerIsConnected(t) { + if !testutil.DockerIsConnected(t) { t.SkipNow() } diff --git a/client/driver/executor/checks_test.go b/client/driver/executor/checks_test.go index 0bdaa2a7096c..2449c74bbe37 100644 --- a/client/driver/executor/checks_test.go +++ b/client/driver/executor/checks_test.go @@ -13,25 +13,6 @@ import ( "github.com/hashicorp/nomad/client/testutil" ) -// dockerIsConnected checks to see if a docker daemon is available (local or remote) -func dockerIsConnected(t *testing.T) bool { - client, err := docker.NewClientFromEnv() - if err != nil { - return false - } - - // Creating a client doesn't actually connect, so make sure we do something - // like call Version() on it. - env, err := client.Version() - if err != nil { - t.Logf("Failed to connect to docker daemon: %s", err) - return false - } - - t.Logf("Successfully connected to docker daemon running version %s", env.Get("Version")) - return true -} - func TestExecScriptCheckNoIsolation(t *testing.T) { check := &ExecScriptCheck{ id: "foo", @@ -97,7 +78,7 @@ func TestExecScriptCheckWithIsolation(t *testing.T) { } func TestDockerScriptCheck(t *testing.T) { - if !dockerIsConnected(t) { + if !testutil.DockerIsConnected(t) { return } client, err := docker.NewClientFromEnv() diff --git a/client/testutil/docker.go b/client/testutil/docker.go new file mode 100644 index 000000000000..48d0c52ca0fc --- /dev/null +++ b/client/testutil/docker.go @@ -0,0 +1,25 @@ +package testutil + +import ( + docker "github.com/fsouza/go-dockerclient" + "testing" +) + +// DockerIsConnected checks to see if a docker daemon is available (local or remote) +func DockerIsConnected(t *testing.T) bool { + client, err := docker.NewClientFromEnv() + if err != nil { + return false + } + + // Creating a client doesn't actually connect, so make sure we do something + // like call Version() on it. + env, err := client.Version() + if err != nil { + t.Logf("Failed to connect to docker daemon: %s", err) + return false + } + + t.Logf("Successfully connected to docker daemon running version %s", env.Get("Version")) + return true +} From 6ee99af4520cc33f7079189353b025f9f3e1f4a9 Mon Sep 17 00:00:00 2001 From: Diptanu Choudhury Date: Fri, 25 Mar 2016 17:48:05 -0700 Subject: [PATCH 24/25] Fixing the exec script check to run within the chroot --- client/driver/executor/checks_test.go | 2 +- client/driver/executor/executor_posix.go | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/client/driver/executor/checks_test.go b/client/driver/executor/checks_test.go index 2449c74bbe37..fd15476cf3f8 100644 --- a/client/driver/executor/checks_test.go +++ b/client/driver/executor/checks_test.go @@ -58,7 +58,7 @@ func TestExecScriptCheckWithIsolation(t *testing.T) { id: "foo", cmd: "/bin/echo", args: []string{"hello", "world"}, - taskDir: "/tmp", + taskDir: ctx.AllocDir.TaskDirs["web"], FSIsolation: true, } diff --git a/client/driver/executor/executor_posix.go b/client/driver/executor/executor_posix.go index 4be468b2c290..07c688084fec 100644 --- a/client/driver/executor/executor_posix.go +++ b/client/driver/executor/executor_posix.go @@ -12,6 +12,12 @@ import ( func (e *UniversalExecutor) LaunchSyslogServer(ctx *ExecutorContext) (*SyslogServerState, error) { e.ctx = ctx + + // configuring the task dir + if err := e.configureTaskDir(); err != nil { + return nil, err + } + e.syslogChan = make(chan *logging.SyslogMessage, 2048) l, err := e.getListener(e.ctx.PortLowerBound, e.ctx.PortUpperBound) if err != nil { From 59e91e1cb2547794d4e17d5164a9cc70299b2fb7 Mon Sep 17 00:00:00 2001 From: Diptanu Choudhury Date: Fri, 25 Mar 2016 18:21:43 -0700 Subject: [PATCH 25/25] Using latest busybox --- client/driver/executor/checks_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/driver/executor/checks_test.go b/client/driver/executor/checks_test.go index fd15476cf3f8..62d7910b873f 100644 --- a/client/driver/executor/checks_test.go +++ b/client/driver/executor/checks_test.go @@ -86,7 +86,7 @@ func TestDockerScriptCheck(t *testing.T) { t.Fatalf("error creating docker client: %v", err) } - if err := client.PullImage(docker.PullImageOptions{Repository: "busybox", Tag: "1-uclibc"}, + if err := client.PullImage(docker.PullImageOptions{Repository: "busybox", Tag: "latest"}, docker.AuthConfiguration{}); err != nil { t.Fatalf("error pulling redis: %v", err) }