From 720f3447046355329b5ba5d850caca84328182d5 Mon Sep 17 00:00:00 2001 From: shin- Date: Mon, 14 Apr 2014 20:32:47 +0200 Subject: [PATCH 01/37] Added support for multiple endpoints in X-Docker-Endpoints header Docker-DCO-1.1-Signed-off-by: Joffrey F (github: shin-) --- registry/registry.go | 33 +++++++++++++++++++++++++-------- registry/registry_mock_test.go | 2 +- registry/registry_test.go | 15 ++++++++++++++- 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/registry/registry.go b/registry/registry.go index 817c08afa..3656032e9 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -297,6 +297,25 @@ func (r *Registry) GetRemoteTags(registries []string, repository string, token [ return nil, fmt.Errorf("Could not reach any registry endpoint") } +func buildEndpointsList(headers []string, indexEp string) ([]string, error) { + var endpoints []string + parsedUrl, err := url.Parse(indexEp) + if err != nil { + return nil, err + } + var urlScheme = parsedUrl.Scheme + // The Registry's URL scheme has to match the Index' + for _, ep := range headers { + epList := strings.Split(ep, ",") + for _, epListElement := range epList { + endpoints = append( + endpoints, + fmt.Sprintf("%s://%s/v1/", urlScheme, strings.TrimSpace(epListElement))) + } + } + return endpoints, nil +} + func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) { indexEp := r.indexEndpoint repositoryTarget := fmt.Sprintf("%srepositories/%s/images", indexEp, remote) @@ -332,11 +351,10 @@ func (r *Registry) GetRepositoryData(remote string) (*RepositoryData, error) { } var endpoints []string - var urlScheme = indexEp[:strings.Index(indexEp, ":")] if res.Header.Get("X-Docker-Endpoints") != "" { - // The Registry's URL scheme has to match the Index' - for _, ep := range res.Header["X-Docker-Endpoints"] { - endpoints = append(endpoints, fmt.Sprintf("%s://%s/v1/", urlScheme, ep)) + endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], indexEp) + if err != nil { + return nil, err } } else { return nil, fmt.Errorf("Index response didn't contain any endpoints") @@ -565,7 +583,6 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat } var tokens, endpoints []string - var urlScheme = indexEp[:strings.Index(indexEp, ":")] if !validate { if res.StatusCode != 200 && res.StatusCode != 201 { errBody, err := ioutil.ReadAll(res.Body) @@ -582,9 +599,9 @@ func (r *Registry) PushImageJSONIndex(remote string, imgList []*ImgData, validat } if res.Header.Get("X-Docker-Endpoints") != "" { - // The Registry's URL scheme has to match the Index' - for _, ep := range res.Header["X-Docker-Endpoints"] { - endpoints = append(endpoints, fmt.Sprintf("%s://%s/v1/", urlScheme, ep)) + endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], indexEp) + if err != nil { + return nil, err } } else { return nil, fmt.Errorf("Index response didn't contain any endpoints") diff --git a/registry/registry_mock_test.go b/registry/registry_mock_test.go index dd5da6bd5..6b0075131 100644 --- a/registry/registry_mock_test.go +++ b/registry/registry_mock_test.go @@ -291,7 +291,7 @@ func handlerUsers(w http.ResponseWriter, r *http.Request) { func handlerImages(w http.ResponseWriter, r *http.Request) { u, _ := url.Parse(testHttpServer.URL) - w.Header().Add("X-Docker-Endpoints", u.Host) + w.Header().Add("X-Docker-Endpoints", fmt.Sprintf("%s , %s ", u.Host, "test.example.com")) w.Header().Add("X-Docker-Token", fmt.Sprintf("FAKE-SESSION-%d", time.Now().UnixNano())) if r.Method == "PUT" { if strings.HasSuffix(r.URL.Path, "images") { diff --git a/registry/registry_test.go b/registry/registry_test.go index c072da41c..ad64fb1f4 100644 --- a/registry/registry_test.go +++ b/registry/registry_test.go @@ -1,7 +1,9 @@ package registry import ( + "fmt" "github.com/dotcloud/docker/utils" + "net/url" "strings" "testing" ) @@ -99,12 +101,23 @@ func TestGetRemoteTags(t *testing.T) { func TestGetRepositoryData(t *testing.T) { r := spawnTestRegistry(t) + parsedUrl, err := url.Parse(makeURL("/v1/")) + if err != nil { + t.Fatal(err) + } + host := "http://" + parsedUrl.Host + "/v1/" data, err := r.GetRepositoryData("foo42/bar") if err != nil { t.Fatal(err) } assertEqual(t, len(data.ImgList), 2, "Expected 2 images in ImgList") - assertEqual(t, len(data.Endpoints), 1, "Expected one endpoint in Endpoints") + assertEqual(t, len(data.Endpoints), 2, + fmt.Sprintf("Expected 2 endpoints in Endpoints, found %d instead", len(data.Endpoints))) + assertEqual(t, data.Endpoints[0], host, + fmt.Sprintf("Expected first endpoint to be %s but found %s instead", host, data.Endpoints[0])) + assertEqual(t, data.Endpoints[1], "http://test.example.com/v1/", + fmt.Sprintf("Expected first endpoint to be http://test.example.com/v1/ but found %s instead", data.Endpoints[1])) + } func TestPushImageJSONRegistry(t *testing.T) { From b3ddc31b9581665eb15dedd0aa45bd37c1eb6815 Mon Sep 17 00:00:00 2001 From: Daniel Norberg Date: Tue, 22 Apr 2014 16:56:18 -0400 Subject: [PATCH 02/37] avoid suicide container.Kill() might read a pid of 0 from container.State.Pid due to losing a race with container.monitor() calling container.State.SetStopped(). Sending a SIGKILL to pid 0 is undesirable as "If pid equals 0, then sig is sent to every process in the process group of the calling process." Docker-DCO-1.1-Signed-off-by: Daniel Norberg (github: danielnorberg) --- daemon/container.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index 6f63d565f..c06fd2c07 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -654,9 +654,12 @@ func (container *Container) Kill() error { // 2. Wait for the process to die, in last resort, try to kill the process directly if err := container.WaitTimeout(10 * time.Second); err != nil { - log.Printf("Container %s failed to exit within 10 seconds of kill - trying direct SIGKILL", utils.TruncateID(container.ID)) - if err := syscall.Kill(container.State.Pid, 9); err != nil { - return err + // Ensure that we don't kill ourselves + if pid := container.State.Pid; pid != 0 { + log.Printf("Container %s failed to exit within 10 seconds of kill - trying direct SIGKILL", utils.TruncateID(container.ID)) + if err := syscall.Kill(pid, 9); err != nil { + return err + } } } From f90029611fec082a41b5629e43a88a39f0674fe2 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 23 Apr 2014 13:47:56 -0700 Subject: [PATCH 03/37] Add exported status code from a job This allows the job's status code to be consumed externally so that we can use it as an exit code or saving to a state file. Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- engine/job.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/engine/job.go b/engine/job.go index 50d64011f..b56155ac1 100644 --- a/engine/job.go +++ b/engine/job.go @@ -208,3 +208,7 @@ func (job *Job) Error(err error) Status { fmt.Fprintf(job.Stderr, "%s\n", err) return StatusErr } + +func (job *Job) StatusCode() int { + return int(job.status) +} From da8f6ffdeb54a2d99a70deb4ffc58fdff76d5880 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Wed, 23 Apr 2014 11:03:03 +0000 Subject: [PATCH 04/37] initial version of installation on ubuntu 14.04 LTS Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/installation/ubuntulinux.md | 29 ++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/docs/sources/installation/ubuntulinux.md b/docs/sources/installation/ubuntulinux.md index 40dc541b6..04173cf91 100644 --- a/docs/sources/installation/ubuntulinux.md +++ b/docs/sources/installation/ubuntulinux.md @@ -4,10 +4,6 @@ page_keywords: Docker, Docker documentation, requirements, virtualbox, vagrant, # Ubuntu -> **Warning**: -> These instructions have changed for 0.6. If you are upgrading from an -> earlier version, you will need to follow them again. - > **Note**: > Docker is still under heavy development! We don't recommend using it in > production yet, but we're getting closer with each release. Please see @@ -16,6 +12,7 @@ page_keywords: Docker, Docker documentation, requirements, virtualbox, vagrant, Docker is supported on the following versions of Ubuntu: + - [*Ubuntu Trusty 14.04 (LTS) (64-bit)*](#ubuntu-trusty-1404-lts-64-bit) - [*Ubuntu Precise 12.04 (LTS) (64-bit)*](#ubuntu-precise-1204-lts-64-bit) - [*Ubuntu Raring 13.04 and Saucy 13.10 (64 bit)*](#ubuntu-raring-1304-and-saucy-1310-64-bit) @@ -23,6 +20,30 @@ Docker is supported on the following versions of Ubuntu: Please read [*Docker and UFW*](#docker-and-ufw), if you plan to use [UFW (Uncomplicated Firewall)](https://help.ubuntu.com/community/UFW) +## Ubuntu Trusty 14.04 (LTS) (64-bit) + +Ubuntu Trusty comes with a 3.13.0 Linux kernel, and a `docker.io` package which +installs all its prerequisites from Ubuntu's repository. + +> **Note**: +> Ubuntu (and Debian) contain a much older KDE3/GNOME2 package called ``docker``, so the +> package and the executable are called ``docker.io``. + +### Installation + +To install the latest Ubuntu package (may not be the latest Docker release): + + sudo apt-get update + sudo apt-get install docker.io + sudo ln -sf /usr/bin/docker.io /usr/local/bin/docker + +To verify that everything has worked as expected: + + sudo docker run -i -t ubuntu /bin/bash + +Which should download the `ubuntu` image, and then start `bash` in a container. + + ## Ubuntu Precise 12.04 (LTS) (64-bit) This installation path should work at all times. From d26ea78e42ebf18219b88e01c6252f30aa764aa2 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 10 Apr 2014 21:41:31 +0000 Subject: [PATCH 05/37] Move apparmor into security sub dir Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/nsinit/init.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 0e85c0e4b..ce51db37f 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -13,6 +13,7 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/capabilities" "github.com/dotcloud/docker/pkg/libcontainer/network" + "github.com/dotcloud/docker/pkg/libcontainer/security/apparmor" "github.com/dotcloud/docker/pkg/libcontainer/utils" "github.com/dotcloud/docker/pkg/system" "github.com/dotcloud/docker/pkg/user" From 60a90970bc4add3547064004f08c19ab5027141b Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 10 Apr 2014 23:03:52 +0000 Subject: [PATCH 06/37] Add restrictions to proc in libcontainer Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- daemon/execdriver/native/create.go | 3 + daemon/execdriver/native/driver.go | 7 ++ pkg/libcontainer/nsinit/init.go | 2 +- pkg/libcontainer/nsinit/mount.go | 67 +++++++++++-------- .../security/restrict/restrict.go | 46 +++++++++++++ 5 files changed, 96 insertions(+), 29 deletions(-) create mode 100644 pkg/libcontainer/security/restrict/restrict.go diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index ef17ce704..1edbd17ad 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -25,6 +25,7 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Container container.Cgroups.Name = c.ID // check to see if we are running in ramdisk to disable pivot root container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != "" + container.Context["restriction_path"] = d.restrictionPath if err := d.createNetwork(container, c); err != nil { return nil, err @@ -81,6 +82,8 @@ func (d *driver) setPrivileged(container *libcontainer.Container) error { c.Enabled = true } container.Cgroups.DeviceAccess = true + delete(container.Context, "restriction_path") + if apparmor.IsEnabled() { container.Context["apparmor_profile"] = "unconfined" } diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index ab82cdcc6..31a2eb0da 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -62,6 +62,7 @@ type driver struct { root string initPath string activeContainers map[string]*exec.Cmd + restrictionPath string } func NewDriver(root, initPath string) (*driver, error) { @@ -72,8 +73,14 @@ func NewDriver(root, initPath string) (*driver, error) { if err := apparmor.InstallDefaultProfile(filepath.Join(root, "../..", BackupApparmorProfilePath)); err != nil { return nil, err } + restrictionPath := filepath.Join(root, "empty") + if err := os.MkdirAll(restrictionPath, 0700); err != nil { + return nil, err + } + return &driver{ root: root, + restrictionPath: restrictionPath, initPath: initPath, activeContainers: make(map[string]*exec.Cmd), }, nil diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index ce51db37f..fb3a895a7 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -61,7 +61,7 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol label.Init() ns.logger.Println("setup mount namespace") - if err := setupNewMountNamespace(rootfs, container.Mounts, console, container.ReadonlyFs, container.NoPivotRoot, container.Context["mount_label"]); err != nil { + if err := setupNewMountNamespace(rootfs, console, container); err != nil { return fmt.Errorf("setup mount namespace %s", err) } if err := system.Sethostname(container.Hostname); err != nil { diff --git a/pkg/libcontainer/nsinit/mount.go b/pkg/libcontainer/nsinit/mount.go index dd6b1c8a4..e4869a0ec 100644 --- a/pkg/libcontainer/nsinit/mount.go +++ b/pkg/libcontainer/nsinit/mount.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/security/restrict" "github.com/dotcloud/docker/pkg/system" "io/ioutil" "os" @@ -21,9 +22,9 @@ const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NOD // // There is no need to unmount the new mounts because as soon as the mount namespace // is no longer in use, the mounts will be removed automatically -func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, console string, readonly, noPivotRoot bool, mountLabel string) error { +func setupNewMountNamespace(rootfs, console string, container *libcontainer.Container) error { flag := syscall.MS_PRIVATE - if noPivotRoot { + if container.NoPivotRoot { flag = syscall.MS_SLAVE } if err := system.Mount("", "/", "", uintptr(flag|syscall.MS_REC), ""); err != nil { @@ -32,44 +33,28 @@ func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, cons if err := system.Mount(rootfs, rootfs, "bind", syscall.MS_BIND|syscall.MS_REC, ""); err != nil { return fmt.Errorf("mouting %s as bind %s", rootfs, err) } - if err := mountSystem(rootfs, mountLabel); err != nil { + if err := mountSystem(rootfs, container.Context["mount_label"]); err != nil { return fmt.Errorf("mount system %s", err) } - - for _, m := range bindMounts { - var ( - flags = syscall.MS_BIND | syscall.MS_REC - dest = filepath.Join(rootfs, m.Destination) - ) - if !m.Writable { - flags = flags | syscall.MS_RDONLY - } - if err := system.Mount(m.Source, dest, "bind", uintptr(flags), ""); err != nil { - return fmt.Errorf("mounting %s into %s %s", m.Source, dest, err) - } - if !m.Writable { - if err := system.Mount(m.Source, dest, "bind", uintptr(flags|syscall.MS_REMOUNT), ""); err != nil { - return fmt.Errorf("remounting %s into %s %s", m.Source, dest, err) - } - } - if m.Private { - if err := system.Mount("", dest, "none", uintptr(syscall.MS_PRIVATE), ""); err != nil { - return fmt.Errorf("mounting %s private %s", dest, err) - } + if err := setupBindmounts(rootfs, container.Mounts); err != nil { + return fmt.Errorf("bind mounts %s", err) + } + if restrictionPath := container.Context["restriction_path"]; restrictionPath != "" { + if err := restrict.Restrict(rootfs, restrictionPath); err != nil { + return fmt.Errorf("restrict %s", err) } } - if err := copyDevNodes(rootfs); err != nil { return fmt.Errorf("copy dev nodes %s", err) } - if err := setupPtmx(rootfs, console, mountLabel); err != nil { + if err := setupPtmx(rootfs, console, container.Context["mount_label"]); err != nil { return err } if err := system.Chdir(rootfs); err != nil { return fmt.Errorf("chdir into %s %s", rootfs, err) } - if noPivotRoot { + if container.NoPivotRoot { if err := rootMsMove(rootfs); err != nil { return err } @@ -79,7 +64,7 @@ func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, cons } } - if readonly { + if container.ReadonlyFs { if err := system.Mount("/", "/", "bind", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_REC, ""); err != nil { return fmt.Errorf("mounting %s as readonly %s", rootfs, err) } @@ -263,3 +248,29 @@ func remountSys() error { } return nil } + +func setupBindmounts(rootfs string, bindMounts []libcontainer.Mount) error { + for _, m := range bindMounts { + var ( + flags = syscall.MS_BIND | syscall.MS_REC + dest = filepath.Join(rootfs, m.Destination) + ) + if !m.Writable { + flags = flags | syscall.MS_RDONLY + } + if err := system.Mount(m.Source, dest, "bind", uintptr(flags), ""); err != nil { + return fmt.Errorf("mounting %s into %s %s", m.Source, dest, err) + } + if !m.Writable { + if err := system.Mount(m.Source, dest, "bind", uintptr(flags|syscall.MS_REMOUNT), ""); err != nil { + return fmt.Errorf("remounting %s into %s %s", m.Source, dest, err) + } + } + if m.Private { + if err := system.Mount("", dest, "none", uintptr(syscall.MS_PRIVATE), ""); err != nil { + return fmt.Errorf("mounting %s private %s", dest, err) + } + } + } + return nil +} diff --git a/pkg/libcontainer/security/restrict/restrict.go b/pkg/libcontainer/security/restrict/restrict.go new file mode 100644 index 000000000..d5c1dbbe2 --- /dev/null +++ b/pkg/libcontainer/security/restrict/restrict.go @@ -0,0 +1,46 @@ +package restrict + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/system" + "path/filepath" + "syscall" +) + +const flags = syscall.MS_BIND | syscall.MS_REC | syscall.MS_RDONLY + +var restrictions = map[string]string{ + // dirs + "/proc/sys": "", + "/proc/irq": "", + "/proc/acpi": "", + + // files + "/proc/sysrq-trigger": "/dev/null", + "/proc/kcore": "/dev/null", +} + +// Restrict locks down access to many areas of proc +// by using the asumption that the user does not have mount caps to +// revert the changes made here +func Restrict(rootfs, empty string) error { + for dest, source := range restrictions { + dest = filepath.Join(rootfs, dest) + + // we don't have a "/dev/null" for dirs so have the requester pass a dir + // for us to bind mount + switch source { + case "": + source = empty + default: + source = filepath.Join(rootfs, source) + } + if err := system.Mount(source, dest, "bind", flags, ""); err != nil { + return fmt.Errorf("unable to mount %s over %s %s", source, dest, err) + } + if err := system.Mount("", dest, "bind", flags|syscall.MS_REMOUNT, ""); err != nil { + return fmt.Errorf("unable to mount %s over %s %s", source, dest, err) + } + } + return nil +} From 0779a8c3287fbf7ff1938df10897b551b839cbee Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 10 Apr 2014 23:27:27 +0000 Subject: [PATCH 07/37] Add lxc support for restricting proc Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- daemon/execdriver/lxc/driver.go | 34 +++++++++++++++++---------- daemon/execdriver/lxc/lxc_template.go | 9 ++++++- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/daemon/execdriver/lxc/driver.go b/daemon/execdriver/lxc/driver.go index 1ebb73e80..1232d608a 100644 --- a/daemon/execdriver/lxc/driver.go +++ b/daemon/execdriver/lxc/driver.go @@ -59,9 +59,10 @@ func init() { } type driver struct { - root string // root path for the driver to use - apparmor bool - sharedRoot bool + root string // root path for the driver to use + apparmor bool + sharedRoot bool + restrictionPath string } func NewDriver(root string, apparmor bool) (*driver, error) { @@ -69,10 +70,15 @@ func NewDriver(root string, apparmor bool) (*driver, error) { if err := linkLxcStart(root); err != nil { return nil, err } + restrictionPath := filepath.Join(root, "empty") + if err := os.MkdirAll(restrictionPath, 0700); err != nil { + return nil, err + } return &driver{ - apparmor: apparmor, - root: root, - sharedRoot: rootIsShared(), + apparmor: apparmor, + root: root, + sharedRoot: rootIsShared(), + restrictionPath: restrictionPath, }, nil } @@ -403,14 +409,16 @@ func (d *driver) generateLXCConfig(c *execdriver.Command) (string, error) { if err := LxcTemplateCompiled.Execute(fo, struct { *execdriver.Command - AppArmor bool - ProcessLabel string - MountLabel string + AppArmor bool + ProcessLabel string + MountLabel string + RestrictionSource string }{ - Command: c, - AppArmor: d.apparmor, - ProcessLabel: process, - MountLabel: mount, + Command: c, + AppArmor: d.apparmor, + ProcessLabel: process, + MountLabel: mount, + RestrictionSource: d.restrictionPath, }); err != nil { return "", err } diff --git a/daemon/execdriver/lxc/lxc_template.go b/daemon/execdriver/lxc/lxc_template.go index f4cb3d19e..25c227ef1 100644 --- a/daemon/execdriver/lxc/lxc_template.go +++ b/daemon/execdriver/lxc/lxc_template.go @@ -109,8 +109,15 @@ lxc.mount.entry = {{$value.Source}} {{escapeFstabSpaces $ROOTFS}}/{{escapeFstabS {{if .AppArmor}} lxc.aa_profile = unconfined {{else}} -#lxc.aa_profile = unconfined +# not unconfined {{end}} +{{else}} +# restrict access to proc +lxc.mount.entry = {{.RestrictionSource}} {{escapeFstabSpaces $ROOTFS}}/proc/sys none bind,ro 0 0 +lxc.mount.entry = {{.RestrictionSource}} {{escapeFstabSpaces $ROOTFS}}/proc/irq none bind,ro 0 0 +lxc.mount.entry = {{.RestrictionSource}} {{escapeFstabSpaces $ROOTFS}}/proc/acpi none bind,ro 0 0 +lxc.mount.entry = {{escapeFstabSpaces $ROOTFS}}/dev/null {{escapeFstabSpaces $ROOTFS}}/proc/sysrq-trigger none bind,ro 0 0 +lxc.mount.entry = {{escapeFstabSpaces $ROOTFS}}/dev/null {{escapeFstabSpaces $ROOTFS}}/proc/kcore none bind,ro 0 0 {{end}} # limits From 81e5026a6afb282589704fd5f6bcac9ed50108ea Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 11:45:39 +0000 Subject: [PATCH 08/37] No not mount sysfs by default for non privilged containers Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- daemon/execdriver/lxc/lxc_template.go | 2 + daemon/execdriver/native/create.go | 11 +- integration-cli/docker_cli_run_test.go | 275 +------------------------ pkg/libcontainer/container.go | 25 ++- pkg/libcontainer/nsinit/mount.go | 42 ++-- 5 files changed, 66 insertions(+), 289 deletions(-) diff --git a/daemon/execdriver/lxc/lxc_template.go b/daemon/execdriver/lxc/lxc_template.go index 25c227ef1..bc94e7a19 100644 --- a/daemon/execdriver/lxc/lxc_template.go +++ b/daemon/execdriver/lxc/lxc_template.go @@ -88,7 +88,9 @@ lxc.mount.entry = proc {{escapeFstabSpaces $ROOTFS}}/proc proc nosuid,nodev,noex # WARNING: sysfs is a known attack vector and should probably be disabled # if your userspace allows it. eg. see http://bit.ly/T9CkqJ +{{if .Privileged}} lxc.mount.entry = sysfs {{escapeFstabSpaces $ROOTFS}}/sys sysfs nosuid,nodev,noexec 0 0 +{{end}} {{if .Tty}} lxc.mount.entry = {{.Console}} {{escapeFstabSpaces $ROOTFS}}/dev/console none bind,rw 0 0 diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index 1edbd17ad..e26ff8d2b 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -82,6 +82,9 @@ func (d *driver) setPrivileged(container *libcontainer.Container) error { c.Enabled = true } container.Cgroups.DeviceAccess = true + + // add sysfs as a mount for privileged containers + container.Mounts = append(container.Mounts, libcontainer.Mount{Type: "sysfs"}) delete(container.Context, "restriction_path") if apparmor.IsEnabled() { @@ -101,7 +104,13 @@ func (d *driver) setupCgroups(container *libcontainer.Container, c *execdriver.C func (d *driver) setupMounts(container *libcontainer.Container, c *execdriver.Command) error { for _, m := range c.Mounts { - container.Mounts = append(container.Mounts, libcontainer.Mount{m.Source, m.Destination, m.Writable, m.Private}) + container.Mounts = append(container.Mounts, libcontainer.Mount{ + Type: "bind", + Source: m.Source, + Destination: m.Destination, + Writable: m.Writable, + Private: m.Private, + }) } return nil } diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index d356f5f4d..40781294a 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -389,279 +389,24 @@ func TestMultipleVolumesFrom(t *testing.T) { logDone("run - multiple volumes from") } -// this tests verifies the ID format for the container -func TestVerifyContainerID(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") - out, exit, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err) - } - if exit != 0 { - t.Fatalf("expected exit code 0 received %d", exit) - } - match, err := regexp.MatchString("^[0-9a-f]{64}$", strings.TrimSuffix(out, "\n")) - if err != nil { - t.Fatal(err) - } - if !match { - t.Fatalf("Invalid container ID: %s", out) +func TestSysNotAvaliableInNonPrivilegedContainers(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "busybox", "ls", "/sys/kernel") + if code, err := runCommand(cmd); err == nil || code == 0 { + t.Fatal("sys should not be available in a non privileged container") } deleteAllContainers() - logDone("run - verify container ID") + logDone("run - sys not avaliable in non privileged container") } -// Test that creating a container with a volume doesn't crash. Regression test for #995. -func TestCreateVolume(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "-v", "/var/lib/data", "busybox", "true") - if _, err := runCommand(cmd); err != nil { - t.Fatal(err) +func TestSysAvaliableInPrivilegedContainers(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "ls", "/sys/kernel") + if code, err := runCommand(cmd); err != nil || code != 0 { + t.Fatalf("sys should be available in privileged container") } deleteAllContainers() - logDone("run - create docker mangaed volume") -} - -func TestExitCode(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "busybox", "/bin/sh", "-c", "exit 72") - - exit, err := runCommand(cmd) - if err == nil { - t.Fatal("should not have a non nil error") - } - if exit != 72 { - t.Fatalf("expected exit code 72 received %d", exit) - } - - deleteAllContainers() - - logDone("run - correct exit code") -} - -func TestUserDefaultsToRoot(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "busybox", "id") - - out, _, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err, out) - } - if !strings.Contains(out, "uid=0(root) gid=0(root)") { - t.Fatalf("expected root user got %s", out) - } - deleteAllContainers() - - logDone("run - default user") -} - -func TestUserByName(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "-u", "root", "busybox", "id") - - out, _, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err, out) - } - if !strings.Contains(out, "uid=0(root) gid=0(root)") { - t.Fatalf("expected root user got %s", out) - } - deleteAllContainers() - - logDone("run - user by name") -} - -func TestUserByID(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "-u", "1", "busybox", "id") - - out, _, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err, out) - } - if !strings.Contains(out, "uid=1(daemon) gid=1(daemon)") { - t.Fatalf("expected daemon user got %s", out) - } - deleteAllContainers() - - logDone("run - user by id") -} - -func TestUserNotFound(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "-u", "notme", "busybox", "id") - - _, err := runCommand(cmd) - if err == nil { - t.Fatal("unknown user should cause container to fail") - } - deleteAllContainers() - - logDone("run - user not found") -} - -func TestRunTwoConcurrentContainers(t *testing.T) { - group := sync.WaitGroup{} - group.Add(2) - - for i := 0; i < 2; i++ { - go func() { - defer group.Done() - cmd := exec.Command(dockerBinary, "run", "busybox", "sleep", "2") - if _, err := runCommand(cmd); err != nil { - t.Fatal(err) - } - }() - } - - group.Wait() - - deleteAllContainers() - - logDone("run - two concurrent containers") -} - -func TestEnvironment(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "-h", "testing", "-e=FALSE=true", "-e=TRUE", "-e=TRICKY", "busybox", "env") - cmd.Env = append(os.Environ(), - "TRUE=false", - "TRICKY=tri\ncky\n", - ) - - out, _, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err, out) - } - - actualEnv := strings.Split(out, "\n") - if actualEnv[len(actualEnv)-1] == "" { - actualEnv = actualEnv[:len(actualEnv)-1] - } - sort.Strings(actualEnv) - - goodEnv := []string{ - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "HOME=/", - "HOSTNAME=testing", - "FALSE=true", - "TRUE=false", - "TRICKY=tri", - "cky", - "", - } - sort.Strings(goodEnv) - if len(goodEnv) != len(actualEnv) { - t.Fatalf("Wrong environment: should be %d variables, not: '%s'\n", len(goodEnv), strings.Join(actualEnv, ", ")) - } - for i := range goodEnv { - if actualEnv[i] != goodEnv[i] { - t.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i]) - } - } - - deleteAllContainers() - - logDone("run - verify environment") -} - -func TestContainerNetwork(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "busybox", "ping", "-c", "1", "127.0.0.1") - if _, err := runCommand(cmd); err != nil { - t.Fatal(err) - } - - deleteAllContainers() - - logDone("run - test container network via ping") -} - -// Issue #4681 -func TestLoopbackWhenNetworkDisabled(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "--networking=false", "busybox", "ping", "-c", "1", "127.0.0.1") - if _, err := runCommand(cmd); err != nil { - t.Fatal(err) - } - - deleteAllContainers() - - logDone("run - test container loopback when networking disabled") -} - -func TestLoopbackOnlyExistsWhenNetworkingDisabled(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "--networking=false", "busybox", "ip", "a", "show", "up") - out, _, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err, out) - } - - interfaces := regexp.MustCompile(`(?m)^[0-9]+: [a-zA-Z0-9]+`).FindAllString(out, -1) - if len(interfaces) != 1 { - t.Fatalf("Wrong interface count in test container: expected [*: lo], got %s", interfaces) - } - if !strings.HasSuffix(interfaces[0], ": lo") { - t.Fatalf("Wrong interface in test container: expected [*: lo], got %s", interfaces) - } - - deleteAllContainers() - - logDone("run - test loopback only exists when networking disabled") -} - -func TestPrivilegedCanMknod(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") - out, _, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err) - } - - if actual := strings.Trim(out, "\r\n"); actual != "ok" { - t.Fatalf("expected output ok received %s", actual) - } - deleteAllContainers() - - logDone("run - test privileged can mknod") -} - -func TestUnPrivilegedCanMknod(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") - out, _, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err) - } - - if actual := strings.Trim(out, "\r\n"); actual != "ok" { - t.Fatalf("expected output ok received %s", actual) - } - deleteAllContainers() - - logDone("run - test un-privileged can mknod") -} - -func TestPrivilegedCanMount(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "sh", "-c", "mount -t tmpfs none /tmp && echo ok") - - out, _, err := runCommandWithOutput(cmd) - if err != nil { - t.Fatal(err) - } - - if actual := strings.Trim(out, "\r\n"); actual != "ok" { - t.Fatalf("expected output ok received %s", actual) - } - deleteAllContainers() - - logDone("run - test privileged can mount") -} - -func TestUnPrivilegedCannotMount(t *testing.T) { - cmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "mount -t tmpfs none /tmp && echo ok") - - out, _, err := runCommandWithOutput(cmd) - if err == nil { - t.Fatal(err, out) - } - - if actual := strings.Trim(out, "\r\n"); actual == "ok" { - t.Fatalf("expected output not ok received %s", actual) - } - deleteAllContainers() - - logDone("run - test un-privileged cannot mount") + logDone("run - sys avaliable in privileged container") } diff --git a/pkg/libcontainer/container.go b/pkg/libcontainer/container.go index c7cac3542..1e032c064 100644 --- a/pkg/libcontainer/container.go +++ b/pkg/libcontainer/container.go @@ -23,7 +23,7 @@ type Container struct { Networks []*Network `json:"networks,omitempty"` // nil for host's network stack Cgroups *cgroups.Cgroup `json:"cgroups,omitempty"` // cgroups Context Context `json:"context,omitempty"` // generic context for specific options (apparmor, selinux) - Mounts []Mount `json:"mounts,omitempty"` + Mounts Mounts `json:"mounts,omitempty"` } // Network defines configuration for a container's networking stack @@ -38,11 +38,22 @@ type Network struct { Mtu int `json:"mtu,omitempty"` } -// Bind mounts from the host system to the container -// +type Mounts []Mount + +func (s Mounts) OfType(t string) Mounts { + out := Mounts{} + for _, m := range s { + if m.Type == t { + out = append(out, m) + } + } + return out +} + type Mount struct { - Source string `json:"source"` // Source path, in the host namespace - Destination string `json:"destination"` // Destination path, in the container - Writable bool `json:"writable"` - Private bool `json:"private"` + Type string `json:"type,omitempty"` + Source string `json:"source,omitempty"` // Source path, in the host namespace + Destination string `json:"destination,omitempty"` // Destination path, in the container + Writable bool `json:"writable,omitempty"` + Private bool `json:"private,omitempty"` } diff --git a/pkg/libcontainer/nsinit/mount.go b/pkg/libcontainer/nsinit/mount.go index e4869a0ec..ee480328c 100644 --- a/pkg/libcontainer/nsinit/mount.go +++ b/pkg/libcontainer/nsinit/mount.go @@ -17,6 +17,14 @@ import ( // default mount point flags const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV +type mount struct { + source string + path string + device string + flags int + data string +} + // setupNewMountNamespace is used to initialize a new mount namespace for an new // container in the rootfs that is specified. // @@ -33,7 +41,7 @@ func setupNewMountNamespace(rootfs, console string, container *libcontainer.Cont if err := system.Mount(rootfs, rootfs, "bind", syscall.MS_BIND|syscall.MS_REC, ""); err != nil { return fmt.Errorf("mouting %s as bind %s", rootfs, err) } - if err := mountSystem(rootfs, container.Context["mount_label"]); err != nil { + if err := mountSystem(rootfs, container); err != nil { return fmt.Errorf("mount system %s", err) } if err := setupBindmounts(rootfs, container.Mounts); err != nil { @@ -183,19 +191,8 @@ func setupConsole(rootfs, console string, mountLabel string) error { // mountSystem sets up linux specific system mounts like sys, proc, shm, and devpts // inside the mount namespace -func mountSystem(rootfs string, mountLabel string) error { - for _, m := range []struct { - source string - path string - device string - flags int - data string - }{ - {source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags}, - {source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags}, - {source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)}, - {source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)}, - } { +func mountSystem(rootfs string, container *libcontainer.Container) error { + for _, m := range newSystemMounts(rootfs, container.Context["mount_label"], container.Mounts) { if err := os.MkdirAll(m.path, 0755); err != nil && !os.IsExist(err) { return fmt.Errorf("mkdirall %s %s", m.path, err) } @@ -249,8 +246,8 @@ func remountSys() error { return nil } -func setupBindmounts(rootfs string, bindMounts []libcontainer.Mount) error { - for _, m := range bindMounts { +func setupBindmounts(rootfs string, bindMounts libcontainer.Mounts) error { + for _, m := range bindMounts.OfType("bind") { var ( flags = syscall.MS_BIND | syscall.MS_REC dest = filepath.Join(rootfs, m.Destination) @@ -274,3 +271,16 @@ func setupBindmounts(rootfs string, bindMounts []libcontainer.Mount) error { } return nil } + +func newSystemMounts(rootfs, mountLabel string, mounts libcontainer.Mounts) []mount { + systemMounts := []mount{ + {source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags}, + {source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)}, + {source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)}, + } + + if len(mounts.OfType("sysfs")) == 1 { + systemMounts = append(systemMounts, mount{source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags}) + } + return systemMounts +} From 5ba1242bdc309352c2b0b9a1ef9e07fe835e4857 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 14:30:09 +0000 Subject: [PATCH 09/37] Mount over dev and only copy allowed nodes in Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- daemon/execdriver/native/create.go | 2 ++ pkg/libcontainer/nsinit/mount.go | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index e26ff8d2b..76d65bd15 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -34,6 +34,8 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Container if err := d.setPrivileged(container); err != nil { return nil, err } + } else { + container.Mounts = append(container.Mounts, libcontainer.Mount{Type: "devtmpfs"}) } if err := d.setupCgroups(container, c); err != nil { return nil, err diff --git a/pkg/libcontainer/nsinit/mount.go b/pkg/libcontainer/nsinit/mount.go index ee480328c..81229ef2d 100644 --- a/pkg/libcontainer/nsinit/mount.go +++ b/pkg/libcontainer/nsinit/mount.go @@ -47,14 +47,14 @@ func setupNewMountNamespace(rootfs, console string, container *libcontainer.Cont if err := setupBindmounts(rootfs, container.Mounts); err != nil { return fmt.Errorf("bind mounts %s", err) } + if err := copyDevNodes(rootfs); err != nil { + return fmt.Errorf("copy dev nodes %s", err) + } if restrictionPath := container.Context["restriction_path"]; restrictionPath != "" { if err := restrict.Restrict(rootfs, restrictionPath); err != nil { return fmt.Errorf("restrict %s", err) } } - if err := copyDevNodes(rootfs); err != nil { - return fmt.Errorf("copy dev nodes %s", err) - } if err := setupPtmx(rootfs, console, container.Context["mount_label"]); err != nil { return err } @@ -273,12 +273,20 @@ func setupBindmounts(rootfs string, bindMounts libcontainer.Mounts) error { } func newSystemMounts(rootfs, mountLabel string, mounts libcontainer.Mounts) []mount { - systemMounts := []mount{ - {source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags}, + devMounts := []mount{ {source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)}, {source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)}, } + systemMounts := []mount{ + {source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags}, + } + + if len(mounts.OfType("devtmpfs")) == 1 { + systemMounts = append(systemMounts, mount{source: "tmpfs", path: filepath.Join(rootfs, "dev"), device: "tmpfs", flags: syscall.MS_NOSUID | syscall.MS_STRICTATIME, data: "mode=755"}) + } + systemMounts = append(systemMounts, devMounts...) + if len(mounts.OfType("sysfs")) == 1 { systemMounts = append(systemMounts, mount{source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags}) } From de3d51b0a824e31d7e245aed958d53f436456699 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 14:42:53 +0000 Subject: [PATCH 10/37] Move console into its own package Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/console/console.go | 46 +++++++++++++++++++++++++++++ pkg/libcontainer/nsinit/mount.go | 41 +++---------------------- 2 files changed, 50 insertions(+), 37 deletions(-) create mode 100644 pkg/libcontainer/console/console.go diff --git a/pkg/libcontainer/console/console.go b/pkg/libcontainer/console/console.go new file mode 100644 index 000000000..deee54418 --- /dev/null +++ b/pkg/libcontainer/console/console.go @@ -0,0 +1,46 @@ +// +build linux + +package console + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/label" + "github.com/dotcloud/docker/pkg/system" + "os" + "path/filepath" + "syscall" +) + +// Setup initializes the proper /dev/console inside the rootfs path +func Setup(rootfs, consolePath, mountLabel string) error { + oldMask := system.Umask(0000) + defer system.Umask(oldMask) + + stat, err := os.Stat(consolePath) + if err != nil { + return fmt.Errorf("stat console %s %s", consolePath, err) + } + var ( + st = stat.Sys().(*syscall.Stat_t) + dest = filepath.Join(rootfs, "dev/console") + ) + if err := os.Remove(dest); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("remove %s %s", dest, err) + } + if err := os.Chmod(consolePath, 0600); err != nil { + return err + } + if err := os.Chown(consolePath, 0, 0); err != nil { + return err + } + if err := system.Mknod(dest, (st.Mode&^07777)|0600, int(st.Rdev)); err != nil { + return fmt.Errorf("mknod %s %s", dest, err) + } + if err := label.SetFileLabel(consolePath, mountLabel); err != nil { + return fmt.Errorf("set file label %s %s", dest, err) + } + if err := system.Mount(consolePath, dest, "bind", syscall.MS_BIND, ""); err != nil { + return fmt.Errorf("bind %s to %s %s", consolePath, dest, err) + } + return nil +} diff --git a/pkg/libcontainer/nsinit/mount.go b/pkg/libcontainer/nsinit/mount.go index 81229ef2d..c85058a9f 100644 --- a/pkg/libcontainer/nsinit/mount.go +++ b/pkg/libcontainer/nsinit/mount.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/console" "github.com/dotcloud/docker/pkg/libcontainer/security/restrict" "github.com/dotcloud/docker/pkg/system" "io/ioutil" @@ -155,40 +156,6 @@ func copyDevNode(rootfs, node string) error { return nil } -// setupConsole ensures that the container has a proper /dev/console setup -func setupConsole(rootfs, console string, mountLabel string) error { - oldMask := system.Umask(0000) - defer system.Umask(oldMask) - - stat, err := os.Stat(console) - if err != nil { - return fmt.Errorf("stat console %s %s", console, err) - } - var ( - st = stat.Sys().(*syscall.Stat_t) - dest = filepath.Join(rootfs, "dev/console") - ) - if err := os.Remove(dest); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("remove %s %s", dest, err) - } - if err := os.Chmod(console, 0600); err != nil { - return err - } - if err := os.Chown(console, 0, 0); err != nil { - return err - } - if err := system.Mknod(dest, (st.Mode&^07777)|0600, int(st.Rdev)); err != nil { - return fmt.Errorf("mknod %s %s", dest, err) - } - if err := label.SetFileLabel(console, mountLabel); err != nil { - return fmt.Errorf("SetFileLabel Failed %s %s", dest, err) - } - if err := system.Mount(console, dest, "bind", syscall.MS_BIND, ""); err != nil { - return fmt.Errorf("bind %s to %s %s", console, dest, err) - } - return nil -} - // mountSystem sets up linux specific system mounts like sys, proc, shm, and devpts // inside the mount namespace func mountSystem(rootfs string, container *libcontainer.Container) error { @@ -205,7 +172,7 @@ func mountSystem(rootfs string, container *libcontainer.Container) error { // setupPtmx adds a symlink to pts/ptmx for /dev/ptmx and // finishes setting up /dev/console -func setupPtmx(rootfs, console string, mountLabel string) error { +func setupPtmx(rootfs, consolePath, mountLabel string) error { ptmx := filepath.Join(rootfs, "dev/ptmx") if err := os.Remove(ptmx); err != nil && !os.IsNotExist(err) { return err @@ -213,8 +180,8 @@ func setupPtmx(rootfs, console string, mountLabel string) error { if err := os.Symlink("pts/ptmx", ptmx); err != nil { return fmt.Errorf("symlink dev ptmx %s", err) } - if console != "" { - if err := setupConsole(rootfs, console, mountLabel); err != nil { + if consolePath != "" { + if err := console.Setup(rootfs, consolePath, mountLabel); err != nil { return err } } From 05b611574f85c7ff7d479e04e01ac2b57b233591 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 15:06:56 +0000 Subject: [PATCH 11/37] Refactor mounts into pkg to make changes easier Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- .../{nsinit/mount.go => mount/init.go} | 153 +++--------------- pkg/libcontainer/mount/msmoveroot.go | 19 +++ pkg/libcontainer/mount/nodes/nodes.go | 49 ++++++ pkg/libcontainer/mount/pivotroot.go | 31 ++++ pkg/libcontainer/mount/ptmx.go | 26 +++ pkg/libcontainer/mount/readonly.go | 12 ++ pkg/libcontainer/mount/remount.go | 31 ++++ pkg/libcontainer/nsinit/execin.go | 5 +- pkg/libcontainer/nsinit/init.go | 3 +- 9 files changed, 191 insertions(+), 138 deletions(-) rename pkg/libcontainer/{nsinit/mount.go => mount/init.go} (51%) create mode 100644 pkg/libcontainer/mount/msmoveroot.go create mode 100644 pkg/libcontainer/mount/nodes/nodes.go create mode 100644 pkg/libcontainer/mount/pivotroot.go create mode 100644 pkg/libcontainer/mount/ptmx.go create mode 100644 pkg/libcontainer/mount/readonly.go create mode 100644 pkg/libcontainer/mount/remount.go diff --git a/pkg/libcontainer/nsinit/mount.go b/pkg/libcontainer/mount/init.go similarity index 51% rename from pkg/libcontainer/nsinit/mount.go rename to pkg/libcontainer/mount/init.go index c85058a9f..2a5e47a4a 100644 --- a/pkg/libcontainer/nsinit/mount.go +++ b/pkg/libcontainer/mount/init.go @@ -1,15 +1,14 @@ // +build linux -package nsinit +package mount import ( "fmt" "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/pkg/libcontainer/console" + "github.com/dotcloud/docker/pkg/libcontainer/mount/nodes" "github.com/dotcloud/docker/pkg/libcontainer/security/restrict" "github.com/dotcloud/docker/pkg/system" - "io/ioutil" "os" "path/filepath" "syscall" @@ -26,13 +25,13 @@ type mount struct { data string } -// setupNewMountNamespace is used to initialize a new mount namespace for an new -// container in the rootfs that is specified. -// -// There is no need to unmount the new mounts because as soon as the mount namespace -// is no longer in use, the mounts will be removed automatically -func setupNewMountNamespace(rootfs, console string, container *libcontainer.Container) error { - flag := syscall.MS_PRIVATE +// InitializeMountNamespace setups up the devices, mount points, and filesystems for use inside a +// new mount namepsace +func InitializeMountNamespace(rootfs, console string, container *libcontainer.Container) error { + var ( + err error + flag = syscall.MS_PRIVATE + ) if container.NoPivotRoot { flag = syscall.MS_SLAVE } @@ -48,7 +47,7 @@ func setupNewMountNamespace(rootfs, console string, container *libcontainer.Cont if err := setupBindmounts(rootfs, container.Mounts); err != nil { return fmt.Errorf("bind mounts %s", err) } - if err := copyDevNodes(rootfs); err != nil { + if err := nodes.CopyN(rootfs, nodes.DefaultNodes); err != nil { return fmt.Errorf("copy dev nodes %s", err) } if restrictionPath := container.Context["restriction_path"]; restrictionPath != "" { @@ -56,7 +55,7 @@ func setupNewMountNamespace(rootfs, console string, container *libcontainer.Cont return fmt.Errorf("restrict %s", err) } } - if err := setupPtmx(rootfs, console, container.Context["mount_label"]); err != nil { + if err := SetupPtmx(rootfs, console, container.Context["mount_label"]); err != nil { return err } if err := system.Chdir(rootfs); err != nil { @@ -64,18 +63,17 @@ func setupNewMountNamespace(rootfs, console string, container *libcontainer.Cont } if container.NoPivotRoot { - if err := rootMsMove(rootfs); err != nil { - return err - } + err = MsMoveRoot(rootfs) } else { - if err := rootPivot(rootfs); err != nil { - return err - } + err = PivotRoot(rootfs) + } + if err != nil { + return err } if container.ReadonlyFs { - if err := system.Mount("/", "/", "bind", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_REC, ""); err != nil { - return fmt.Errorf("mounting %s as readonly %s", rootfs, err) + if err := SetReadonly(); err != nil { + return fmt.Errorf("set readonly %s", err) } } @@ -84,78 +82,6 @@ func setupNewMountNamespace(rootfs, console string, container *libcontainer.Cont return nil } -// use a pivot root to setup the rootfs -func rootPivot(rootfs string) error { - pivotDir, err := ioutil.TempDir(rootfs, ".pivot_root") - if err != nil { - return fmt.Errorf("can't create pivot_root dir %s", pivotDir, err) - } - if err := system.Pivotroot(rootfs, pivotDir); err != nil { - return fmt.Errorf("pivot_root %s", err) - } - if err := system.Chdir("/"); err != nil { - return fmt.Errorf("chdir / %s", err) - } - // path to pivot dir now changed, update - pivotDir = filepath.Join("/", filepath.Base(pivotDir)) - if err := system.Unmount(pivotDir, syscall.MNT_DETACH); err != nil { - return fmt.Errorf("unmount pivot_root dir %s", err) - } - if err := os.Remove(pivotDir); err != nil { - return fmt.Errorf("remove pivot_root dir %s", err) - } - return nil -} - -// use MS_MOVE and chroot to setup the rootfs -func rootMsMove(rootfs string) error { - if err := system.Mount(rootfs, "/", "", syscall.MS_MOVE, ""); err != nil { - return fmt.Errorf("mount move %s into / %s", rootfs, err) - } - if err := system.Chroot("."); err != nil { - return fmt.Errorf("chroot . %s", err) - } - if err := system.Chdir("/"); err != nil { - return fmt.Errorf("chdir / %s", err) - } - return nil -} - -// copyDevNodes mknods the hosts devices so the new container has access to them -func copyDevNodes(rootfs string) error { - oldMask := system.Umask(0000) - defer system.Umask(oldMask) - - for _, node := range []string{ - "null", - "zero", - "full", - "random", - "urandom", - "tty", - } { - if err := copyDevNode(rootfs, node); err != nil { - return err - } - } - return nil -} - -func copyDevNode(rootfs, node string) error { - stat, err := os.Stat(filepath.Join("/dev", node)) - if err != nil { - return err - } - var ( - dest = filepath.Join(rootfs, "dev", node) - st = stat.Sys().(*syscall.Stat_t) - ) - if err := system.Mknod(dest, st.Mode, int(st.Rdev)); err != nil && !os.IsExist(err) { - return fmt.Errorf("copy %s %s", node, err) - } - return nil -} - // mountSystem sets up linux specific system mounts like sys, proc, shm, and devpts // inside the mount namespace func mountSystem(rootfs string, container *libcontainer.Container) error { @@ -170,49 +96,6 @@ func mountSystem(rootfs string, container *libcontainer.Container) error { return nil } -// setupPtmx adds a symlink to pts/ptmx for /dev/ptmx and -// finishes setting up /dev/console -func setupPtmx(rootfs, consolePath, mountLabel string) error { - ptmx := filepath.Join(rootfs, "dev/ptmx") - if err := os.Remove(ptmx); err != nil && !os.IsNotExist(err) { - return err - } - if err := os.Symlink("pts/ptmx", ptmx); err != nil { - return fmt.Errorf("symlink dev ptmx %s", err) - } - if consolePath != "" { - if err := console.Setup(rootfs, consolePath, mountLabel); err != nil { - return err - } - } - return nil -} - -// remountProc is used to detach and remount the proc filesystem -// commonly needed with running a new process inside an existing container -func remountProc() error { - if err := system.Unmount("/proc", syscall.MNT_DETACH); err != nil { - return err - } - if err := system.Mount("proc", "/proc", "proc", uintptr(defaultMountFlags), ""); err != nil { - return err - } - return nil -} - -func remountSys() error { - if err := system.Unmount("/sys", syscall.MNT_DETACH); err != nil { - if err != syscall.EINVAL { - return err - } - } else { - if err := system.Mount("sysfs", "/sys", "sysfs", uintptr(defaultMountFlags), ""); err != nil { - return err - } - } - return nil -} - func setupBindmounts(rootfs string, bindMounts libcontainer.Mounts) error { for _, m := range bindMounts.OfType("bind") { var ( diff --git a/pkg/libcontainer/mount/msmoveroot.go b/pkg/libcontainer/mount/msmoveroot.go new file mode 100644 index 000000000..b336c8649 --- /dev/null +++ b/pkg/libcontainer/mount/msmoveroot.go @@ -0,0 +1,19 @@ +// +build linux + +package mount + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/system" + "syscall" +) + +func MsMoveRoot(rootfs string) error { + if err := system.Mount(rootfs, "/", "", syscall.MS_MOVE, ""); err != nil { + return fmt.Errorf("mount move %s into / %s", rootfs, err) + } + if err := system.Chroot("."); err != nil { + return fmt.Errorf("chroot . %s", err) + } + return system.Chdir("/") +} diff --git a/pkg/libcontainer/mount/nodes/nodes.go b/pkg/libcontainer/mount/nodes/nodes.go new file mode 100644 index 000000000..5022f85b0 --- /dev/null +++ b/pkg/libcontainer/mount/nodes/nodes.go @@ -0,0 +1,49 @@ +// +build linux + +package nodes + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/system" + "os" + "path/filepath" + "syscall" +) + +// Default list of device nodes to copy +var DefaultNodes = []string{ + "null", + "zero", + "full", + "random", + "urandom", + "tty", +} + +// CopyN copies the device node from the host into the rootfs +func CopyN(rootfs string, nodesToCopy []string) error { + oldMask := system.Umask(0000) + defer system.Umask(oldMask) + + for _, node := range nodesToCopy { + if err := Copy(rootfs, node); err != nil { + return err + } + } + return nil +} + +func Copy(rootfs, node string) error { + stat, err := os.Stat(filepath.Join("/dev", node)) + if err != nil { + return err + } + var ( + dest = filepath.Join(rootfs, "dev", node) + st = stat.Sys().(*syscall.Stat_t) + ) + if err := system.Mknod(dest, st.Mode, int(st.Rdev)); err != nil && !os.IsExist(err) { + return fmt.Errorf("copy %s %s", node, err) + } + return nil +} diff --git a/pkg/libcontainer/mount/pivotroot.go b/pkg/libcontainer/mount/pivotroot.go new file mode 100644 index 000000000..447f5904b --- /dev/null +++ b/pkg/libcontainer/mount/pivotroot.go @@ -0,0 +1,31 @@ +// +build linux + +package mount + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/system" + "io/ioutil" + "os" + "path/filepath" + "syscall" +) + +func PivotRoot(rootfs string) error { + pivotDir, err := ioutil.TempDir(rootfs, ".pivot_root") + if err != nil { + return fmt.Errorf("can't create pivot_root dir %s", pivotDir, err) + } + if err := system.Pivotroot(rootfs, pivotDir); err != nil { + return fmt.Errorf("pivot_root %s", err) + } + if err := system.Chdir("/"); err != nil { + return fmt.Errorf("chdir / %s", err) + } + // path to pivot dir now changed, update + pivotDir = filepath.Join("/", filepath.Base(pivotDir)) + if err := system.Unmount(pivotDir, syscall.MNT_DETACH); err != nil { + return fmt.Errorf("unmount pivot_root dir %s", err) + } + return os.Remove(pivotDir) +} diff --git a/pkg/libcontainer/mount/ptmx.go b/pkg/libcontainer/mount/ptmx.go new file mode 100644 index 000000000..f6ca53463 --- /dev/null +++ b/pkg/libcontainer/mount/ptmx.go @@ -0,0 +1,26 @@ +// +build linux + +package mount + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/libcontainer/console" + "os" + "path/filepath" +) + +func SetupPtmx(rootfs, consolePath, mountLabel string) error { + ptmx := filepath.Join(rootfs, "dev/ptmx") + if err := os.Remove(ptmx); err != nil && !os.IsNotExist(err) { + return err + } + if err := os.Symlink("pts/ptmx", ptmx); err != nil { + return fmt.Errorf("symlink dev ptmx %s", err) + } + if consolePath != "" { + if err := console.Setup(rootfs, consolePath, mountLabel); err != nil { + return err + } + } + return nil +} diff --git a/pkg/libcontainer/mount/readonly.go b/pkg/libcontainer/mount/readonly.go new file mode 100644 index 000000000..0658358ad --- /dev/null +++ b/pkg/libcontainer/mount/readonly.go @@ -0,0 +1,12 @@ +// +build linux + +package mount + +import ( + "github.com/dotcloud/docker/pkg/system" + "syscall" +) + +func SetReadonly() error { + return system.Mount("/", "/", "bind", syscall.MS_BIND|syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_REC, "") +} diff --git a/pkg/libcontainer/mount/remount.go b/pkg/libcontainer/mount/remount.go new file mode 100644 index 000000000..3e00509ae --- /dev/null +++ b/pkg/libcontainer/mount/remount.go @@ -0,0 +1,31 @@ +// +build linux + +package mount + +import ( + "github.com/dotcloud/docker/pkg/system" + "syscall" +) + +func RemountProc() error { + if err := system.Unmount("/proc", syscall.MNT_DETACH); err != nil { + return err + } + if err := system.Mount("proc", "/proc", "proc", uintptr(defaultMountFlags), ""); err != nil { + return err + } + return nil +} + +func RemountSys() error { + if err := system.Unmount("/sys", syscall.MNT_DETACH); err != nil { + if err != syscall.EINVAL { + return err + } + } else { + if err := system.Mount("sysfs", "/sys", "sysfs", uintptr(defaultMountFlags), ""); err != nil { + return err + } + } + return nil +} diff --git a/pkg/libcontainer/nsinit/execin.go b/pkg/libcontainer/nsinit/execin.go index 9017af06e..b79881015 100644 --- a/pkg/libcontainer/nsinit/execin.go +++ b/pkg/libcontainer/nsinit/execin.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" + "github.com/dotcloud/docker/pkg/libcontainer/mount" "github.com/dotcloud/docker/pkg/system" "os" "path/filepath" @@ -63,10 +64,10 @@ func (ns *linuxNs) ExecIn(container *libcontainer.Container, nspid int, args []s if err := system.Unshare(syscall.CLONE_NEWNS); err != nil { return -1, err } - if err := remountProc(); err != nil { + if err := mount.RemountProc(); err != nil { return -1, fmt.Errorf("remount proc %s", err) } - if err := remountSys(); err != nil { + if err := mount.RemountSys(); err != nil { return -1, fmt.Errorf("remount sys %s", err) } goto dropAndExec diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index fb3a895a7..6e6b0e5a8 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -12,6 +12,7 @@ import ( "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/capabilities" + "github.com/dotcloud/docker/pkg/libcontainer/mount" "github.com/dotcloud/docker/pkg/libcontainer/network" "github.com/dotcloud/docker/pkg/libcontainer/security/apparmor" "github.com/dotcloud/docker/pkg/libcontainer/utils" @@ -61,7 +62,7 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol label.Init() ns.logger.Println("setup mount namespace") - if err := setupNewMountNamespace(rootfs, console, container); err != nil { + if err := mount.InitializeMountNamespace(rootfs, console, container); err != nil { return fmt.Errorf("setup mount namespace %s", err) } if err := system.Sethostname(container.Hostname); err != nil { From a949d39f195e7b87288b10b0ef31843e6a3d8eb0 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 15:15:28 +0000 Subject: [PATCH 12/37] Move rest of console functions to pkg Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/console/console.go | 14 ++++++++++++ pkg/libcontainer/nsinit/init.go | 34 +++++++---------------------- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/pkg/libcontainer/console/console.go b/pkg/libcontainer/console/console.go index deee54418..05cd08a92 100644 --- a/pkg/libcontainer/console/console.go +++ b/pkg/libcontainer/console/console.go @@ -44,3 +44,17 @@ func Setup(rootfs, consolePath, mountLabel string) error { } return nil } + +func OpenAndDup(consolePath string) error { + slave, err := system.OpenTerminal(consolePath, syscall.O_RDWR) + if err != nil { + return fmt.Errorf("open terminal %s", err) + } + if err := system.Dup2(slave.Fd(), 0); err != nil { + return err + } + if err := system.Dup2(slave.Fd(), 1); err != nil { + return err + } + return system.Dup2(slave.Fd(), 2) +} diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 6e6b0e5a8..9aac9a40e 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -12,6 +12,7 @@ import ( "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/capabilities" + "github.com/dotcloud/docker/pkg/libcontainer/console" "github.com/dotcloud/docker/pkg/libcontainer/mount" "github.com/dotcloud/docker/pkg/libcontainer/network" "github.com/dotcloud/docker/pkg/libcontainer/security/apparmor" @@ -22,7 +23,7 @@ import ( // Init is the init process that first runs inside a new namespace to setup mounts, users, networking, // and other options required for the new container. -func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, console string, syncPipe *SyncPipe, args []string) error { +func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consolePath string, syncPipe *SyncPipe, args []string) error { rootfs, err := utils.ResolveRootfs(uncleanRootfs) if err != nil { return err @@ -38,20 +39,16 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol ns.logger.Println("received context from parent") syncPipe.Close() - if console != "" { - ns.logger.Printf("setting up %s as console\n", console) - slave, err := system.OpenTerminal(console, syscall.O_RDWR) - if err != nil { - return fmt.Errorf("open terminal %s", err) - } - if err := dupSlave(slave); err != nil { - return fmt.Errorf("dup2 slave %s", err) + if consolePath != "" { + ns.logger.Printf("setting up %s as console\n", consolePath) + if err := console.OpenAndDup(consolePath); err != nil { + return err } } if _, err := system.Setsid(); err != nil { return fmt.Errorf("setsid %s", err) } - if console != "" { + if consolePath != "" { if err := system.Setctty(); err != nil { return fmt.Errorf("setctty %s", err) } @@ -62,7 +59,7 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol label.Init() ns.logger.Println("setup mount namespace") - if err := mount.InitializeMountNamespace(rootfs, console, container); err != nil { + if err := mount.InitializeMountNamespace(rootfs, consolePath, container); err != nil { return fmt.Errorf("setup mount namespace %s", err) } if err := system.Sethostname(container.Hostname); err != nil { @@ -116,21 +113,6 @@ func setupUser(container *libcontainer.Container) error { return nil } -// dupSlave dup2 the pty slave's fd into stdout and stdin and ensures that -// the slave's fd is 0, or stdin -func dupSlave(slave *os.File) error { - if err := system.Dup2(slave.Fd(), 0); err != nil { - return err - } - if err := system.Dup2(slave.Fd(), 1); err != nil { - return err - } - if err := system.Dup2(slave.Fd(), 2); err != nil { - return err - } - return nil -} - // setupVethNetwork uses the Network config if it is not nil to initialize // the new veth interface inside the container for use by changing the name to eth0 // setting the MTU and IP address along with the default gateway From 156987c118f6f4067794e09e90aabeee0002d05c Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 15:26:23 +0000 Subject: [PATCH 13/37] Move mounts into types.go Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/container.go | 20 -------------------- pkg/libcontainer/mount/init.go | 11 +++++------ pkg/libcontainer/types.go | 20 ++++++++++++++++++++ 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/pkg/libcontainer/container.go b/pkg/libcontainer/container.go index 1e032c064..ddcc6cab7 100644 --- a/pkg/libcontainer/container.go +++ b/pkg/libcontainer/container.go @@ -37,23 +37,3 @@ type Network struct { Gateway string `json:"gateway,omitempty"` Mtu int `json:"mtu,omitempty"` } - -type Mounts []Mount - -func (s Mounts) OfType(t string) Mounts { - out := Mounts{} - for _, m := range s { - if m.Type == t { - out = append(out, m) - } - } - return out -} - -type Mount struct { - Type string `json:"type,omitempty"` - Source string `json:"source,omitempty"` // Source path, in the host namespace - Destination string `json:"destination,omitempty"` // Destination path, in the container - Writable bool `json:"writable,omitempty"` - Private bool `json:"private,omitempty"` -} diff --git a/pkg/libcontainer/mount/init.go b/pkg/libcontainer/mount/init.go index 2a5e47a4a..06b2c82f5 100644 --- a/pkg/libcontainer/mount/init.go +++ b/pkg/libcontainer/mount/init.go @@ -122,12 +122,9 @@ func setupBindmounts(rootfs string, bindMounts libcontainer.Mounts) error { return nil } +// TODO: this is crappy right now and should be cleaned up with a better way of handling system and +// standard bind mounts allowing them to be more dymanic func newSystemMounts(rootfs, mountLabel string, mounts libcontainer.Mounts) []mount { - devMounts := []mount{ - {source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)}, - {source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)}, - } - systemMounts := []mount{ {source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags}, } @@ -135,7 +132,9 @@ func newSystemMounts(rootfs, mountLabel string, mounts libcontainer.Mounts) []mo if len(mounts.OfType("devtmpfs")) == 1 { systemMounts = append(systemMounts, mount{source: "tmpfs", path: filepath.Join(rootfs, "dev"), device: "tmpfs", flags: syscall.MS_NOSUID | syscall.MS_STRICTATIME, data: "mode=755"}) } - systemMounts = append(systemMounts, devMounts...) + systemMounts = append(systemMounts, + mount{source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1777,size=65536k", mountLabel)}, + mount{source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)}) if len(mounts.OfType("sysfs")) == 1 { systemMounts = append(systemMounts, mount{source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags}) diff --git a/pkg/libcontainer/types.go b/pkg/libcontainer/types.go index d4818c3ff..ade3c32f1 100644 --- a/pkg/libcontainer/types.go +++ b/pkg/libcontainer/types.go @@ -11,6 +11,26 @@ var ( ErrUnsupported = errors.New("Unsupported method") ) +type Mounts []Mount + +func (s Mounts) OfType(t string) Mounts { + out := Mounts{} + for _, m := range s { + if m.Type == t { + out = append(out, m) + } + } + return out +} + +type Mount struct { + Type string `json:"type,omitempty"` + Source string `json:"source,omitempty"` // Source path, in the host namespace + Destination string `json:"destination,omitempty"` // Destination path, in the container + Writable bool `json:"writable,omitempty"` + Private bool `json:"private,omitempty"` +} + // namespaceList is used to convert the libcontainer types // into the names of the files located in /proc//ns/* for // each namespace From 7a0b3610664c2269fd5932f294adae72e6e54020 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 15:28:56 +0000 Subject: [PATCH 14/37] Move capabilities into security pkg Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/nsinit/init.go | 2 +- pkg/libcontainer/{ => security}/capabilities/capabilities.go | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename pkg/libcontainer/{ => security}/capabilities/capabilities.go (100%) diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 9aac9a40e..22fdfbeeb 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -11,11 +11,11 @@ import ( "github.com/dotcloud/docker/pkg/apparmor" "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" - "github.com/dotcloud/docker/pkg/libcontainer/capabilities" "github.com/dotcloud/docker/pkg/libcontainer/console" "github.com/dotcloud/docker/pkg/libcontainer/mount" "github.com/dotcloud/docker/pkg/libcontainer/network" "github.com/dotcloud/docker/pkg/libcontainer/security/apparmor" + "github.com/dotcloud/docker/pkg/libcontainer/security/capabilities" "github.com/dotcloud/docker/pkg/libcontainer/utils" "github.com/dotcloud/docker/pkg/system" "github.com/dotcloud/docker/pkg/user" diff --git a/pkg/libcontainer/capabilities/capabilities.go b/pkg/libcontainer/security/capabilities/capabilities.go similarity index 100% rename from pkg/libcontainer/capabilities/capabilities.go rename to pkg/libcontainer/security/capabilities/capabilities.go From 2d6c3674349c09318e8d1fb3ce43dbabc15c97da Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 15:31:45 +0000 Subject: [PATCH 15/37] Increment native driver version with these changes Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- daemon/execdriver/native/driver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index 31a2eb0da..8b374d993 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -23,7 +23,7 @@ import ( const ( DriverName = "native" - Version = "0.1" + Version = "0.2" BackupApparmorProfilePath = "apparmor/docker.back" // relative to docker root ) From 2d31aeb911fc94baa88f975110c5ccd45d041acb Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Apr 2014 15:44:11 +0000 Subject: [PATCH 16/37] Update container.json and readme Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/README.md | 193 ++++++++++++++++++++++---------- pkg/libcontainer/container.json | 192 +++++++++++++++++++++++-------- 2 files changed, 277 insertions(+), 108 deletions(-) diff --git a/pkg/libcontainer/README.md b/pkg/libcontainer/README.md index d6d0fbae4..31031b26c 100644 --- a/pkg/libcontainer/README.md +++ b/pkg/libcontainer/README.md @@ -16,76 +16,149 @@ process are specified in this file. The configuration is used for each process Sample `container.json` file: ```json { + "mounts" : [ + { + "type" : "devtmpfs" + } + ], + "tty" : true, + "environment" : [ + "HOME=/", + "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin", + "container=docker", + "TERM=xterm-256color" + ], "hostname" : "koye", + "cgroups" : { + "parent" : "docker", + "name" : "docker-koye" + }, + "capabilities_mask" : [ + { + "value" : 8, + "key" : "SETPCAP", + "enabled" : false + }, + { + "enabled" : false, + "value" : 16, + "key" : "SYS_MODULE" + }, + { + "value" : 17, + "key" : "SYS_RAWIO", + "enabled" : false + }, + { + "key" : "SYS_PACCT", + "value" : 20, + "enabled" : false + }, + { + "value" : 21, + "key" : "SYS_ADMIN", + "enabled" : false + }, + { + "value" : 23, + "key" : "SYS_NICE", + "enabled" : false + }, + { + "value" : 24, + "key" : "SYS_RESOURCE", + "enabled" : false + }, + { + "key" : "SYS_TIME", + "value" : 25, + "enabled" : false + }, + { + "enabled" : false, + "value" : 26, + "key" : "SYS_TTY_CONFIG" + }, + { + "key" : "AUDIT_WRITE", + "value" : 29, + "enabled" : false + }, + { + "value" : 30, + "key" : "AUDIT_CONTROL", + "enabled" : false + }, + { + "enabled" : false, + "key" : "MAC_OVERRIDE", + "value" : 32 + }, + { + "enabled" : false, + "key" : "MAC_ADMIN", + "value" : 33 + }, + { + "key" : "NET_ADMIN", + "value" : 12, + "enabled" : false + }, + { + "value" : 27, + "key" : "MKNOD", + "enabled" : true + } + ], "networks" : [ { - "gateway" : "172.17.42.1", + "mtu" : 1500, + "address" : "127.0.0.1/0", + "type" : "loopback", + "gateway" : "localhost" + }, + { + "mtu" : 1500, + "address" : "172.17.42.2/16", + "type" : "veth", "context" : { "bridge" : "docker0", "prefix" : "veth" }, - "address" : "172.17.0.2/16", - "type" : "veth", - "mtu" : 1500 - } - ], - "cgroups" : { - "parent" : "docker", - "name" : "11bb30683fb0bdd57fab4d3a8238877f1e4395a2cfc7320ea359f7a02c1a5620" - }, - "tty" : true, - "environment" : [ - "HOME=/", - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "HOSTNAME=11bb30683fb0", - "TERM=xterm" - ], - "capabilities_mask" : [ - "SETPCAP", - "SYS_MODULE", - "SYS_RAWIO", - "SYS_PACCT", - "SYS_ADMIN", - "SYS_NICE", - "SYS_RESOURCE", - "SYS_TIME", - "SYS_TTY_CONFIG", - "MKNOD", - "AUDIT_WRITE", - "AUDIT_CONTROL", - "MAC_OVERRIDE", - "MAC_ADMIN", - "NET_ADMIN" - ], - "context" : { - "apparmor_profile" : "docker-default" - }, - "mounts" : [ - { - "source" : "/var/lib/docker/containers/11bb30683fb0bdd57fab4d3a8238877f1e4395a2cfc7320ea359f7a02c1a5620/resolv.conf", - "writable" : false, - "destination" : "/etc/resolv.conf", - "private" : true - }, - { - "source" : "/var/lib/docker/containers/11bb30683fb0bdd57fab4d3a8238877f1e4395a2cfc7320ea359f7a02c1a5620/hostname", - "writable" : false, - "destination" : "/etc/hostname", - "private" : true - }, - { - "source" : "/var/lib/docker/containers/11bb30683fb0bdd57fab4d3a8238877f1e4395a2cfc7320ea359f7a02c1a5620/hosts", - "writable" : false, - "destination" : "/etc/hosts", - "private" : true + "gateway" : "172.17.42.1" } ], "namespaces" : [ - "NEWNS", - "NEWUTS", - "NEWIPC", - "NEWPID", - "NEWNET" + { + "key" : "NEWNS", + "value" : 131072, + "enabled" : true, + "file" : "mnt" + }, + { + "key" : "NEWUTS", + "value" : 67108864, + "enabled" : true, + "file" : "uts" + }, + { + "enabled" : true, + "file" : "ipc", + "key" : "NEWIPC", + "value" : 134217728 + }, + { + "file" : "pid", + "enabled" : true, + "value" : 536870912, + "key" : "NEWPID" + }, + { + "enabled" : true, + "file" : "net", + "key" : "NEWNET", + "value" : 1073741824 + } ] } ``` diff --git a/pkg/libcontainer/container.json b/pkg/libcontainer/container.json index f045315a4..f15a49ab0 100644 --- a/pkg/libcontainer/container.json +++ b/pkg/libcontainer/container.json @@ -1,50 +1,146 @@ { - "hostname": "koye", - "tty": true, - "environment": [ - "HOME=/", - "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin", - "container=docker", - "TERM=xterm-256color" - ], - "namespaces": [ - "NEWIPC", - "NEWNS", - "NEWPID", - "NEWUTS", - "NEWNET" - ], - "capabilities_mask": [ - "SETPCAP", - "SYS_MODULE", - "SYS_RAWIO", - "SYS_PACCT", - "SYS_ADMIN", - "SYS_NICE", - "SYS_RESOURCE", - "SYS_TIME", - "SYS_TTY_CONFIG", - "MKNOD", - "AUDIT_WRITE", - "AUDIT_CONTROL", - "MAC_OVERRIDE", - "MAC_ADMIN", - "NET_ADMIN" - ], - "networks": [{ - "type": "veth", - "context": { - "bridge": "docker0", - "prefix": "dock" - }, - "address": "172.17.0.100/16", - "gateway": "172.17.42.1", - "mtu": 1500 - } - ], - "cgroups": { - "name": "docker-koye", - "parent": "docker", - "memory": 5248000 - } + "mounts" : [ + { + "type" : "devtmpfs" + } + ], + "tty" : true, + "environment" : [ + "HOME=/", + "PATH=PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin", + "container=docker", + "TERM=xterm-256color" + ], + "hostname" : "koye", + "cgroups" : { + "parent" : "docker", + "name" : "docker-koye" + }, + "capabilities_mask" : [ + { + "value" : 8, + "key" : "SETPCAP", + "enabled" : false + }, + { + "enabled" : false, + "value" : 16, + "key" : "SYS_MODULE" + }, + { + "value" : 17, + "key" : "SYS_RAWIO", + "enabled" : false + }, + { + "key" : "SYS_PACCT", + "value" : 20, + "enabled" : false + }, + { + "value" : 21, + "key" : "SYS_ADMIN", + "enabled" : false + }, + { + "value" : 23, + "key" : "SYS_NICE", + "enabled" : false + }, + { + "value" : 24, + "key" : "SYS_RESOURCE", + "enabled" : false + }, + { + "key" : "SYS_TIME", + "value" : 25, + "enabled" : false + }, + { + "enabled" : false, + "value" : 26, + "key" : "SYS_TTY_CONFIG" + }, + { + "key" : "AUDIT_WRITE", + "value" : 29, + "enabled" : false + }, + { + "value" : 30, + "key" : "AUDIT_CONTROL", + "enabled" : false + }, + { + "enabled" : false, + "key" : "MAC_OVERRIDE", + "value" : 32 + }, + { + "enabled" : false, + "key" : "MAC_ADMIN", + "value" : 33 + }, + { + "key" : "NET_ADMIN", + "value" : 12, + "enabled" : false + }, + { + "value" : 27, + "key" : "MKNOD", + "enabled" : true + } + ], + "networks" : [ + { + "mtu" : 1500, + "address" : "127.0.0.1/0", + "type" : "loopback", + "gateway" : "localhost" + }, + { + "mtu" : 1500, + "address" : "172.17.42.2/16", + "type" : "veth", + "context" : { + "bridge" : "docker0", + "prefix" : "veth" + }, + "gateway" : "172.17.42.1" + } + ], + "namespaces" : [ + { + "key" : "NEWNS", + "value" : 131072, + "enabled" : true, + "file" : "mnt" + }, + { + "key" : "NEWUTS", + "value" : 67108864, + "enabled" : true, + "file" : "uts" + }, + { + "enabled" : true, + "file" : "ipc", + "key" : "NEWIPC", + "value" : 134217728 + }, + { + "file" : "pid", + "enabled" : true, + "value" : 536870912, + "key" : "NEWPID" + }, + { + "enabled" : true, + "file" : "net", + "key" : "NEWNET", + "value" : 1073741824 + } + ] } From 90678b31331de54598c7a6665c3e7a78bfe6ed63 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 18 Apr 2014 14:35:16 -0700 Subject: [PATCH 17/37] Update create with apparmor import Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- integration-cli/docker_cli_run_test.go | 277 +++++++++++++++++++++++++ 1 file changed, 277 insertions(+) diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 40781294a..5973f2fe1 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -389,6 +389,283 @@ func TestMultipleVolumesFrom(t *testing.T) { logDone("run - multiple volumes from") } +// this tests verifies the ID format for the container +func TestVerifyContainerID(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") + out, exit, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err) + } + if exit != 0 { + t.Fatalf("expected exit code 0 received %d", exit) + } + match, err := regexp.MatchString("^[0-9a-f]{64}$", strings.TrimSuffix(out, "\n")) + if err != nil { + t.Fatal(err) + } + if !match { + t.Fatalf("Invalid container ID: %s", out) + } + + deleteAllContainers() + + logDone("run - verify container ID") +} + +// Test that creating a container with a volume doesn't crash. Regression test for #995. +func TestCreateVolume(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "-v", "/var/lib/data", "busybox", "true") + if _, err := runCommand(cmd); err != nil { + t.Fatal(err) + } + + deleteAllContainers() + + logDone("run - create docker mangaed volume") +} + +func TestExitCode(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "busybox", "/bin/sh", "-c", "exit 72") + + exit, err := runCommand(cmd) + if err == nil { + t.Fatal("should not have a non nil error") + } + if exit != 72 { + t.Fatalf("expected exit code 72 received %d", exit) + } + + deleteAllContainers() + + logDone("run - correct exit code") +} + +func TestUserDefaultsToRoot(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "busybox", "id") + + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + if !strings.Contains(out, "uid=0(root) gid=0(root)") { + t.Fatalf("expected root user got %s", out) + } + deleteAllContainers() + + logDone("run - default user") +} + +func TestUserByName(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "-u", "root", "busybox", "id") + + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + if !strings.Contains(out, "uid=0(root) gid=0(root)") { + t.Fatalf("expected root user got %s", out) + } + deleteAllContainers() + + logDone("run - user by name") +} + +func TestUserByID(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "-u", "1", "busybox", "id") + + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + if !strings.Contains(out, "uid=1(daemon) gid=1(daemon)") { + t.Fatalf("expected daemon user got %s", out) + } + deleteAllContainers() + + logDone("run - user by id") +} + +func TestUserNotFound(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "-u", "notme", "busybox", "id") + + _, err := runCommand(cmd) + if err == nil { + t.Fatal("unknown user should cause container to fail") + } + deleteAllContainers() + + logDone("run - user not found") +} + +func TestRunTwoConcurrentContainers(t *testing.T) { + group := sync.WaitGroup{} + group.Add(2) + + for i := 0; i < 2; i++ { + go func() { + defer group.Done() + cmd := exec.Command(dockerBinary, "run", "busybox", "sleep", "2") + if _, err := runCommand(cmd); err != nil { + t.Fatal(err) + } + }() + } + + group.Wait() + + deleteAllContainers() + + logDone("run - two concurrent containers") +} + +func TestEnvironment(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "-h", "testing", "-e=FALSE=true", "-e=TRUE", "-e=TRICKY", "busybox", "env") + cmd.Env = append(os.Environ(), + "TRUE=false", + "TRICKY=tri\ncky\n", + ) + + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + + actualEnv := strings.Split(out, "\n") + if actualEnv[len(actualEnv)-1] == "" { + actualEnv = actualEnv[:len(actualEnv)-1] + } + sort.Strings(actualEnv) + + goodEnv := []string{ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "HOME=/", + "HOSTNAME=testing", + "FALSE=true", + "TRUE=false", + "TRICKY=tri", + "cky", + "", + } + sort.Strings(goodEnv) + if len(goodEnv) != len(actualEnv) { + t.Fatalf("Wrong environment: should be %d variables, not: '%s'\n", len(goodEnv), strings.Join(actualEnv, ", ")) + } + for i := range goodEnv { + if actualEnv[i] != goodEnv[i] { + t.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i]) + } + } + + deleteAllContainers() + + logDone("run - verify environment") +} + +func TestContainerNetwork(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "busybox", "ping", "-c", "1", "127.0.0.1") + if _, err := runCommand(cmd); err != nil { + t.Fatal(err) + } + + deleteAllContainers() + + logDone("run - test container network via ping") +} + +// Issue #4681 +func TestLoopbackWhenNetworkDisabled(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--networking=false", "busybox", "ping", "-c", "1", "127.0.0.1") + if _, err := runCommand(cmd); err != nil { + t.Fatal(err) + } + + deleteAllContainers() + + logDone("run - test container loopback when networking disabled") +} + +func TestLoopbackOnlyExistsWhenNetworkingDisabled(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--networking=false", "busybox", "ip", "a", "show", "up") + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + + interfaces := regexp.MustCompile(`(?m)^[0-9]+: [a-zA-Z0-9]+`).FindAllString(out, -1) + if len(interfaces) != 1 { + t.Fatalf("Wrong interface count in test container: expected [*: lo], got %s", interfaces) + } + if !strings.HasSuffix(interfaces[0], ": lo") { + t.Fatalf("Wrong interface in test container: expected [*: lo], got %s", interfaces) + } + + deleteAllContainers() + + logDone("run - test loopback only exists when networking disabled") +} + +func TestPrivilegedCanMknod(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err) + } + + if actual := strings.Trim(out, "\r\n"); actual != "ok" { + t.Fatalf("expected output ok received %s", actual) + } + deleteAllContainers() + + logDone("run - test privileged can mknod") +} + +func TestUnPrivilegedCanMknod(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err) + } + + if actual := strings.Trim(out, "\r\n"); actual != "ok" { + t.Fatalf("expected output ok received %s", actual) + } + deleteAllContainers() + + logDone("run - test un-privileged can mknod") +} + +func TestPrivilegedCanMount(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "sh", "-c", "mount -t tmpfs none /tmp && echo ok") + + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err) + } + + if actual := strings.Trim(out, "\r\n"); actual != "ok" { + t.Fatalf("expected output ok received %s", actual) + } + deleteAllContainers() + + logDone("run - test privileged can mount") +} + +func TestUnPrivilegedCannotMount(t *testing.T) { + cmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "mount -t tmpfs none /tmp && echo ok") + + out, _, err := runCommandWithOutput(cmd) + if err == nil { + t.Fatal(err, out) + } + + if actual := strings.Trim(out, "\r\n"); actual == "ok" { + t.Fatalf("expected output not ok received %s", actual) + } + deleteAllContainers() + + logDone("run - test un-privileged cannot mount") +} + func TestSysNotAvaliableInNonPrivilegedContainers(t *testing.T) { cmd := exec.Command(dockerBinary, "run", "busybox", "ls", "/sys/kernel") if code, err := runCommand(cmd); err == nil || code == 0 { From fa5cabf9fe9e257d64638043ca2fd08a7bf96cb3 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 21 Apr 2014 12:07:07 -0700 Subject: [PATCH 18/37] Update init for new apparmor import path Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/nsinit/init.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 22fdfbeeb..67095fdba 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -14,7 +14,6 @@ import ( "github.com/dotcloud/docker/pkg/libcontainer/console" "github.com/dotcloud/docker/pkg/libcontainer/mount" "github.com/dotcloud/docker/pkg/libcontainer/network" - "github.com/dotcloud/docker/pkg/libcontainer/security/apparmor" "github.com/dotcloud/docker/pkg/libcontainer/security/capabilities" "github.com/dotcloud/docker/pkg/libcontainer/utils" "github.com/dotcloud/docker/pkg/system" From d5c9f61ecc1c8167322a8cc3b41f29a35c80b9b8 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 23 Apr 2014 18:12:07 -0700 Subject: [PATCH 19/37] Ignore isnot exists errors for proc paths Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/libcontainer/security/restrict/restrict.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/libcontainer/security/restrict/restrict.go b/pkg/libcontainer/security/restrict/restrict.go index d5c1dbbe2..291d6ca5d 100644 --- a/pkg/libcontainer/security/restrict/restrict.go +++ b/pkg/libcontainer/security/restrict/restrict.go @@ -2,9 +2,11 @@ package restrict import ( "fmt" - "github.com/dotcloud/docker/pkg/system" + "os" "path/filepath" "syscall" + + "github.com/dotcloud/docker/pkg/system" ) const flags = syscall.MS_BIND | syscall.MS_REC | syscall.MS_RDONLY @@ -36,6 +38,9 @@ func Restrict(rootfs, empty string) error { source = filepath.Join(rootfs, source) } if err := system.Mount(source, dest, "bind", flags, ""); err != nil { + if os.IsNotExist(err) { + continue + } return fmt.Errorf("unable to mount %s over %s %s", source, dest, err) } if err := system.Mount("", dest, "bind", flags|syscall.MS_REMOUNT, ""); err != nil { From ad924959a9879af7477fa23c19d570882c1f378f Mon Sep 17 00:00:00 2001 From: Victor Marmol Date: Thu, 24 Apr 2014 22:59:37 +0000 Subject: [PATCH 20/37] Add memory usage and max usage stats. Docker-DCO-1.1-Signed-off-by: Victor Marmol (github: vmarmol) --- pkg/cgroups/fs/memory.go | 22 ++++++++++-- pkg/cgroups/fs/utils.go | 11 ++++++ pkg/cgroups/fs/utils_test.go | 68 ++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 pkg/cgroups/fs/utils_test.go diff --git a/pkg/cgroups/fs/memory.go b/pkg/cgroups/fs/memory.go index 531529119..837640c08 100644 --- a/pkg/cgroups/fs/memory.go +++ b/pkg/cgroups/fs/memory.go @@ -2,6 +2,7 @@ package fs import ( "bufio" + "fmt" "os" "path/filepath" "strconv" @@ -56,13 +57,14 @@ func (s *memoryGroup) Stats(d *data) (map[string]float64, error) { return nil, err } - f, err := os.Open(filepath.Join(path, "memory.stat")) + // Set stats from memory.stat. + statsFile, err := os.Open(filepath.Join(path, "memory.stat")) if err != nil { return nil, err } - defer f.Close() + defer statsFile.Close() - sc := bufio.NewScanner(f) + sc := bufio.NewScanner(statsFile) for sc.Scan() { t, v, err := getCgroupParamKeyValue(sc.Text()) if err != nil { @@ -70,5 +72,19 @@ func (s *memoryGroup) Stats(d *data) (map[string]float64, error) { } paramData[t] = v } + + // Set memory usage and max historical usage. + params := []string{ + "usage_in_bytes", + "max_usage_in_bytes", + } + for _, param := range params { + value, err := getCgroupParamFloat64(path, fmt.Sprintf("memory.%s", param)) + if err != nil { + return nil, err + } + paramData[param] = value + } + return paramData, nil } diff --git a/pkg/cgroups/fs/utils.go b/pkg/cgroups/fs/utils.go index f4c4846b8..8be65c97e 100644 --- a/pkg/cgroups/fs/utils.go +++ b/pkg/cgroups/fs/utils.go @@ -3,6 +3,8 @@ package fs import ( "errors" "fmt" + "io/ioutil" + "path/filepath" "strconv" "strings" ) @@ -27,3 +29,12 @@ func getCgroupParamKeyValue(t string) (string, float64, error) { return "", 0.0, ErrNotValidFormat } } + +// Gets a single float64 value from the specified cgroup file. +func getCgroupParamFloat64(cgroupPath, cgroupFile string) (float64, error) { + contents, err := ioutil.ReadFile(filepath.Join(cgroupPath, cgroupFile)) + if err != nil { + return -1.0, err + } + return strconv.ParseFloat(strings.TrimSpace(string(contents)), 64) +} diff --git a/pkg/cgroups/fs/utils_test.go b/pkg/cgroups/fs/utils_test.go new file mode 100644 index 000000000..c8f1b0172 --- /dev/null +++ b/pkg/cgroups/fs/utils_test.go @@ -0,0 +1,68 @@ +package fs + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" +) + +const ( + cgroupFile = "cgroup.file" + floatValue = 2048.0 + floatString = "2048" +) + +func TestGetCgroupParamsFloat64(t *testing.T) { + // Setup tempdir. + tempDir, err := ioutil.TempDir("", "cgroup_utils_test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tempDir) + tempFile := filepath.Join(tempDir, cgroupFile) + + // Success. + err = ioutil.WriteFile(tempFile, []byte(floatString), 0755) + if err != nil { + t.Fatal(err) + } + value, err := getCgroupParamFloat64(tempDir, cgroupFile) + if err != nil { + t.Fatal(err) + } else if value != floatValue { + t.Fatalf("Expected %f to equal %f", value, floatValue) + } + + // Success with new line. + err = ioutil.WriteFile(tempFile, []byte(floatString+"\n"), 0755) + if err != nil { + t.Fatal(err) + } + value, err = getCgroupParamFloat64(tempDir, cgroupFile) + if err != nil { + t.Fatal(err) + } else if value != floatValue { + t.Fatalf("Expected %f to equal %f", value, floatValue) + } + + // Not a float. + err = ioutil.WriteFile(tempFile, []byte("not-a-float"), 0755) + if err != nil { + t.Fatal(err) + } + _, err = getCgroupParamFloat64(tempDir, cgroupFile) + if err == nil { + t.Fatal("Expecting error, got none") + } + + // Unknown file. + err = os.Remove(tempFile) + if err != nil { + t.Fatal(err) + } + _, err = getCgroupParamFloat64(tempDir, cgroupFile) + if err == nil { + t.Fatal("Expecting error, got none") + } +} From c7bd1f4e648c6615eafebbfcaeb1c30020fc0aba Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Fri, 25 Apr 2014 20:36:31 +1000 Subject: [PATCH 21/37] small api doc formatting fixup Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- .../reference/api/docker_remote_api.md | 144 +++++++++--------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/docs/sources/reference/api/docker_remote_api.md b/docs/sources/reference/api/docker_remote_api.md index 3c58b1b99..a6aafbeee 100644 --- a/docs/sources/reference/api/docker_remote_api.md +++ b/docs/sources/reference/api/docker_remote_api.md @@ -127,83 +127,83 @@ entry for each repo/tag on an image, each image is only represented once, with a nested attribute indicating the repo/tags that apply to that image. - Instead of: +Instead of: - HTTP/1.1 200 OK - Content-Type: application/json + HTTP/1.1 200 OK + Content-Type: application/json - [ - { - "VirtualSize": 131506275, - "Size": 131506275, - "Created": 1365714795, - "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", - "Tag": "12.04", - "Repository": "ubuntu" - }, - { - "VirtualSize": 131506275, - "Size": 131506275, - "Created": 1365714795, - "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", - "Tag": "latest", - "Repository": "ubuntu" - }, - { - "VirtualSize": 131506275, - "Size": 131506275, - "Created": 1365714795, - "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", - "Tag": "precise", - "Repository": "ubuntu" - }, - { - "VirtualSize": 180116135, - "Size": 24653, - "Created": 1364102658, - "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", - "Tag": "12.10", - "Repository": "ubuntu" - }, - { - "VirtualSize": 180116135, - "Size": 24653, - "Created": 1364102658, - "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", - "Tag": "quantal", - "Repository": "ubuntu" - } - ] + [ + { + "VirtualSize": 131506275, + "Size": 131506275, + "Created": 1365714795, + "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", + "Tag": "12.04", + "Repository": "ubuntu" + }, + { + "VirtualSize": 131506275, + "Size": 131506275, + "Created": 1365714795, + "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", + "Tag": "latest", + "Repository": "ubuntu" + }, + { + "VirtualSize": 131506275, + "Size": 131506275, + "Created": 1365714795, + "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", + "Tag": "precise", + "Repository": "ubuntu" + }, + { + "VirtualSize": 180116135, + "Size": 24653, + "Created": 1364102658, + "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "Tag": "12.10", + "Repository": "ubuntu" + }, + { + "VirtualSize": 180116135, + "Size": 24653, + "Created": 1364102658, + "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "Tag": "quantal", + "Repository": "ubuntu" + } + ] - The returned json looks like this: +The returned json looks like this: - HTTP/1.1 200 OK - Content-Type: application/json + HTTP/1.1 200 OK + Content-Type: application/json - [ - { - "RepoTags": [ - "ubuntu:12.04", - "ubuntu:precise", - "ubuntu:latest" - ], - "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", - "Created": 1365714795, - "Size": 131506275, - "VirtualSize": 131506275 - }, - { - "RepoTags": [ - "ubuntu:12.10", - "ubuntu:quantal" - ], - "ParentId": "27cf784147099545", - "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", - "Created": 1364102658, - "Size": 24653, - "VirtualSize": 180116135 - } - ] + [ + { + "RepoTags": [ + "ubuntu:12.04", + "ubuntu:precise", + "ubuntu:latest" + ], + "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", + "Created": 1365714795, + "Size": 131506275, + "VirtualSize": 131506275 + }, + { + "RepoTags": [ + "ubuntu:12.10", + "ubuntu:quantal" + ], + "ParentId": "27cf784147099545", + "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "Created": 1364102658, + "Size": 24653, + "VirtualSize": 180116135 + } + ] `GET /images/viz` From 9422451ac3f541a17daba0d5f6dc8b40a6edc9e9 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 22 Apr 2014 16:51:06 -0700 Subject: [PATCH 22/37] engine.Installer: a standard interface for "installable" services Installer is a standard interface for objects which can "install" themselves an engine by registering handlers. This can be used as an entrypoint for external plugins etc. Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- engine/engine.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/engine/engine.go b/engine/engine.go index 58c37ab93..aaf5c1f59 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -10,6 +10,13 @@ import ( "strings" ) +// Installer is a standard interface for objects which can "install" themselves +// on an engine by registering handlers. +// This can be used as an entrypoint for external plugins etc. +type Installer interface { + Install(*Engine) error +} + type Handler func(*Job) Status var globalHandlers map[string]Handler From 68d3e757503fab422fc96a00d511336a3fdfd619 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 24 Apr 2014 00:36:21 -0700 Subject: [PATCH 23/37] engine: allow registering a "catchall" handler which receives all commands Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- engine/engine.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/engine/engine.go b/engine/engine.go index aaf5c1f59..dc1984ccb 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -43,6 +43,7 @@ func unregister(name string) { // containers by executing *jobs*. type Engine struct { handlers map[string]Handler + catchall Handler hack Hack // data for temporary hackery (see hack.go) id string Stdout io.Writer @@ -60,6 +61,10 @@ func (eng *Engine) Register(name string, handler Handler) error { return nil } +func (eng *Engine) RegisterCatchall(catchall Handler) { + eng.catchall = catchall +} + // New initializes a new engine. func New() *Engine { eng := &Engine{ @@ -113,9 +118,13 @@ func (eng *Engine) Job(name string, args ...string) *Job { if eng.Logging { job.Stderr.Add(utils.NopWriteCloser(eng.Stderr)) } - handler, exists := eng.handlers[name] - if exists { - job.handler = handler + if eng.catchall != nil { + job.handler = eng.catchall + } else { + handler, exists := eng.handlers[name] + if exists { + job.handler = handler + } } return job } From 3c1d5ca33ecbd644d4e8d864ff59b389f4a4a555 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 24 Apr 2014 00:46:32 -0700 Subject: [PATCH 24/37] Remote communication between engines using beam Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- engine/remote.go | 109 ++++++++++++++++++++++++++++++++++++++++++ engine/remote_test.go | 3 ++ 2 files changed, 112 insertions(+) create mode 100644 engine/remote.go create mode 100644 engine/remote_test.go diff --git a/engine/remote.go b/engine/remote.go new file mode 100644 index 000000000..1e8777a4b --- /dev/null +++ b/engine/remote.go @@ -0,0 +1,109 @@ +package engine + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/beam" + "github.com/dotcloud/docker/pkg/beam/data" + "io" + "os" + "strconv" + "sync" +) + +type Sender struct { + beam.Sender +} + +func NewSender(s beam.Sender) *Sender { + return &Sender{s} +} + +func (s *Sender) Install(eng *Engine) error { + // FIXME: this doesn't exist yet. + eng.RegisterCatchall(s.Handle) + return nil +} + +func (s *Sender) Handle(job *Job) Status { + msg := data.Empty().Set("cmd", append([]string{job.Name}, job.Args...)...) + peer, err := beam.SendConn(s, msg.Bytes()) + if err != nil { + return job.Errorf("beamsend: %v", err) + } + defer peer.Close() + var tasks sync.WaitGroup + defer tasks.Wait() + r := beam.NewRouter(nil) + r.NewRoute().KeyStartsWith("cmd", "log", "stdout").HasAttachment().Handler(func(p []byte, stdout *os.File) error { + tasks.Add(1) + io.Copy(job.Stdout, stdout) + tasks.Done() + return nil + }) + r.NewRoute().KeyStartsWith("cmd", "log", "stderr").HasAttachment().Handler(func(p []byte, stderr *os.File) error { + tasks.Add(1) + io.Copy(job.Stderr, stderr) + tasks.Done() + return nil + }) + var status int + r.NewRoute().KeyStartsWith("cmd", "status").Handler(func(p []byte, f *os.File) error { + cmd := data.Message(p).Get("cmd") + if len(cmd) != 3 { + return fmt.Errorf("usage: %s <0-127>", cmd[0]) + } + s, err := strconv.ParseUint(cmd[2], 10, 8) + if err != nil { + return fmt.Errorf("usage: %s <0-127>", cmd[0]) + } + status = int(s) + return nil + + }) + if _, err := beam.Copy(r, peer); err != nil { + return job.Errorf("%v", err) + } + return Status(status) +} + +type Receiver struct { + *Engine + peer beam.Receiver +} + +func NewReceiver(peer beam.Receiver) *Receiver { + return &Receiver{Engine: New(), peer: peer} +} + +func (rcv *Receiver) Run() error { + r := beam.NewRouter(nil) + r.NewRoute().KeyExists("cmd").Handler(func(p []byte, f *os.File) error { + // Use the attachment as a beam return channel + peer, err := beam.FileConn(f) + if err != nil { + f.Close() + return err + } + cmd := data.Message(p).Get("cmd") + job := rcv.Engine.Job(cmd[0], cmd[1:]...) + stdout, err := beam.SendPipe(peer, data.Empty().Set("cmd", "log", "stdout").Bytes()) + if err != nil { + return err + } + job.Stdout.Add(stdout) + stderr, err := beam.SendPipe(peer, data.Empty().Set("cmd", "log", "stderr").Bytes()) + if err != nil { + return err + } + job.Stderr.Add(stderr) + // ignore error because we pass the raw status + job.Run() + err = peer.Send(data.Empty().Set("cmd", "status", fmt.Sprintf("%d", job.status)).Bytes(), nil) + if err != nil { + return err + } + return nil + }) + _, err := beam.Copy(r, rcv.peer) + return err +} diff --git a/engine/remote_test.go b/engine/remote_test.go new file mode 100644 index 000000000..54092ec93 --- /dev/null +++ b/engine/remote_test.go @@ -0,0 +1,3 @@ +package engine + +import () From b63b98ee2766321e2ca6f3b159c2bfb303870105 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 25 Apr 2014 16:47:03 -0700 Subject: [PATCH 25/37] engine.Sender and engine.Receiver support stdin Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- engine/remote.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/engine/remote.go b/engine/remote.go index 1e8777a4b..48638e438 100644 --- a/engine/remote.go +++ b/engine/remote.go @@ -46,6 +46,12 @@ func (s *Sender) Handle(job *Job) Status { tasks.Done() return nil }) + r.NewRoute().KeyStartsWith("cmd", "log", "stdin").HasAttachment().Handler(func(p []byte, stdin *os.File) error { + tasks.Add(1) + io.Copy(stdin, job.Stdin) + tasks.Done() + return nil + }) var status int r.NewRoute().KeyStartsWith("cmd", "status").Handler(func(p []byte, f *os.File) error { cmd := data.Message(p).Get("cmd") @@ -96,6 +102,11 @@ func (rcv *Receiver) Run() error { return err } job.Stderr.Add(stderr) + stdin, err := beam.SendPipe(peer, data.Empty().Set("cmd", "log", "stdin").Bytes()) + if err != nil { + return err + } + job.Stdin.Add(stdin) // ignore error because we pass the raw status job.Run() err = peer.Send(data.Empty().Set("cmd", "status", fmt.Sprintf("%d", job.status)).Bytes(), nil) From 7e3624a498b8b96a4e8a0f1d59fc2c50bf48efb3 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 25 Apr 2014 16:48:16 -0700 Subject: [PATCH 26/37] engine: 'rengine' is a small command-line utility to debug remote engine Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- engine/rengine/main.go | 43 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 engine/rengine/main.go diff --git a/engine/rengine/main.go b/engine/rengine/main.go new file mode 100644 index 000000000..b4fa01d39 --- /dev/null +++ b/engine/rengine/main.go @@ -0,0 +1,43 @@ +package main + +import ( + "fmt" + "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/pkg/beam" + "net" + "os" +) + +func main() { + eng := engine.New() + + c, err := net.Dial("unix", "beam.sock") + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + return + } + defer c.Close() + f, err := c.(*net.UnixConn).File() + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + return + } + + child, err := beam.FileConn(f) + if err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + return + } + defer child.Close() + + sender := engine.NewSender(child) + sender.Install(eng) + + cmd := eng.Job(os.Args[1], os.Args[2:]...) + cmd.Stdout.Add(os.Stdout) + cmd.Stderr.Add(os.Stderr) + if err := cmd.Run(); err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } +} From f4055ee2a467458d4848ad6f8e79fc0162d377f9 Mon Sep 17 00:00:00 2001 From: Victor Marmol Date: Fri, 25 Apr 2014 17:44:40 +0000 Subject: [PATCH 27/37] Adding a test for blkio stats. Also adds a test utility we can use for other cgroup tests. Docker-DCO-1.1-Signed-off-by: Victor Marmol (github: vmarmol) --- pkg/cgroups/fs/blkio_test.go | 169 +++++++++++++++++++++++++++++++++++ pkg/cgroups/fs/test_util.go | 75 ++++++++++++++++ 2 files changed, 244 insertions(+) create mode 100644 pkg/cgroups/fs/blkio_test.go create mode 100644 pkg/cgroups/fs/test_util.go diff --git a/pkg/cgroups/fs/blkio_test.go b/pkg/cgroups/fs/blkio_test.go new file mode 100644 index 000000000..5279ac437 --- /dev/null +++ b/pkg/cgroups/fs/blkio_test.go @@ -0,0 +1,169 @@ +package fs + +import ( + "testing" +) + +const ( + sectorsRecursiveContents = `8:0 1024` + serviceBytesRecursiveContents = `8:0 Read 100 +8:0 Write 400 +8:0 Sync 200 +8:0 Async 300 +8:0 Total 500 +Total 500` + servicedRecursiveContents = `8:0 Read 10 +8:0 Write 40 +8:0 Sync 20 +8:0 Async 30 +8:0 Total 50 +Total 50` + queuedRecursiveContents = `8:0 Read 1 +8:0 Write 4 +8:0 Sync 2 +8:0 Async 3 +8:0 Total 5 +Total 5` +) + +func TestBlkioStats(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + }) + + blkio := &blkioGroup{} + stats, err := blkio.Stats(helper.CgroupData) + if err != nil { + t.Fatal(err) + } + + // Verify expected stats. + expectedStats := map[string]float64{ + "blkio.sectors_recursive:8:0": 1024.0, + + // Serviced bytes. + "io_service_bytes_recursive:8:0:Read": 100.0, + "io_service_bytes_recursive:8:0:Write": 400.0, + "io_service_bytes_recursive:8:0:Sync": 200.0, + "io_service_bytes_recursive:8:0:Async": 300.0, + "io_service_bytes_recursive:8:0:Total": 500.0, + + // Serviced requests. + "io_serviced_recursive:8:0:Read": 10.0, + "io_serviced_recursive:8:0:Write": 40.0, + "io_serviced_recursive:8:0:Sync": 20.0, + "io_serviced_recursive:8:0:Async": 30.0, + "io_serviced_recursive:8:0:Total": 50.0, + + // Queued requests. + "io_queued_recursive:8:0:Read": 1.0, + "io_queued_recursive:8:0:Write": 4.0, + "io_queued_recursive:8:0:Sync": 2.0, + "io_queued_recursive:8:0:Async": 3.0, + "io_queued_recursive:8:0:Total": 5.0, + } + expectStats(t, expectedStats, stats) +} + +func TestBlkioStatsNoSectorsFile(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + }) + + blkio := &blkioGroup{} + _, err := blkio.Stats(helper.CgroupData) + if err == nil { + t.Fatal("Expected to fail, but did not") + } +} + +func TestBlkioStatsNoServiceBytesFile(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + }) + + blkio := &blkioGroup{} + _, err := blkio.Stats(helper.CgroupData) + if err == nil { + t.Fatal("Expected to fail, but did not") + } +} + +func TestBlkioStatsNoServicedFile(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + }) + + blkio := &blkioGroup{} + _, err := blkio.Stats(helper.CgroupData) + if err == nil { + t.Fatal("Expected to fail, but did not") + } +} + +func TestBlkioStatsNoQueuedFile(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": serviceBytesRecursiveContents, + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + }) + + blkio := &blkioGroup{} + _, err := blkio.Stats(helper.CgroupData) + if err == nil { + t.Fatal("Expected to fail, but did not") + } +} + +func TestBlkioStatsUnexpectedNumberOfFields(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": "8:0 Read 100 100", + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + }) + + blkio := &blkioGroup{} + _, err := blkio.Stats(helper.CgroupData) + if err == nil { + t.Fatal("Expected to fail, but did not") + } +} + +func TestBlkioStatsUnexpectedFieldType(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + helper.writeFileContents(map[string]string{ + "blkio.io_service_bytes_recursive": "8:0 Read Write", + "blkio.io_serviced_recursive": servicedRecursiveContents, + "blkio.io_queued_recursive": queuedRecursiveContents, + "blkio.sectors_recursive": sectorsRecursiveContents, + }) + + blkio := &blkioGroup{} + _, err := blkio.Stats(helper.CgroupData) + if err == nil { + t.Fatal("Expected to fail, but did not") + } +} diff --git a/pkg/cgroups/fs/test_util.go b/pkg/cgroups/fs/test_util.go new file mode 100644 index 000000000..11b90b21d --- /dev/null +++ b/pkg/cgroups/fs/test_util.go @@ -0,0 +1,75 @@ +/* +Utility for testing cgroup operations. + +Creates a mock of the cgroup filesystem for the duration of the test. +*/ +package fs + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "testing" +) + +type cgroupTestUtil struct { + // data to use in tests. + CgroupData *data + + // Path to the mock cgroup directory. + CgroupPath string + + // Temporary directory to store mock cgroup filesystem. + tempDir string + t *testing.T +} + +// Creates a new test util for the specified subsystem +func NewCgroupTestUtil(subsystem string, t *testing.T) *cgroupTestUtil { + d := &data{} + tempDir, err := ioutil.TempDir("", fmt.Sprintf("%s_cgroup_test", subsystem)) + if err != nil { + t.Fatal(err) + } + d.root = tempDir + testCgroupPath, err := d.path(subsystem) + if err != nil { + t.Fatal(err) + } + + // Ensure the full mock cgroup path exists. + err = os.MkdirAll(testCgroupPath, 0755) + if err != nil { + t.Fatal(err) + } + return &cgroupTestUtil{CgroupData: d, CgroupPath: testCgroupPath, tempDir: tempDir, t: t} +} + +func (c *cgroupTestUtil) cleanup() { + os.RemoveAll(c.tempDir) +} + +// Write the specified contents on the mock of the specified cgroup files. +func (c *cgroupTestUtil) writeFileContents(fileContents map[string]string) { + for file, contents := range fileContents { + err := writeFile(c.CgroupPath, file, contents) + if err != nil { + c.t.Fatal(err) + } + } +} + +// Expect the specified stats. +func expectStats(t *testing.T, expected, actual map[string]float64) { + for stat, expectedValue := range expected { + actualValue, ok := actual[stat] + if !ok { + log.Printf("Expected stat %s to exist: %s", stat, actual) + t.Fail() + } else if actualValue != expectedValue { + log.Printf("Expected stats %s to have value %f but had %f instead", stat, expectedValue, actualValue) + t.Fail() + } + } +} From 7744f63159cee9cfa5d9e9307e5c29a4f589837f Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 26 Apr 2014 08:10:19 -0400 Subject: [PATCH 28/37] Updated Docs README Docker-DCO-1.1-Signed-off-by: James Turnbull (github: jamtur01) --- docs/README.md | 74 ++++++++++++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 39 deletions(-) diff --git a/docs/README.md b/docs/README.md index a113cb9ed..e5474d524 100755 --- a/docs/README.md +++ b/docs/README.md @@ -7,64 +7,60 @@ Overview The source for Docker documentation is here under ``sources/`` and uses extended Markdown, as implemented by [mkdocs](http://mkdocs.org). -The HTML files are built and hosted on https://docs.docker.io, and update -automatically after each change to the master or release branch of the -[docker files on GitHub](https://github.com/dotcloud/docker) thanks to -post-commit hooks. The "release" branch maps to the "latest" -documentation and the "master" (unreleased development) branch maps to the "master" -documentation. +The HTML files are built and hosted on `https://docs.docker.io`, and +update automatically after each change to the master or release branch +of [Docker on GitHub](https://github.com/dotcloud/docker) +thanks to post-commit hooks. The "docs" branch maps to the "latest" +documentation and the "master" (unreleased development) branch maps to +the "master" documentation. ## Branches **There are two branches related to editing docs**: ``master`` and a -``docs`` branch. You should always edit -docs on a local branch of the ``master`` branch, and send a PR against ``master``. -That way your fixes -will automatically get included in later releases, and docs maintainers -can easily cherry-pick your changes into the ``docs`` release branch. -In the rare case where your change is not forward-compatible, -you may need to base your changes on the ``docs`` branch. +``docs`` branch. You should always edit documentation on a local branch +of the ``master`` branch, and send a PR against ``master``. -Now that we have a ``docs`` branch, we can keep the [http://docs.docker.io](http://docs.docker.io) docs -up to date with any bugs found between ``docker`` code releases. +That way your fixes will automatically get included in later releases, +and docs maintainers can easily cherry-pick your changes into the +``docs`` release branch. In the rare case where your change is not +forward-compatible, you may need to base your changes on the ``docs`` +branch. -**Warning**: When *reading* the docs, the [http://beta-docs.docker.io](http://beta-docs.docker.io) documentation may -include features not yet part of any official docker -release. The ``beta-docs`` site should be used only for understanding -bleeding-edge development and ``docs.docker.io`` (which points to the ``docs`` +Also, now that we have a ``docs`` branch, we can keep the +[http://docs.docker.io](http://docs.docker.io) docs up to date with any +bugs found between ``docker`` code releases. + +**Warning**: When *reading* the docs, the +[http://beta-docs.docker.io](http://beta-docs.docker.io) documentation +may include features not yet part of any official docker release. The +``beta-docs`` site should be used only for understanding bleeding-edge +development and ``docs.docker.io`` (which points to the ``docs`` branch``) should be used for the latest official release. Getting Started --------------- -Docker documentation builds are done in a docker container, which installs all -the required tools, adds the local ``docs/`` directory and builds the HTML -docs. It then starts a HTTP server on port 8000 so that you can connect -and see your changes. +Docker documentation builds are done in a Docker container, which +installs all the required tools, adds the local ``docs/`` directory and +builds the HTML docs. It then starts a HTTP server on port 8000 so that +you can connect and see your changes. -In the ``docker`` source directory, run: - ```make docs``` +In the root of the ``docker`` source directory: + + cd docker + +Run: + + make docs If you have any issues you need to debug, you can use ``make docs-shell`` and then run ``mkdocs serve`` # Contributing -## Normal Case: - * Follow the contribution guidelines ([see ``../CONTRIBUTING.md``](../CONTRIBUTING.md)). * [Remember to sign your work!](../CONTRIBUTING.md#sign-your-work) -* Work in your own fork of the code, we accept pull requests. -* Change the ``.md`` files with your favorite editor -- try to keep the - lines short (80 chars) and respect Markdown conventions. -* Run ``make clean docs`` to clean up old files and generate new ones, - or just ``make docs`` to update after small changes. -* Your static website can now be found in the ``_build`` directory. -* To preview what you have generated run ``make server`` and open - http://localhost:8000/ in your favorite browser. - -``make clean docs`` must complete without any warnings or errors. Working using GitHub's file editor ---------------------------------- @@ -87,7 +83,7 @@ Publishing Documentation ------------------------ To publish a copy of the documentation you need a ``docs/awsconfig`` -file containing AWS settings to deploy to. The release script will +file containing AWS settings to deploy to. The release script will create an s3 if needed, and will then push the files to it. ``` @@ -97,7 +93,7 @@ aws_secret_access_key = OIUYSADJHLKUHQWIUHE...... region = ap-southeast-2 ``` -The ``profile`` name must be the same as the name of the bucket you are +The ``profile`` name must be the same as the name of the bucket you are deploying to - which you call from the docker directory: ``make AWS_S3_BUCKET=dowideit-docs docs-release`` From df018bc8012ad4181ac16e1329c970fe213f1f87 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 26 Apr 2014 09:48:07 -0400 Subject: [PATCH 29/37] Fixed Dockerise to Dockerize Docker-DCO-1.1-Signed-off-by: James Turnbull (github: jamtur01) --- docs/sources/introduction/understanding-docker.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/introduction/understanding-docker.md b/docs/sources/introduction/understanding-docker.md index 1c979d581..5920da5bc 100644 --- a/docs/sources/introduction/understanding-docker.md +++ b/docs/sources/introduction/understanding-docker.md @@ -87,14 +87,14 @@ to you. *Docker is made for humans.* It's easy to get started and easy to build and deploy applications with -Docker: or as we say "*dockerise*" them! As much of Docker as possible +Docker: or as we say "*dockerize*" them! As much of Docker as possible uses plain English for commands and tries to be as lightweight and transparent as possible. We want to get out of the way so you can build and deploy your applications. ### Docker is Portable -*Dockerise And Go!* +*Dockerize And Go!* Docker containers are highly portable. Docker provides a standard container format to hold your applications: From 723d314f097fe7b1e15f27c1d6f05794b6c5f411 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 26 Apr 2014 09:53:17 -0400 Subject: [PATCH 30/37] Replaced all double backticks in README with singles Docker-DCO-1.1-Signed-off-by: James Turnbull (github: jamtur01) --- docs/README.md | 56 ++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/docs/README.md b/docs/README.md index e5474d524..bbc741d59 100755 --- a/docs/README.md +++ b/docs/README.md @@ -4,7 +4,7 @@ Docker Documentation Overview -------- -The source for Docker documentation is here under ``sources/`` and uses +The source for Docker documentation is here under `sources/` and uses extended Markdown, as implemented by [mkdocs](http://mkdocs.org). The HTML files are built and hosted on `https://docs.docker.io`, and @@ -16,36 +16,36 @@ the "master" documentation. ## Branches -**There are two branches related to editing docs**: ``master`` and a -``docs`` branch. You should always edit documentation on a local branch -of the ``master`` branch, and send a PR against ``master``. +**There are two branches related to editing docs**: `master` and a +`docs` branch. You should always edit documentation on a local branch +of the `master` branch, and send a PR against `master`. That way your fixes will automatically get included in later releases, and docs maintainers can easily cherry-pick your changes into the -``docs`` release branch. In the rare case where your change is not -forward-compatible, you may need to base your changes on the ``docs`` +`docs` release branch. In the rare case where your change is not +forward-compatible, you may need to base your changes on the `docs` branch. -Also, now that we have a ``docs`` branch, we can keep the +Also, now that we have a `docs` branch, we can keep the [http://docs.docker.io](http://docs.docker.io) docs up to date with any -bugs found between ``docker`` code releases. +bugs found between `docker` code releases. **Warning**: When *reading* the docs, the [http://beta-docs.docker.io](http://beta-docs.docker.io) documentation may include features not yet part of any official docker release. The -``beta-docs`` site should be used only for understanding bleeding-edge -development and ``docs.docker.io`` (which points to the ``docs`` -branch``) should be used for the latest official release. +`beta-docs` site should be used only for understanding bleeding-edge +development and `docs.docker.io` (which points to the `docs` +branch`) should be used for the latest official release. Getting Started --------------- Docker documentation builds are done in a Docker container, which -installs all the required tools, adds the local ``docs/`` directory and +installs all the required tools, adds the local `docs/` directory and builds the HTML docs. It then starts a HTTP server on port 8000 so that you can connect and see your changes. -In the root of the ``docker`` source directory: +In the root of the `docker` source directory: cd docker @@ -53,13 +53,13 @@ Run: make docs -If you have any issues you need to debug, you can use ``make docs-shell`` and -then run ``mkdocs serve`` +If you have any issues you need to debug, you can use `make docs-shell` and +then run `mkdocs serve` # Contributing * Follow the contribution guidelines ([see - ``../CONTRIBUTING.md``](../CONTRIBUTING.md)). + `../CONTRIBUTING.md`](../CONTRIBUTING.md)). * [Remember to sign your work!](../CONTRIBUTING.md#sign-your-work) Working using GitHub's file editor @@ -67,7 +67,7 @@ Working using GitHub's file editor Alternatively, for small changes and typos you might want to use GitHub's built in file editor. It allows you to preview your changes -right online (though there can be some differences between GitHub +right on-line (though there can be some differences between GitHub Markdown and mkdocs Markdown). Just be careful not to create many commits. And you must still [sign your work!](../CONTRIBUTING.md#sign-your-work) @@ -75,26 +75,24 @@ Images ------ When you need to add images, try to make them as small as possible -(e.g. as gif). Usually images should go in the same directory as the -.md file which references them, or in a subdirectory if one already +(e.g. as gifs). Usually images should go in the same directory as the +`.md` file which references them, or in a subdirectory if one already exists. Publishing Documentation ------------------------ -To publish a copy of the documentation you need a ``docs/awsconfig`` +To publish a copy of the documentation you need a `docs/awsconfig` file containing AWS settings to deploy to. The release script will create an s3 if needed, and will then push the files to it. -``` -[profile dowideit-docs] -aws_access_key_id = IHOIUAHSIDH234rwf.... -aws_secret_access_key = OIUYSADJHLKUHQWIUHE...... -region = ap-southeast-2 -``` + [profile dowideit-docs] + aws_access_key_id = IHOIUAHSIDH234rwf.... + aws_secret_access_key = OIUYSADJHLKUHQWIUHE...... + region = ap-southeast-2 -The ``profile`` name must be the same as the name of the bucket you are -deploying to - which you call from the docker directory: +The `profile` name must be the same as the name of the bucket you are +deploying to - which you call from the `docker` directory: -``make AWS_S3_BUCKET=dowideit-docs docs-release`` + make AWS_S3_BUCKET=dowideit-docs docs-release From e83fc70d362bf0035811bbcc5566020c6cc9893f Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 25 Apr 2014 13:21:28 -0700 Subject: [PATCH 31/37] Freeze ./integration and explain where to contribute new tests Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- integration/MAINTAINERS | 4 ++++ integration/README.md | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 integration/MAINTAINERS create mode 100644 integration/README.md diff --git a/integration/MAINTAINERS b/integration/MAINTAINERS new file mode 100644 index 000000000..2d47d7a71 --- /dev/null +++ b/integration/MAINTAINERS @@ -0,0 +1,4 @@ +Solomon Hykes +# WE ARE LOOKING FOR VOLUNTEERS TO HELP CLEAN THIS UP. +# TO VOLUNTEER PLEASE OPEN A PULL REQUEST ADDING YOURSELF TO THIS FILE. +# WE WILL HELP YOU GET STARTED. THANKS! diff --git a/integration/README.md b/integration/README.md new file mode 100644 index 000000000..41f43a4ba --- /dev/null +++ b/integration/README.md @@ -0,0 +1,23 @@ +## Legacy integration tests + +`./integration` contains Docker's legacy integration tests. +It is DEPRECATED and will eventually be removed. + +### If you are a *CONTRIBUTOR* and want to add a test: + +* Consider mocking out side effects and contributing a *unit test* in the subsystem +you're modifying. For example, the remote API has unit tests in `./api/server/server_unit_tests.go`. +The events subsystem has unit tests in `./events/events_test.go`. And so on. + +* For end-to-end integration tests, please contribute to `./integration-cli`. + + +### If you are a *MAINTAINER* + +Please don't allow patches adding new tests to `./integration`. + +### If you are *LOOKING FOR A WAY TO HELP* + +Please consider porting tests away from `./integration` and into either unit tests or CLI tests. + +Any help will be greatly appreciated! From 91deb591c8a52a974612b4c6885d989251f819bd Mon Sep 17 00:00:00 2001 From: Soulou Date: Tue, 11 Mar 2014 09:30:51 +0000 Subject: [PATCH 32/37] [Documentation - API] Add missing 'signal' parameter for /containers/:id/kill endpoint Docker-DCO-1.1-Signed-off-by: Leo Unbekandt (github: Soulou) --- .../sources/reference/api/archive/docker_remote_api_v1.7.rst | 1 + .../sources/reference/api/archive/docker_remote_api_v1.8.rst | 1 + docs/sources/reference/api/docker_remote_api_v1.10.md | 5 +++++ docs/sources/reference/api/docker_remote_api_v1.10.rst | 1 + docs/sources/reference/api/docker_remote_api_v1.11.md | 5 +++++ docs/sources/reference/api/docker_remote_api_v1.11.rst | 1 + docs/sources/reference/api/docker_remote_api_v1.9.md | 5 +++++ docs/sources/reference/api/docker_remote_api_v1.9.rst | 1 + 8 files changed, 20 insertions(+) diff --git a/docs/sources/reference/api/archive/docker_remote_api_v1.7.rst b/docs/sources/reference/api/archive/docker_remote_api_v1.7.rst index 1bafaddfc..7a4f688d8 100644 --- a/docs/sources/reference/api/archive/docker_remote_api_v1.7.rst +++ b/docs/sources/reference/api/archive/docker_remote_api_v1.7.rst @@ -454,6 +454,7 @@ Kill a container HTTP/1.1 204 OK + :query signal: Signal to send to the container (integer). When not set, SIGKILL is assumed and the call will waits for the container to exit. :statuscode 204: no error :statuscode 404: no such container :statuscode 500: server error diff --git a/docs/sources/reference/api/archive/docker_remote_api_v1.8.rst b/docs/sources/reference/api/archive/docker_remote_api_v1.8.rst index 16492dde7..4f1b266bb 100644 --- a/docs/sources/reference/api/archive/docker_remote_api_v1.8.rst +++ b/docs/sources/reference/api/archive/docker_remote_api_v1.8.rst @@ -482,6 +482,7 @@ Kill a container HTTP/1.1 204 OK + :query signal: Signal to send to the container (integer). When not set, SIGKILL is assumed and the call will waits for the container to exit. :statuscode 204: no error :statuscode 404: no such container :statuscode 500: server error diff --git a/docs/sources/reference/api/docker_remote_api_v1.10.md b/docs/sources/reference/api/docker_remote_api_v1.10.md index c07f96f38..749ff8e38 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.10.md +++ b/docs/sources/reference/api/docker_remote_api_v1.10.md @@ -459,6 +459,11 @@ Kill the container `id` HTTP/1.1 204 OK + Query Parameters + + - **signal** - Signal to send to the container: integer or string like "SIGINT". + When not set, SIGKILL is assumed and the call will waits for the container to exit. + Status Codes: - **204** – no error diff --git a/docs/sources/reference/api/docker_remote_api_v1.10.rst b/docs/sources/reference/api/docker_remote_api_v1.10.rst index 83e2c3c15..8635ec482 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.10.rst +++ b/docs/sources/reference/api/docker_remote_api_v1.10.rst @@ -466,6 +466,7 @@ Kill a container HTTP/1.1 204 OK + :query signal: Signal to send to the container, must be integer or string (i.e. SIGINT). When not set, SIGKILL is assumed and the call will waits for the container to exit. :statuscode 204: no error :statuscode 404: no such container :statuscode 500: server error diff --git a/docs/sources/reference/api/docker_remote_api_v1.11.md b/docs/sources/reference/api/docker_remote_api_v1.11.md index 5e3fdcb0a..baabade45 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.11.md +++ b/docs/sources/reference/api/docker_remote_api_v1.11.md @@ -461,6 +461,11 @@ Kill the container `id` HTTP/1.1 204 OK + Query Parameters + + - **signal** - Signal to send to the container: integer or string like "SIGINT". + When not set, SIGKILL is assumed and the call will waits for the container to exit. + Status Codes: - **204** – no error diff --git a/docs/sources/reference/api/docker_remote_api_v1.11.rst b/docs/sources/reference/api/docker_remote_api_v1.11.rst index 556491c49..d66b4b141 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.11.rst +++ b/docs/sources/reference/api/docker_remote_api_v1.11.rst @@ -468,6 +468,7 @@ Kill a container HTTP/1.1 204 OK + :query signal: Signal to send to the container, must be integer or string (i.e. SIGINT). When not set, SIGKILL is assumed and the call will waits for the container to exit. :statuscode 204: no error :statuscode 404: no such container :statuscode 500: server error diff --git a/docs/sources/reference/api/docker_remote_api_v1.9.md b/docs/sources/reference/api/docker_remote_api_v1.9.md index 74e85a7ee..1fe154a3d 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.9.md +++ b/docs/sources/reference/api/docker_remote_api_v1.9.md @@ -482,6 +482,11 @@ Kill the container `id` HTTP/1.1 204 OK + Query Parameters + + - **signal** - Signal to send to the container: integer or string like "SIGINT". + When not set, SIGKILL is assumed and the call will waits for the container to exit. + Status Codes: - **204** – no error diff --git a/docs/sources/reference/api/docker_remote_api_v1.9.rst b/docs/sources/reference/api/docker_remote_api_v1.9.rst index 4bbffcbd3..db0e3bfda 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.9.rst +++ b/docs/sources/reference/api/docker_remote_api_v1.9.rst @@ -480,6 +480,7 @@ Kill a container HTTP/1.1 204 OK + :query signal: Signal to send to the container, must be integer or string (i.e. SIGINT). When not set, SIGKILL is assumed and the call will waits for the container to exit. :statuscode 204: no error :statuscode 404: no such container :statuscode 500: server error From c6060a3b25b94ae4738ac27bbf62cbb914af7ced Mon Sep 17 00:00:00 2001 From: Josh Hawn Date: Sun, 27 Apr 2014 11:17:48 -0700 Subject: [PATCH 33/37] Added back OAuth and Accounts API docs pages Removed a now unused endpoint from the accounts API. Updated some of the accounts links to point to www.docker.io as the account signup and resend-email-confirmation links should no longer point to the index. Docker-DCO-1.1-Signed-off-by: Josh Hawn (github: jlhawn) --- docs/mkdocs.yml | 2 + docs/sources/index/accounts.md | 4 +- .../reference/api/docker_io_accounts_api.md | 73 +------------------ 3 files changed, 5 insertions(+), 74 deletions(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 353864271..0b526e016 100755 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -106,6 +106,8 @@ pages: - ['reference/api/docker_remote_api_v1.10.md', 'Reference', 'Docker Remote API v1.10'] - ['reference/api/docker_remote_api_v1.9.md', 'Reference', 'Docker Remote API v1.9'] - ['reference/api/remote_api_client_libraries.md', 'Reference', 'Docker Remote API Client Libraries'] +- ['reference/api/docker_io_oauth_api.md', 'Reference', 'Docker IO OAuth API'] +- ['reference/api/docker_io_accounts_api.md', 'Reference', 'Docker IO Accounts API'] # Contribute: - ['contributing/index.md', '**HIDDEN**'] diff --git a/docs/sources/index/accounts.md b/docs/sources/index/accounts.md index c3138b61d..54d015ac2 100644 --- a/docs/sources/index/accounts.md +++ b/docs/sources/index/accounts.md @@ -14,7 +14,7 @@ to need a [Docker IO](https://www.docker.io) account. ### Registration for a Docker IO Account You can get a Docker IO account by [signing up for one here]( -https://index.docker.io/account/signup/). A valid email address is required to +https://www.docker.io/account/signup/). A valid email address is required to register, which you will need to verify for account activation. ### Email activation process @@ -22,7 +22,7 @@ register, which you will need to verify for account activation. You need to have at least one verified email address to be able to use your Docker IO account. If you can't find the validation email, you can request another by visiting the [Resend Email Confirmation]( -https://index.docker.io/account/resend-email-confirmation/) page. +https://www.docker.io/account/resend-email-confirmation/) page. ### Password reset process diff --git a/docs/sources/reference/api/docker_io_accounts_api.md b/docs/sources/reference/api/docker_io_accounts_api.md index 8186e306f..b9f76ba92 100644 --- a/docs/sources/reference/api/docker_io_accounts_api.md +++ b/docs/sources/reference/api/docker_io_accounts_api.md @@ -237,78 +237,7 @@ automatically sent. "primary": false } -### 1.5 Update an email address for a user - -`PATCH /api/v1.1/users/:username/emails/` - -Update an email address for the specified user to either verify an -email address or set it as the primary email for the user. You -cannot use this endpoint to un-verify an email address. You cannot -use this endpoint to unset the primary email, only set another as -the primary. - - Parameters: - - - **username** – username of the user whose email info is being - updated. - - Json Parameters: - -   - - - **email** (*string*) – the email address to be updated. - - **verified** (*boolean*) – (optional) whether the email address - is verified, must be `true` or absent. - - **primary** (*boolean*) – (optional) whether to set the email - address as the primary email, must be `true` - or absent. - - Request Headers: - -   - - - **Authorization** – required authentication credentials of - either type HTTP Basic or OAuth Bearer Token. - - **Content-Type** – MIME Type of post data. JSON, url-encoded - form data, etc. - - Status Codes: - - - **200** – success, user's email updated. - - **400** – data validation error. - - **401** – authentication error. - - **403** – permission error, authenticated user must be the user - whose data is being updated, OAuth access tokens must have - `email_write` scope. - - **404** – the specified username or email address does not - exist. - - **Example request**: - - Once you have independently verified an email address. - - PATCH /api/v1.1/users/janedoe/emails/ HTTP/1.1 - Host: www.docker.io - Accept: application/json - Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ= - - { - "email": "jane.doe+other@example.com", - "verified": true, - } - - **Example response**: - - HTTP/1.1 200 OK - Content-Type: application/json - - { - "email": "jane.doe+other@example.com", - "verified": true, - "primary": false - } - -### 1.6 Delete email address for a user +### 1.5 Delete email address for a user `DELETE /api/v1.1/users/:username/emails/` From 9236e088eb5a9a6d662b08ef7983dbecf01e6ef0 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 26 Apr 2014 18:24:39 -0700 Subject: [PATCH 34/37] Fix bug in engine.Sender Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- engine/remote.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/remote.go b/engine/remote.go index 48638e438..60aad243c 100644 --- a/engine/remote.go +++ b/engine/remote.go @@ -55,10 +55,10 @@ func (s *Sender) Handle(job *Job) Status { var status int r.NewRoute().KeyStartsWith("cmd", "status").Handler(func(p []byte, f *os.File) error { cmd := data.Message(p).Get("cmd") - if len(cmd) != 3 { + if len(cmd) != 2 { return fmt.Errorf("usage: %s <0-127>", cmd[0]) } - s, err := strconv.ParseUint(cmd[2], 10, 8) + s, err := strconv.ParseUint(cmd[1], 10, 8) if err != nil { return fmt.Errorf("usage: %s <0-127>", cmd[0]) } From b4b83ef8ae788cb7e016fbf90f0c1b890af7b23d Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 26 Apr 2014 18:47:20 -0700 Subject: [PATCH 35/37] engine/spawn: run an engine in a subprocess, remote-controlled by Beam Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- engine/spawn/spawn.go | 119 +++++++++++++++++++++++++++++++++ engine/spawn/subengine/main.go | 61 +++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 engine/spawn/spawn.go create mode 100644 engine/spawn/subengine/main.go diff --git a/engine/spawn/spawn.go b/engine/spawn/spawn.go new file mode 100644 index 000000000..6680845bc --- /dev/null +++ b/engine/spawn/spawn.go @@ -0,0 +1,119 @@ +package spawn + +import ( + "fmt" + "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/pkg/beam" + "github.com/dotcloud/docker/utils" + "os" + "os/exec" +) + +var initCalled bool + +// Init checks if the current process has been created by Spawn. +// +// If no, it returns nil and the original program can continue +// unmodified. +// +// If no, it hijacks the process to run as a child worker controlled +// by its parent over a beam connection, with f exposed as a remote +// service. In this case Init never returns. +// +// The hijacking process takes place as follows: +// - Open file descriptor 3 as a beam endpoint. If this fails, +// terminate the current process. +// - Start a new engine. +// - Call f.Install on the engine. Any handlers registered +// will be available for remote invocation by the parent. +// - Listen for beam messages from the parent and pass them to +// the handlers. +// - When the beam endpoint is closed by the parent, terminate +// the current process. +// +// NOTE: Init must be called at the beginning of the same program +// calling Spawn. This is because Spawn approximates a "fork" by +// re-executing the current binary - where it expects spawn.Init +// to intercept the control flow and execute the worker code. +func Init(f engine.Installer) error { + initCalled = true + if os.Getenv("ENGINESPAWN") != "1" { + return nil + } + fmt.Printf("[%d child]\n", os.Getpid()) + // Hijack the process + childErr := func() error { + fd3 := os.NewFile(3, "beam-introspect") + introsp, err := beam.FileConn(fd3) + if err != nil { + return fmt.Errorf("beam introspection error: %v", err) + } + fd3.Close() + defer introsp.Close() + eng := engine.NewReceiver(introsp) + if err := f.Install(eng.Engine); err != nil { + return err + } + if err := eng.Run(); err != nil { + return err + } + return nil + }() + if childErr != nil { + os.Exit(1) + } + os.Exit(0) + return nil // Never reached +} + +// Spawn starts a new Engine in a child process and returns +// a proxy Engine through which it can be controlled. +// +// The commands available on the child engine are determined +// by an earlier call to Init. It is important that Init be +// called at the very beginning of the current program - this +// allows it to be called as a re-execution hook in the child +// process. +// +// Long story short, if you want to expose `myservice` in a child +// process, do this: +// +// func main() { +// spawn.Init(myservice) +// [..] +// child, err := spawn.Spawn() +// [..] +// child.Job("dosomething").Run() +// } +func Spawn() (*engine.Engine, error) { + if !initCalled { + return nil, fmt.Errorf("spawn.Init must be called at the top of the main() function") + } + cmd := exec.Command(utils.SelfPath()) + cmd.Env = append(cmd.Env, "ENGINESPAWN=1") + local, remote, err := beam.SocketPair() + if err != nil { + return nil, err + } + child, err := beam.FileConn(local) + if err != nil { + local.Close() + remote.Close() + return nil, err + } + local.Close() + cmd.ExtraFiles = append(cmd.ExtraFiles, remote) + // FIXME: the beam/engine glue has no way to inform the caller + // of the child's termination. The next call will simply return + // an error. + if err := cmd.Start(); err != nil { + child.Close() + return nil, err + } + eng := engine.New() + if err := engine.NewSender(child).Install(eng); err != nil { + child.Close() + return nil, err + } + return eng, nil +} diff --git a/engine/spawn/subengine/main.go b/engine/spawn/subengine/main.go new file mode 100644 index 000000000..3be7520a6 --- /dev/null +++ b/engine/spawn/subengine/main.go @@ -0,0 +1,61 @@ +package main + +import ( + "fmt" + "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/engine/spawn" + "log" + "os" + "os/exec" + "strings" +) + +func main() { + fmt.Printf("[%d] MAIN\n", os.Getpid()) + spawn.Init(&Worker{}) + fmt.Printf("[%d parent] spawning\n", os.Getpid()) + eng, err := spawn.Spawn() + if err != nil { + log.Fatal(err) + } + fmt.Printf("[parent] spawned\n") + job := eng.Job(os.Args[1], os.Args[2:]...) + job.Stdout.Add(os.Stdout) + job.Stderr.Add(os.Stderr) + job.Run() + // FIXME: use the job's status code + os.Exit(0) +} + +type Worker struct { +} + +func (w *Worker) Install(eng *engine.Engine) error { + eng.Register("exec", w.Exec) + eng.Register("cd", w.Cd) + eng.Register("echo", w.Echo) + return nil +} + +func (w *Worker) Exec(job *engine.Job) engine.Status { + fmt.Printf("--> %v\n", job.Args) + cmd := exec.Command(job.Args[0], job.Args[1:]...) + cmd.Stdout = job.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return job.Errorf("%v\n", err) + } + return engine.StatusOK +} + +func (w *Worker) Cd(job *engine.Job) engine.Status { + if err := os.Chdir(job.Args[0]); err != nil { + return job.Errorf("%v\n", err) + } + return engine.StatusOK +} + +func (w *Worker) Echo(job *engine.Job) engine.Status { + fmt.Fprintf(job.Stdout, "%s\n", strings.Join(job.Args, " ")) + return engine.StatusOK +} From 9b23178f585f65b9dc9558694d6de4207c479801 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sun, 27 Apr 2014 23:57:41 -0700 Subject: [PATCH 36/37] engine.Len returns the number of keys in an env Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- engine/env.go | 7 +++++++ engine/env_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/engine/env.go b/engine/env.go index c43a5ec97..f96795f48 100644 --- a/engine/env.go +++ b/engine/env.go @@ -36,6 +36,13 @@ func (env *Env) Exists(key string) bool { return exists } +// Len returns the number of keys in the environment. +// Note that len(env) might be different from env.Len(), +// because the same key might be set multiple times. +func (env *Env) Len() int { + return len(env.Map()) +} + func (env *Env) Init(src *Env) { (*env) = make([]string, 0, len(*src)) for _, val := range *src { diff --git a/engine/env_test.go b/engine/env_test.go index c7079ff94..0c66cea04 100644 --- a/engine/env_test.go +++ b/engine/env_test.go @@ -4,6 +4,34 @@ import ( "testing" ) +func TestEnvLenZero(t *testing.T) { + env := &Env{} + if env.Len() != 0 { + t.Fatalf("%d", env.Len()) + } +} + +func TestEnvLenNotZero(t *testing.T) { + env := &Env{} + env.Set("foo", "bar") + env.Set("ga", "bu") + if env.Len() != 2 { + t.Fatalf("%d", env.Len()) + } +} + +func TestEnvLenDup(t *testing.T) { + env := &Env{ + "foo=bar", + "foo=baz", + "a=b", + } + // len(env) != env.Len() + if env.Len() != 2 { + t.Fatalf("%d", env.Len()) + } +} + func TestNewJob(t *testing.T) { job := mkJob(t, "dummy", "--level=awesome") if job.Name != "dummy" { From ff7b52abd3f26d9650c2e674400d58fbe8157ad8 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Wed, 9 Apr 2014 21:50:46 -0400 Subject: [PATCH 37/37] Fixes permissions on volumes when dir in container is empty Docker-DCO-1.1-Signed-off-by: Brian Goff (github: cpuguy83) --- daemon/volumes.go | 30 +++++++++++++++--------------- integration/container_test.go | 28 +++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/daemon/volumes.go b/daemon/volumes.go index 4a5c4475b..19bb7cab3 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -246,22 +246,22 @@ func createVolumes(container *Container) error { if err := archive.CopyWithTar(rootVolPath, srcPath); err != nil { return err } + } + } - var stat syscall.Stat_t - if err := syscall.Stat(rootVolPath, &stat); err != nil { - return err - } - var srcStat syscall.Stat_t - if err := syscall.Stat(srcPath, &srcStat); err != nil { - return err - } - // Change the source volume's ownership if it differs from the root - // files that were just copied - if stat.Uid != srcStat.Uid || stat.Gid != srcStat.Gid { - if err := os.Chown(srcPath, int(stat.Uid), int(stat.Gid)); err != nil { - return err - } - } + var stat syscall.Stat_t + if err := syscall.Stat(rootVolPath, &stat); err != nil { + return err + } + var srcStat syscall.Stat_t + if err := syscall.Stat(srcPath, &srcStat); err != nil { + return err + } + // Change the source volume's ownership if it differs from the root + // files that were just copied + if stat.Uid != srcStat.Uid || stat.Gid != srcStat.Gid { + if err := os.Chown(srcPath, int(stat.Uid), int(stat.Gid)); err != nil { + return err } } } diff --git a/integration/container_test.go b/integration/container_test.go index 67b2783ce..8fe52a3cd 100644 --- a/integration/container_test.go +++ b/integration/container_test.go @@ -407,7 +407,7 @@ func TestCopyVolumeUidGid(t *testing.T) { defer r.Nuke() // Add directory not owned by root - container1, _, _ := mkContainer(r, []string{"_", "/bin/sh", "-c", "mkdir -p /hello && touch /hello/test.txt && chown daemon.daemon /hello"}, t) + container1, _, _ := mkContainer(r, []string{"_", "/bin/sh", "-c", "mkdir -p /hello && touch /hello/test && chown daemon.daemon /hello"}, t) defer r.Destroy(container1) if container1.State.IsRunning() { @@ -432,6 +432,32 @@ func TestCopyVolumeUidGid(t *testing.T) { if !strings.Contains(stdout1, "daemon daemon") { t.Fatal("Container failed to transfer uid and gid to volume") } + + container2, _, _ := mkContainer(r, []string{"_", "/bin/sh", "-c", "mkdir -p /hello && chown daemon.daemon /hello"}, t) + defer r.Destroy(container1) + + if container2.State.IsRunning() { + t.Errorf("Container shouldn't be running") + } + if err := container2.Run(); err != nil { + t.Fatal(err) + } + if container2.State.IsRunning() { + t.Errorf("Container shouldn't be running") + } + + img2, err := r.Commit(container2, "", "", "unit test commited image", "", nil) + if err != nil { + t.Error(err) + } + + // Test that the uid and gid is copied from the image to the volume + tmpDir2 := tempDir(t) + defer os.RemoveAll(tmpDir2) + stdout2, _ := runContainer(eng, r, []string{"-v", "/hello", img2.ID, "stat", "-c", "%U %G", "/hello"}, t) + if !strings.Contains(stdout2, "daemon daemon") { + t.Fatal("Container failed to transfer uid and gid to volume") + } } // Test for #1582