1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Engine: 'start' starts the specified container

This commit is contained in:
Solomon Hykes 2013-10-26 19:24:01 -07:00
parent 433c8e9c7d
commit 958b4a8757
9 changed files with 186 additions and 60 deletions

24
api.go
View file

@ -639,26 +639,20 @@ func deleteImages(srv *Server, version float64, w http.ResponseWriter, r *http.R
}
func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
var hostConfig *HostConfig
// allow a nil body for backwards compatibility
if r.Body != nil {
if matchesContentType(r.Header.Get("Content-Type"), "application/json") {
hostConfig = &HostConfig{}
if err := json.NewDecoder(r.Body).Decode(hostConfig); err != nil {
return err
}
}
}
if vars == nil {
return fmt.Errorf("Missing parameter")
}
name := vars["name"]
// Register any links from the host config before starting the container
if err := srv.RegisterLinks(name, hostConfig); err != nil {
return err
job := srv.Eng.Job("start", name)
// allow a nil body for backwards compatibility
if r.Body != nil {
if matchesContentType(r.Header.Get("Content-Type"), "application/json") {
if err := job.DecodeEnv(r.Body); err != nil {
return err
}
}
}
if err := srv.ContainerStart(name, hostConfig); err != nil {
if err := job.Run(); err != nil {
return err
}
w.WriteHeader(http.StatusNoContent)

View file

@ -781,11 +781,11 @@ func TestPostContainersRestart(t *testing.T) {
}
func TestPostContainersStart(t *testing.T) {
runtime := mkRuntime(t)
eng := NewTestEngine(t)
srv := mkServerFromEngine(eng, t)
runtime := srv.runtime
defer nuke(runtime)
srv := &Server{runtime: runtime}
container, _, err := runtime.Create(
&Config{
Image: GetTestImage(runtime).ID,

View file

@ -13,10 +13,11 @@ type Handler func(*Job) string
var globalHandlers map[string]Handler
func init() {
globalHandlers = make(map[string]Handler)
}
func Register(name string, handler Handler) error {
if globalHandlers == nil {
globalHandlers = make(map[string]Handler)
}
globalHandlers[name] = handler
return nil
}
@ -27,6 +28,7 @@ func Register(name string, handler Handler) error {
type Engine struct {
root string
handlers map[string]Handler
hack Hack // data for temporary hackery (see hack.go)
}
// New initializes a new engine managing the directory specified at `root`.
@ -66,7 +68,7 @@ func New(root string) (*Engine, error) {
// This function mimics `Command` from the standard os/exec package.
func (eng *Engine) Job(name string, args ...string) *Job {
job := &Job{
eng: eng,
Eng: eng,
Name: name,
Args: args,
Stdin: os.Stdin,

23
engine/hack.go Normal file
View file

@ -0,0 +1,23 @@
package engine
type Hack map[string]interface{}
func (eng *Engine) Hack_GetGlobalVar(key string) interface{} {
if eng.hack == nil {
return nil
}
val, exists := eng.hack[key]
if !exists {
return nil
}
return val
}
func (eng *Engine) Hack_SetGlobalVar(key string, val interface{}) {
if eng.hack == nil {
eng.hack = make(Hack)
}
eng.hack[key] = val
}

View file

@ -1,6 +1,7 @@
package engine
import (
"bytes"
"io"
"strings"
"fmt"
@ -22,7 +23,7 @@ import (
// This allows for richer error reporting.
//
type Job struct {
eng *Engine
Eng *Engine
Name string
Args []string
env []string
@ -111,3 +112,60 @@ func (job *Job) SetenvList(key string, value []string) error {
func (job *Job) Setenv(key, value string) {
job.env = append(job.env, key + "=" + value)
}
// DecodeEnv decodes `src` as a json dictionary, and adds
// each decoded key-value pair to the environment.
//
// If `text` cannot be decoded as a json dictionary, an error
// is returned.
func (job *Job) DecodeEnv(src io.Reader) error {
m := make(map[string]interface{})
if err := json.NewDecoder(src).Decode(&m); err != nil {
return err
}
for k, v := range m {
if sval, ok := v.(string); ok {
job.Setenv(k, sval)
} else if val, err := json.Marshal(v); err == nil {
job.Setenv(k, string(val))
} else {
job.Setenv(k, fmt.Sprintf("%v", v))
}
}
return nil
}
func (job *Job) EncodeEnv(dst io.Writer) error {
return json.NewEncoder(dst).Encode(job.Environ())
}
func (job *Job) ExportEnv(dst interface{}) error {
var buf bytes.Buffer
if err := job.EncodeEnv(&buf); err != nil {
return err
}
if err := json.NewDecoder(&buf).Decode(dst); err != nil {
return err
}
return nil
}
func (job *Job) ImportEnv(src interface{}) error {
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(src); err != nil {
return err
}
if err := job.DecodeEnv(&buf); err != nil {
return err
}
return nil
}
func (job *Job) Environ() map[string]string {
m := make(map[string]string)
for _, kv := range job.env {
parts := strings.SplitN(kv, "=", 2)
m[parts[0]] = parts[1]
}
return m
}

View file

@ -15,7 +15,7 @@ func init() {
Register("dummy", func(job *Job) string { return ""; })
}
func mkEngine(t *testing.T) *Engine {
func NewTestEngine(t *testing.T) *Engine {
// Use the caller function name as a prefix.
// This helps trace temp directories back to their test.
pc, _, _, _ := runtime.Caller(1)
@ -38,5 +38,5 @@ func mkEngine(t *testing.T) *Engine {
}
func mkJob(t *testing.T, name string, args ...string) *Job {
return mkEngine(t).Job(name, args...)
return NewTestEngine(t).Job(name, args...)
}

View file

@ -40,7 +40,7 @@ func init() {
// Only one api server can run at the same time - this is enforced by a pidfile.
// The signals SIGINT, SIGKILL and SIGTERM are intercepted for cleanup.
func jobInitApi(job *engine.Job) string {
srv, err := NewServer(ConfigFromJob(job))
srv, err := NewServer(job.Eng, ConfigFromJob(job))
if err != nil {
return err.Error()
}
@ -56,17 +56,19 @@ func jobInitApi(job *engine.Job) string {
srv.Close()
os.Exit(0)
}()
err = engine.Register("serveapi", func(job *engine.Job) string {
return srv.ListenAndServe(job.Args...).Error()
})
if err != nil {
job.Eng.Hack_SetGlobalVar("httpapi.server", srv)
if err := engine.Register("start", srv.ContainerStart); err != nil {
return err.Error()
}
if err := engine.Register("serveapi", srv.ListenAndServe); err != nil {
return err.Error()
}
return "0"
}
func (srv *Server) ListenAndServe(protoAddrs ...string) error {
func (srv *Server) ListenAndServe(job *engine.Job) string {
protoAddrs := job.Args
chErrors := make(chan error, len(protoAddrs))
for _, protoAddr := range protoAddrs {
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
@ -80,7 +82,7 @@ func (srv *Server) ListenAndServe(protoAddrs ...string) error {
log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
}
default:
return fmt.Errorf("Invalid protocol format.")
return "Invalid protocol format."
}
go func() {
chErrors <- ListenAndServe(protoAddrParts[0], protoAddrParts[1], srv, true)
@ -89,10 +91,10 @@ func (srv *Server) ListenAndServe(protoAddrs ...string) error {
for i := 0; i < len(protoAddrs); i += 1 {
err := <-chErrors
if err != nil {
return err
return err.Error()
}
}
return nil
return "0"
}
func (srv *Server) DockerVersion() APIVersion {
@ -1282,8 +1284,7 @@ func (srv *Server) RegisterLinks(name string, hostConfig *HostConfig) error {
return fmt.Errorf("No such container: %s", name)
}
// Register links
if hostConfig != nil && hostConfig.Links != nil {
if hostConfig.Links != nil {
for _, l := range hostConfig.Links {
parts, err := parseLink(l)
if err != nil {
@ -1296,7 +1297,6 @@ func (srv *Server) RegisterLinks(name string, hostConfig *HostConfig) error {
if child == nil {
return fmt.Errorf("Could not get container for %s", parts["name"])
}
if err := runtime.RegisterLink(container, child, parts["alias"]); err != nil {
return err
}
@ -1312,22 +1312,36 @@ func (srv *Server) RegisterLinks(name string, hostConfig *HostConfig) error {
return nil
}
func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error {
func (srv *Server) ContainerStart(job *engine.Job) string {
if len(job.Args) < 1 {
return fmt.Sprintf("Usage: %s container_id", job.Name)
}
name := job.Args[0]
runtime := srv.runtime
container := runtime.Get(name)
if container == nil {
return fmt.Errorf("No such container: %s", name)
return fmt.Sprintf("No such container: %s", name)
}
if hostConfig != nil {
container.hostConfig = hostConfig
// If no environment was set, then no hostconfig was passed.
if len(job.Environ()) > 0 {
var hostConfig HostConfig
if err := job.ExportEnv(&hostConfig); err != nil {
return err.Error()
}
// Register any links from the host config before starting the container
// FIXME: we could just pass the container here, no need to lookup by name again.
if err := srv.RegisterLinks(name, &hostConfig); err != nil {
return err.Error()
}
container.hostConfig = &hostConfig
container.ToDisk()
}
if err := container.Start(); err != nil {
return fmt.Errorf("Cannot start container %s: %s", name, err)
return fmt.Sprintf("Cannot start container %s: %s", name, err)
}
srv.LogEvent("start", container.ShortID(), runtime.repositories.ImageName(container.Image))
return nil
return "0"
}
func (srv *Server) ContainerStop(name string, t int) error {
@ -1478,12 +1492,13 @@ func (srv *Server) ContainerCopy(name string, resource string, out io.Writer) er
}
func NewServer(config *DaemonConfig) (*Server, error) {
func NewServer(eng *engine.Engine, config *DaemonConfig) (*Server, error) {
runtime, err := NewRuntime(config)
if err != nil {
return nil, err
}
srv := &Server{
Eng: eng,
runtime: runtime,
pullingPool: make(map[string]struct{}),
pushingPool: make(map[string]struct{}),
@ -1527,4 +1542,5 @@ type Server struct {
events []utils.JSONMessage
listeners map[string]chan utils.JSONMessage
reqFactory *utils.HTTPRequestFactory
Eng *engine.Engine
}

View file

@ -1,6 +1,7 @@
package docker
import (
"github.com/dotcloud/docker/engine"
"github.com/dotcloud/docker/utils"
"strings"
"testing"
@ -109,10 +110,11 @@ func TestCreateRm(t *testing.T) {
}
func TestCreateRmVolumes(t *testing.T) {
runtime := mkRuntime(t)
defer nuke(runtime)
eng := engine.NewTestEngine(t)
srv := &Server{runtime: runtime}
srv := mkServerFromEngine(eng, t)
runtime := srv.runtime
defer nuke(runtime)
config, hostConfig, _, err := ParseRun([]string{"-v", "/srv", GetTestImage(runtime).ID, "echo test"}, nil)
if err != nil {
@ -128,8 +130,11 @@ func TestCreateRmVolumes(t *testing.T) {
t.Errorf("Expected 1 container, %v found", len(runtime.List()))
}
err = srv.ContainerStart(id, hostConfig)
if err != nil {
job := eng.Job("start", id)
if err := job.ImportEnv(hostConfig); err != nil {
t.Fatal(err)
}
if err := job.Run(); err != nil {
t.Fatal(err)
}
@ -169,11 +174,11 @@ func TestCommit(t *testing.T) {
}
func TestCreateStartRestartStopStartKillRm(t *testing.T) {
runtime := mkRuntime(t)
eng := engine.NewTestEngine(t)
srv := mkServerFromEngine(eng, t)
runtime := srv.runtime
defer nuke(runtime)
srv := &Server{runtime: runtime}
config, hostConfig, _, err := ParseRun([]string{GetTestImage(runtime).ID, "/bin/cat"}, nil)
if err != nil {
t.Fatal(err)
@ -188,7 +193,11 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
t.Errorf("Expected 1 container, %v found", len(runtime.List()))
}
if err := srv.ContainerStart(id, hostConfig); err != nil {
job := eng.Job("start", id)
if err := job.ImportEnv(hostConfig); err != nil {
t.Fatal(err)
}
if err := job.Run(); err != nil {
t.Fatal(err)
}
@ -200,7 +209,11 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
t.Fatal(err)
}
if err := srv.ContainerStart(id, hostConfig); err != nil {
job = eng.Job("start", id)
if err := job.ImportEnv(hostConfig); err != nil {
t.Fatal(err)
}
if err := job.Run(); err != nil {
t.Fatal(err)
}
@ -384,9 +397,10 @@ func TestLogEvent(t *testing.T) {
}
func TestRmi(t *testing.T) {
runtime := mkRuntime(t)
eng := engine.NewTestEngine(t)
srv := mkServerFromEngine(eng, t)
runtime := srv.runtime
defer nuke(runtime)
srv := &Server{runtime: runtime}
initialImages, err := srv.Images(false, "")
if err != nil {
@ -404,8 +418,11 @@ func TestRmi(t *testing.T) {
}
//To remove
err = srv.ContainerStart(containerID, hostConfig)
if err != nil {
job := eng.Job("start", containerID)
if err := job.ImportEnv(hostConfig); err != nil {
t.Fatal(err)
}
if err := job.Run(); err != nil {
t.Fatal(err)
}
@ -425,8 +442,11 @@ func TestRmi(t *testing.T) {
}
//To remove
err = srv.ContainerStart(containerID, hostConfig)
if err != nil {
job = eng.Job("start", containerID)
if err := job.ImportEnv(hostConfig); err != nil {
t.Fatal(err)
}
if err := job.Run(); err != nil {
t.Fatal(err)
}

View file

@ -2,6 +2,7 @@ package docker
import (
"fmt"
"github.com/dotcloud/docker/engine"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
@ -40,6 +41,18 @@ func mkRuntime(f Fataler) *Runtime {
return runtime
}
func mkServerFromEngine(eng *engine.Engine, t Fataler) *Server {
iSrv := eng.Hack_GetGlobalVar("httpapi.server")
if iSrv == nil {
t.Fatal("Legacy server field not set in engine")
}
srv, ok := iSrv.(*Server)
if !ok {
t.Fatal("Legacy server field in engine does not cast to *Server")
}
return srv
}
// A common interface to access the Fatal method of
// both testing.B and testing.T.
type Fataler interface {