+ Runtime: mount volumes from a host directory with 'docker run -b'

This commit is contained in:
Gabriel Monroy 2013-05-13 17:39:54 -06:00 committed by Solomon Hykes
parent cd0f22ef72
commit 4fdf11b2e6
14 changed files with 273 additions and 58 deletions

11
api.go
View file

@ -551,11 +551,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 {
hostConfig := &HostConfig{}
// allow a nil body for backwards compatibility
if r.Body != nil {
if err := json.NewDecoder(r.Body).Decode(hostConfig); err != nil {
return err
}
}
if vars == nil {
return fmt.Errorf("Missing parameter")
}
name := vars["name"]
if err := srv.ContainerStart(name); err != nil {
if err := srv.ContainerStart(name, hostConfig); err != nil {
return err
}
w.WriteHeader(http.StatusNoContent)

View file

@ -873,7 +873,8 @@ func TestPostContainersKill(t *testing.T) {
}
defer runtime.Destroy(container)
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
@ -917,7 +918,8 @@ func TestPostContainersRestart(t *testing.T) {
}
defer runtime.Destroy(container)
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
@ -973,8 +975,15 @@ func TestPostContainersStart(t *testing.T) {
}
defer runtime.Destroy(container)
hostConfigJSON, err := json.Marshal(&HostConfig{})
req, err := http.NewRequest("POST", "/containers/"+container.ID+"/start", bytes.NewReader(hostConfigJSON))
if err != nil {
t.Fatal(err)
}
r := httptest.NewRecorder()
if err := postContainersStart(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil {
if err := postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err != nil {
t.Fatal(err)
}
if r.Code != http.StatusNoContent {
@ -989,7 +998,7 @@ func TestPostContainersStart(t *testing.T) {
}
r = httptest.NewRecorder()
if err = postContainersStart(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err == nil {
if err = postContainersStart(srv, APIVERSION, r, req, map[string]string{"name": container.ID}); err == nil {
t.Fatalf("A running containter should be able to be started")
}
@ -1019,7 +1028,8 @@ func TestPostContainersStop(t *testing.T) {
}
defer runtime.Destroy(container)
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
@ -1068,7 +1078,8 @@ func TestPostContainersWait(t *testing.T) {
}
defer runtime.Destroy(container)
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
@ -1113,7 +1124,8 @@ func TestPostContainersAttach(t *testing.T) {
defer runtime.Destroy(container)
// Start the process
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}

View file

@ -87,7 +87,7 @@ func (b *buildFile) CmdRun(args string) error {
if b.image == "" {
return fmt.Errorf("Please provide a source image with `from` prior to run")
}
config, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil)
config, _, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil)
if err != nil {
return err
}
@ -263,7 +263,8 @@ func (b *buildFile) run() (string, error) {
fmt.Fprintf(b.out, " ---> Running in %s\n", utils.TruncateID(c.ID))
//start the container
if err := c.Start(); err != nil {
hostConfig := &HostConfig{}
if err := c.Start(hostConfig); err != nil {
return "", err
}

View file

@ -1235,7 +1235,7 @@ func (cli *DockerCli) CmdTag(args ...string) error {
}
func (cli *DockerCli) CmdRun(args ...string) error {
config, cmd, err := ParseRun(args, nil)
config, hostConfig, cmd, err := ParseRun(args, nil)
if err != nil {
return err
}
@ -1274,7 +1274,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
}
//start the container
if _, _, err = cli.call("POST", "/containers/"+runResult.ID+"/start", nil); err != nil {
if _, _, err = cli.call("POST", "/containers/"+runResult.ID+"/start", hostConfig); err != nil {
return err
}

View file

@ -52,6 +52,8 @@ type Container struct {
waitLock chan struct{}
Volumes map[string]string
Binds []BindMap
}
type Config struct {
@ -75,7 +77,17 @@ type Config struct {
VolumesFrom string
}
func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet, error) {
type HostConfig struct {
Binds []string
}
type BindMap struct {
SrcPath string
DstPath string
Mode string
}
func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
if len(args) > 0 && args[0] != "--help" {
cmd.SetOutput(ioutil.Discard)
@ -111,11 +123,14 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet
flVolumesFrom := cmd.String("volumes-from", "", "Mount volumes from the specified container")
var flBinds ListOpts
cmd.Var(&flBinds, "b", "Bind mount a volume from the host (e.g. -b /host:/container)")
if err := cmd.Parse(args); err != nil {
return nil, cmd, err
return nil, nil, cmd, err
}
if *flDetach && len(flAttach) > 0 {
return nil, cmd, fmt.Errorf("Conflicting options: -a and -d")
return nil, nil, cmd, fmt.Errorf("Conflicting options: -a and -d")
}
// If neither -d or -a are set, attach to everything by default
if len(flAttach) == 0 && !*flDetach {
@ -127,6 +142,15 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet
}
}
}
// add any bind targets to the list of container volumes
type empty struct{}
for _, bind := range flBinds {
arr := strings.Split(bind, ":")
dstDir := arr[1]
flVolumes[dstDir] = empty{}
}
parsedArgs := cmd.Args()
runCmd := []string{}
image := ""
@ -154,6 +178,9 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet
Volumes: flVolumes,
VolumesFrom: *flVolumesFrom,
}
hostConfig := &HostConfig{
Binds: flBinds,
}
if capabilities != nil && *flMemory > 0 && !capabilities.SwapLimit {
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
@ -164,7 +191,7 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *flag.FlagSet
if config.OpenStdin && config.AttachStdin {
config.StdinOnce = true
}
return config, cmd, nil
return config, hostConfig, cmd, nil
}
type NetworkSettings struct {
@ -430,7 +457,7 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
})
}
func (container *Container) Start() error {
func (container *Container) Start(hostConfig *HostConfig) error {
container.State.lock()
defer container.State.unlock()
@ -483,6 +510,42 @@ func (container *Container) Start() error {
}
}
// Create the requested bind mounts
binds := []BindMap{}
// Define illegal container destinations
illegal_dsts := []string{"/", "."}
for _, bind := range hostConfig.Binds {
var src, dst, mode string
arr := strings.Split(bind, ":")
if len(arr) == 2 {
src = arr[0]
dst = arr[1]
mode = "rw"
} else if len(arr) == 3 {
src = arr[0]
dst = arr[1]
mode = arr[2]
} else {
return fmt.Errorf("Invalid bind specification: %s", bind)
}
// Bail if trying to mount to an illegal destination
for _, illegal := range illegal_dsts {
if dst == illegal {
return fmt.Errorf("Illegal bind destination: %s", dst)
}
}
bindMap := BindMap{
SrcPath: src,
DstPath: dst,
Mode: mode,
}
binds = append(binds, bindMap)
}
container.Binds = binds
if err := container.generateLXCConfig(); err != nil {
return err
}
@ -552,7 +615,8 @@ func (container *Container) Start() error {
}
func (container *Container) Run() error {
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
return err
}
container.Wait()
@ -565,7 +629,8 @@ func (container *Container) Output() (output []byte, err error) {
return nil, err
}
defer pipe.Close()
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
return nil, err
}
output, err = ioutil.ReadAll(pipe)
@ -768,7 +833,8 @@ func (container *Container) Restart(seconds int) error {
if err := container.Stop(seconds); err != nil {
return err
}
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
return err
}
return nil

View file

@ -7,6 +7,7 @@ import (
"io/ioutil"
"math/rand"
"os"
"path"
"regexp"
"sort"
"strings"
@ -70,7 +71,8 @@ func TestMultipleAttachRestart(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
l1, err := bufio.NewReader(stdout1).ReadString('\n')
@ -111,7 +113,7 @@ func TestMultipleAttachRestart(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container.Start(); err != nil {
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
@ -306,7 +308,8 @@ func TestCommitAutoRun(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container2.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container2.Start(hostConfig); err != nil {
t.Fatal(err)
}
container2.Wait()
@ -388,7 +391,8 @@ func TestCommitRun(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container2.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container2.Start(hostConfig); err != nil {
t.Fatal(err)
}
container2.Wait()
@ -436,7 +440,8 @@ func TestStart(t *testing.T) {
t.Fatal(err)
}
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
@ -446,7 +451,7 @@ func TestStart(t *testing.T) {
if !container.State.Running {
t.Errorf("Container should be running")
}
if err := container.Start(); err == nil {
if err := container.Start(hostConfig); err == nil {
t.Fatalf("A running containter should be able to be started")
}
@ -528,7 +533,8 @@ func TestKillDifferentUser(t *testing.T) {
if container.State.Running {
t.Errorf("Container shouldn't be running")
}
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
@ -599,7 +605,8 @@ func TestKill(t *testing.T) {
if container.State.Running {
t.Errorf("Container shouldn't be running")
}
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
@ -724,7 +731,8 @@ func TestRestartStdin(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
if _, err := io.WriteString(stdin, "hello world"); err != nil {
@ -754,7 +762,7 @@ func TestRestartStdin(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container.Start(); err != nil {
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
if _, err := io.WriteString(stdin, "hello world #2"); err != nil {
@ -916,10 +924,11 @@ func TestMultipleContainers(t *testing.T) {
defer runtime.Destroy(container2)
// Start both containers
if err := container1.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container1.Start(hostConfig); err != nil {
t.Fatal(err)
}
if err := container2.Start(); err != nil {
if err := container2.Start(hostConfig); err != nil {
t.Fatal(err)
}
@ -971,7 +980,8 @@ func TestStdin(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
defer stdin.Close()
@ -1018,7 +1028,8 @@ func TestTty(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
defer stdin.Close()
@ -1060,7 +1071,8 @@ func TestEnv(t *testing.T) {
t.Fatal(err)
}
defer stdout.Close()
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
container.Wait()
@ -1196,7 +1208,8 @@ func BenchmarkRunParallel(b *testing.B) {
return
}
defer runtime.Destroy(container)
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
complete <- err
return
}
@ -1225,3 +1238,98 @@ func BenchmarkRunParallel(b *testing.B) {
b.Fatal(errors)
}
}
func TestBindMounts(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
tmpDir, err := ioutil.TempDir("", "docker-test")
if err != nil {
t.Fatal(err)
}
tmpFile := path.Join(tmpDir, "touch-me")
_, err = os.Create(tmpFile)
if err != nil {
t.Fatal(err)
}
// test reading from bind mount
bind_str := fmt.Sprintf("%s:/tmp:ro", tmpDir)
container, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"ls", "/tmp"},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container)
stdout, err := container.StdoutPipe()
if err != nil {
t.Fatal(err)
}
defer stdout.Close()
hostConfig := &HostConfig{
Binds: []string{bind_str},
}
if err := container.Start(hostConfig); err != nil {
t.Fatal(err)
}
container.Wait()
output, err := ioutil.ReadAll(stdout)
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(output), "touch-me") {
t.Fatal("Container failed to read from bind mount")
}
// test writing to bind mount
bind_str2 := fmt.Sprintf("%s:/tmp:rw", tmpDir)
container2, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"touch", "/tmp/holla"},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container2)
hostConfig2 := &HostConfig{
Binds: []string{bind_str2},
}
if err := container2.Start(hostConfig2); err != nil {
t.Fatal(err)
}
container2.Wait()
_, err = ioutil.ReadFile(tmpDir + "/holla")
if err != nil {
t.Fatal("Container failed to write to bind mount")
}
// test mounting to an illegal destination directory
bind_str3 := fmt.Sprintf("%s:.", tmpDir)
container3, err := NewBuilder(runtime).Create(&Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"ls", "."},
},
)
if err != nil {
t.Fatal(err)
}
defer runtime.Destroy(container3)
stdout3, err := container3.StdoutPipe()
if err != nil {
t.Fatal(err)
}
defer stdout3.Close()
hostConfig3 := &HostConfig{
Binds: []string{bind_str3},
}
if err := container3.Start(hostConfig3); err == nil {
t.Fatal("Container bind mounted illegal directory")
}
}

View file

@ -42,6 +42,9 @@ List containers (/containers/json):
- You can use size=1 to get the size of the containers
Start containers (/containers/<id>/start):
- You can now pass host-specific configuration (e.g. bind mounts) in the POST body for start calls
:doc:`docker_remote_api_v1.2`
*****************************

View file

@ -294,23 +294,30 @@ Start a container
.. http:post:: /containers/(id)/start
Start the container ``id``
Start the container ``id``
**Example request**:
**Example request**:
.. sourcecode:: http
.. sourcecode:: http
POST /containers/e90e34656806/start HTTP/1.1
**Example response**:
POST /containers/(id)/start HTTP/1.1
Content-Type: application/json
.. sourcecode:: http
{
"Binds":["/tmp:/tmp"]
}
HTTP/1.1 200 OK
:statuscode 200: no error
:statuscode 404: no such container
:statuscode 500: server error
**Example response**:
.. sourcecode:: http
HTTP/1.1 204 No Content
Content-Type: text/plain
:jsonparam hostConfig: the container's host configuration (optional)
:statuscode 200: no error
:statuscode 404: no such container
:statuscode 500: server error
Stop a contaier

View file

@ -25,3 +25,4 @@
-d=[]: Set custom dns servers for the container
-v=[]: Creates a new volume and mounts it at the specified path.
-volumes-from="": Mount all volumes from the given container.
-b=[]: Create a bind mount with: [host-dir]:[container-dir]:[rw|ro]

View file

@ -88,6 +88,10 @@ lxc.mount.entry = {{.ResolvConfPath}} {{$ROOTFS}}/etc/resolv.conf none bind,ro 0
lxc.mount.entry = {{$realPath}} {{$ROOTFS}}/{{$virtualPath}} none bind,rw 0 0
{{end}}
{{end}}
{{if .Binds}}# User-defined bind mounts
{{range $bindMap := .Binds}}lxc.mount.entry = {{$bindMap.SrcPath}} {{$ROOTFS}}{{$bindMap.DstPath}} none bind,{{$bindMap.Mode}} 0 0
{{end}}
{{end}}
# drop linux capabilities (apply mainly to the user root in the container)
# (Note: 'lxc.cap.keep' is coming soon and should replace this under the

View file

@ -144,7 +144,9 @@ func (runtime *Runtime) Register(container *Container) error {
utils.Debugf("Restarting")
container.State.Ghost = false
container.State.setStopped(0)
if err := container.Start(); err != nil {
// assume empty host config
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
return err
}
nomonitor = true

View file

@ -327,7 +327,8 @@ func findAvailalblePort(runtime *Runtime, port int) (*Container, error) {
if err != nil {
return nil, err
}
if err := container.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container.Start(hostConfig); err != nil {
if strings.Contains(err.Error(), "address already in use") {
return nil, nil
}
@ -437,7 +438,8 @@ func TestRestore(t *testing.T) {
defer runtime1.Destroy(container2)
// Start the container non blocking
if err := container2.Start(); err != nil {
hostConfig := &HostConfig{}
if err := container2.Start(hostConfig); err != nil {
t.Fatal(err)
}

View file

@ -87,7 +87,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils.
}
defer file.Body.Close()
config, _, err := ParseRun([]string{img.ID, "echo", "insert", url, path}, srv.runtime.capabilities)
config, _, _, err := ParseRun([]string{img.ID, "echo", "insert", url, path}, srv.runtime.capabilities)
if err != nil {
return "", err
}
@ -934,9 +934,9 @@ func (srv *Server) ImageGetCached(imgId string, config *Config) (*Image, error)
return nil, nil
}
func (srv *Server) ContainerStart(name string) error {
func (srv *Server) ContainerStart(name string, hostConfig *HostConfig) error {
if container := srv.runtime.Get(name); container != nil {
if err := container.Start(); err != nil {
if err := container.Start(hostConfig); err != nil {
return fmt.Errorf("Error starting container %s: %s", name, err.Error())
}
} else {

View file

@ -65,7 +65,7 @@ func TestCreateRm(t *testing.T) {
srv := &Server{runtime: runtime}
config, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
config, _, _, err := ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil)
if err != nil {
t.Fatal(err)
}
@ -98,7 +98,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
srv := &Server{runtime: runtime}
config, _, err := ParseRun([]string{GetTestImage(runtime).ID, "/bin/cat"}, nil)
config, hostConfig, _, err := ParseRun([]string{GetTestImage(runtime).ID, "/bin/cat"}, nil)
if err != nil {
t.Fatal(err)
}
@ -112,7 +112,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
t.Errorf("Expected 1 container, %v found", len(runtime.List()))
}
err = srv.ContainerStart(id)
err = srv.ContainerStart(id, hostConfig)
if err != nil {
t.Fatal(err)
}
@ -127,7 +127,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
t.Fatal(err)
}
err = srv.ContainerStart(id)
err = srv.ContainerStart(id, hostConfig)
if err != nil {
t.Fatal(err)
}