Compare commits

..

63 Commits

Author SHA1 Message Date
Victor Vieux 53f1bf0f99 Bump version to v0.6.7 2013-11-21 18:03:41 -08:00
Victor Vieux 9dc59797e0 Merge branch 'master' into bump_v0.6.7 2013-11-21 17:40:29 -08:00
Michael Crosby 25e443a3c7 Merge pull request #2798 from dotcloud/fix_state_race
Refactor State to be 100% thread safe
2013-11-21 16:39:50 -08:00
Guillaume J. Charmes 33e70864a2 Refactor State to be 100% thread safe 2013-11-21 16:34:58 -08:00
Michael Crosby d6e6214d37 Merge pull request #2678 from cxmcc/minor
Minor code simplification for Containers api
2013-11-21 16:27:15 -08:00
Michael Crosby 70f1bd3104 Merge pull request #2488 from viirya/fix_container_volumes_delete
Skip the volumes mounted when deleting the volumes of container.
2013-11-21 16:20:22 -08:00
Michael Crosby f7c2a00557 Merge pull request #2304 from unclejack/fix_layer_size_computation
Fix layer size computation: handle hard links correctly
2013-11-21 16:10:24 -08:00
Victor Vieux 8498b44eac Merge pull request #2731 from SvenDowideit/docker-commit-doc
make the docker commit help more copy&pasteable
2013-11-21 15:33:05 -08:00
Andy Rothfusz e2dcfc2cf7 Merge pull request #2772 from bitoiu/patch-3
Change to documentation for AWS AMI request
2013-11-21 15:15:04 -08:00
Andy Rothfusz 9b4c151142 Merge pull request #2717 from metalivedev/2342-uploadingcontext
Fix #2342. Harmonize information about ADD. Cross-link build info.
2013-11-21 15:09:47 -08:00
Andy Rothfusz 50239e0573 Merge pull request #2799 from rosenhouse/patch-1
Fix title on doc page for remote_api_client_libraries.rst
2013-11-21 14:40:33 -08:00
Guillaume J. Charmes 42c23b0f04 Merge pull request #2763 from dotcloud/use_full_id_delete
Do ont truncate ID on docker rmi
2013-11-21 14:39:38 -08:00
Guillaume J. Charmes eec91e7941 Merge pull request #2805 from pmorie/typo
Fix typo in pullImage
2013-11-21 14:35:07 -08:00
Paul Morie 3f17844b6e Fix typo in pullImage 2013-11-21 17:29:03 -05:00
Guillaume J. Charmes bcdeb37bb6 Merge pull request #2794 from dotcloud/fix_status_code_and_usage
fix status code and usage
2013-11-21 13:48:38 -08:00
Gabe Rosenhouse 362e9d6b3c Fix title on doc page for remote_api_client_libraries.rst 2013-11-21 12:45:01 -08:00
Guillaume J. Charmes c4ab498920 Merge pull request #2797 from crosbymichael/revert-exit-lock
Revert "Lock state before we modify."
2013-11-21 12:19:23 -08:00
Michael Crosby cb70eedfda Revert "Lock state before we modify."
This reverts commit d7e2fc8982.
2013-11-21 12:11:25 -08:00
Victor Vieux 75a7f4d90c Return status exit status 2 on usage, move parserun into commands.go, display usage on stderr 2013-11-21 11:43:07 -08:00
Victor Vieux eaeb969138 Merge pull request #2795 from pnasrat/docker-testmultipleattachrestart-race
Lock state before we modify.
2013-11-21 11:28:02 -08:00
Paul Nasrat d7e2fc8982 Lock state before we modify.
When we start a container we lock state, we should do the same in stop.

Detected via -race.
2013-11-21 08:06:02 -05:00
Victor Vieux f20c738963 Merge pull request #2793 from crosbymichael/offline-ids
Allow images to be saved and loaded by id and repository
2013-11-21 02:44:00 -08:00
Michael Crosby fd7ab143bf Allow images to be saved and loaded by id and repository 2013-11-20 17:28:19 -08:00
Andy Rothfusz 82cdd21a34 Merge pull request #2727 from SvenDowideit/docker-images-doc
add some common examples for docker images, and tell the user what -a filters out
2013-11-20 16:30:48 -08:00
Michael Crosby 2bc35287a0 Merge pull request #2723 from SvenDowideit/doc-build-image
Use the work Path for docker cp CONTAINER:PATH
2013-11-20 11:10:13 -08:00
Guillaume J. Charmes b4f7078a02 Merge pull request #2773 from dotcloud/speed_up_docker_ps
speedup docker ps
2013-11-20 10:40:03 -08:00
Guillaume J. Charmes 9e68913397 Merge pull request #2760 from dotcloud/improve_engine_test
improve tests on the engine
2013-11-20 10:38:46 -08:00
Victor Vieux 304a80fcd5 Merge pull request #2747 from jpoimboe/test-fixes
Test fixes
2013-11-20 10:07:40 -08:00
Guillaume J. Charmes 04f1d4dcdb Merge pull request #2788 from jpoimboe/resize-after-start
resize pty after starting
2013-11-20 10:01:59 -08:00
Josh Poimboeuf 171d681724 resize pty after starting
Since ptyMaster is created during container start (startPty), it should
resized after starting, not before.
2013-11-20 11:40:30 -06:00
Michael Crosby cd4c1ac356 Merge pull request #2779 from crosbymichael/pin-python-deps
Pip python deps to a specific version
2013-11-19 21:29:09 -08:00
Michael Crosby b8af68a92b Merge pull request #1974 from dotcloud/1155-offline-image-transfer
Implement offline image transfers
2013-11-19 20:00:58 -08:00
Michael Crosby 9de4590498 Add typo in remove all. Ensure tmpDir is removed 2013-11-20 03:52:33 +00:00
Michael Crosby 0ef6fed5c7 Fix error checks for offline transfer and remove unneeded debug output 2013-11-20 03:52:33 +00:00
Frederick F. Kautz IV 383f95bba1 Adding 1.7 documentation 2013-11-20 03:52:33 +00:00
Frederick F. Kautz IV 1211065c8d Adding content type to images/(name)/get 2013-11-20 03:52:33 +00:00
Frederick F. Kautz IV 844c13bce6 Fixing build 2013-11-20 03:52:33 +00:00
Frederick F. Kautz IV 6014db4a7e Setting offline package version to 1.0 2013-11-20 03:52:33 +00:00
Frederick F. Kautz IV bf504f2afa Adding spaces to fix docs build 2013-11-20 03:52:33 +00:00
Frederick F. Kautz IV 61a8020e51 Adding version info, switching to defer for cleanup 2013-11-20 03:52:33 +00:00
Frederick F. Kautz IV 7eaa59f626 Offline Image Transfers #1155 2013-11-20 03:52:33 +00:00
Michael Crosby 2cccbbdadd Pip python deps to a specific version 2013-11-19 19:25:54 -08:00
Tianon Gravi 66beafa9f3 rename file to remove testing flags from docker binary 2013-11-19 12:41:29 -08:00
Victor Vieux 8e5ab5bfca improve tests on the engine 2013-11-19 11:47:47 -08:00
Andy Rothfusz 4f9f83d6c6 Fix #2342. Harmonize information about ADD. Cross-link build info. 2013-11-19 11:16:28 -08:00
Victor Vieux 145c2008ae speedup docker ps 2013-11-19 11:02:10 -08:00
Vitor Monteiro aeb304b37c Change to documentation for AWS AMI request
Hi guys, it just might be me, but clicking the AMI from http://cloud-images.ubuntu.com/locator/ec2/ is broken to me. So I just did it via the normal Create Instance Wizard.

I just though some people might have the same issue.

Sorry if my markdown for links is screwed up, I went by the examples, since I'm used to the `[]()` traditional one.

Cheers.
2013-11-19 17:50:38 +00:00
unclejack 78c843c8ef fix container size computation: handle hard links
This change makes docker compute container size correctly.

The old code isn't taking hard links into account. Containers could
seem like they're up to 1-1.5x larger than they really were.
2013-11-19 13:53:34 +02:00
unclejack ac821f2446 fix layer size computation: handle hard links
This change makes docker compute layer size correctly.

The old code isn't taking hard links into account. Layers could
seem like they're up to 1-1.5x larger than they really were.
2013-11-19 13:37:54 +02:00
Victor Vieux 2fe4467d73 Do ont truncate ID on docker rmi 2013-11-18 18:39:02 -08:00
Josh Poimboeuf 4b80ec9aae test: remove extra args in TestExitCode
The extra blank argument isn't needed and confuses libvirt.
2013-11-18 15:07:11 -06:00
Josh Poimboeuf fef41ef7bf test: fix TestRmi race condition 2013-11-18 15:07:06 -06:00
Josh Poimboeuf fe302fbfd2 test: 2 second timeout (not 2000) 2013-11-18 10:23:30 -06:00
Josh Poimboeuf 72d02ecdde test: skip TestCreate on Fedora due to lxc utils bug
In the dind environment running on a Fedora host, the lxc utils get
confused by the /sys/fs/cgroup/cpuacct,cpu cgroup mount and lxc-start
fails trying to access the wrong cgroup directory.
2013-11-18 10:23:30 -06:00
Josh Poimboeuf baa687bed2 test: fix TestCreateStartRestartStopStartKillRm
cat needs stdin opened, otherwise it dies immediately.
2013-11-18 10:23:30 -06:00
Josh Poimboeuf 30ea0bebce test: put each arg in a separate string
Each arg to docker run should be placed in a separate string.
Otherwise, when starting the command via exec.Cmd, the command is
interpreted as "echo test", which can't be found.
2013-11-18 10:23:06 -06:00
Sven Dowideit bc74f65068 make the docker commit help more copy&pasteable 2013-11-16 21:11:34 +10:00
Sven Dowideit 152459b727 add some common examples for docker images, and tell the user what -a filters out 2013-11-16 20:45:30 +10:00
Sven Dowideit 27159ce6ba expunge the word 'Resource' in reference to a file/dir in a CONTAINER - that way users don't wonder how its different from a Path 2013-11-16 20:15:04 +10:00
Xiuming Chen 0013aa7d9f Minor code simplification for Containers api 2013-11-13 01:29:00 -08:00
Liang-Chi Hsieh 1d7f22c0d4 use Binds key in hostConfig to detect volumes mounted from external. 2013-11-13 15:08:46 +08:00
Liang-Chi Hsieh 6a693176d6 skip the volumes mounted when deleting the volumes of container. 2013-11-13 14:58:24 +08:00
Victor Vieux 6d420407ca Merge pull request #2577 from dotcloud/bump_v0.6.6
Bump v0.6.6
2013-11-06 12:03:03 -08:00
66 changed files with 1633 additions and 5801 deletions
+39
View File
@@ -1,5 +1,43 @@
# Changelog
## 0.6.7 (2013-11-21)
#### Runtime
* Improved stability, fixes some race conditons
* Skip the volumes mounted when deleting the volumes of container.
* Fix layer size computation: handle hard links correctly
* Use the work Path for docker cp CONTAINER:PATH
* Fix tmp dir never cleanup
* Speedup docker ps
* More informative error message on name collisions
* Fix nameserver regex
* Always return long id's
* Fix container restart race condition
* Keep published ports on docker stop;docker start
* Fix container networking on Fedora
* Correctly express "any address" to iptables
* Fix network setup when reconnecting to ghost container
* Prevent deletion if image is used by a running container
* Lock around read operations in graph
#### RemoteAPI
* Return full ID on docker rmi
#### Client
+ Add -tree option to images
+ Offline image transfer
* Exit with status 2 on usage error and display usage on stderr
* Do not forward SIGCHLD to container
* Use string timestamp for docker events -since
#### Other
* Update to go 1.2rc5
+ Add /etc/default/docker support to upstart
## 0.6.6 (2013-11-06)
#### Runtime
@@ -17,6 +55,7 @@
+ Prevent DNS server conflicts in CreateBridgeIface
+ Validate bind mounts on the server side
+ Use parent image config in docker build
* Fix regression in /etc/hosts
#### Client
+7 -17
View File
@@ -23,7 +23,7 @@
# the case. Therefore, you don't have to disable it anymore.
#
docker-version 0.6.1
docker-version 0.6.1
from ubuntu:12.04
maintainer Solomon Hykes <solomon@dotcloud.com>
@@ -33,23 +33,22 @@ run apt-get update
run apt-get install -y -q curl
run apt-get install -y -q git
run apt-get install -y -q mercurial
run apt-get install -y -q build-essential libsqlite3-dev
run apt-get install -y -q build-essential libsqlite3-dev
# Install Go
run curl -s https://go.googlecode.com/files/go1.2rc5.src.tar.gz | tar -v -C /usr/local -xz
env PATH /usr/local/go/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin
env GOPATH /go:/go/src/github.com/dotcloud/docker/vendor
run cd /usr/local/go/src && ./make.bash && go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std
run cd /usr/local/go/src && ./make.bash && go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std
# Ubuntu stuff
run apt-get install -y -q ruby1.9.3 rubygems libffi-dev
run gem install --no-rdoc --no-ri fpm
run apt-get install -y -q reprepro dpkg-sig
# Install s3cmd 1.0.1 (earlier versions don't support env variables in the config)
run apt-get install -y -q python-pip
run pip install s3cmd
run pip install python-magic
run pip install s3cmd==1.1.0-beta3
run pip install python-magic==0.4.6
run /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY\n' > /.s3cfg
# Runtime dependencies
@@ -57,20 +56,11 @@ run apt-get install -y -q iptables
run apt-get install -y -q lxc
run apt-get install -y -q aufs-tools
# Get lvm2 source for compiling statically
run git clone git://git.fedorahosted.org/git/lvm2.git /usr/local/lvm2 && cd /usr/local/lvm2 && git checkout v2_02_103
# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags
# note: we can't use "git clone -b" above because it requires at least git 1.7.10 to be able to use that on a tag instead of a branch and we only have 1.7.9.5
# Compile and install lvm2
run cd /usr/local/lvm2 && ./configure --enable-static_link && make device-mapper && make install_device-mapper
# see https://git.fedorahosted.org/cgit/lvm2.git/tree/INSTALL
volume /var/lib/docker
workdir /go/src/github.com/dotcloud/docker
# Wrap all commands in the "docker-in-docker" script to allow nested containers
entrypoint ["hack/dind"]
entrypoint ["hack/dind"]
# Upload docker source
add . /go/src/github.com/dotcloud/docker
add . /go/src/github.com/dotcloud/docker
+1 -1
View File
@@ -1 +1 @@
0.6.6-dev
0.6.7
+15 -1
View File
@@ -534,6 +534,18 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http
return nil
}
func getImagesGet(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
name := vars["name"]
if version > 1.0 {
w.Header().Set("Content-Type", "application/x-tar")
}
return srv.ImageExport(name, w)
}
func postImagesLoad(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
return srv.ImageLoad(r.Body)
}
func postContainersCreate(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if err := parseForm(r); err != nil {
return nil
@@ -967,7 +979,7 @@ func postContainersCopy(srv *Server, version float64, w http.ResponseWriter, r *
}
if copyData.Resource == "" {
return fmt.Errorf("Resource cannot be empty")
return fmt.Errorf("Path cannot be empty")
}
if copyData.Resource[0] == '/' {
copyData.Resource = copyData.Resource[1:]
@@ -1036,6 +1048,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
"/images/json": getImagesJSON,
"/images/viz": getImagesViz,
"/images/search": getImagesSearch,
"/images/{name:.*}/get": getImagesGet,
"/images/{name:.*}/history": getImagesHistory,
"/images/{name:.*}/json": getImagesByName,
"/containers/ps": getContainersJSON,
@@ -1052,6 +1065,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
"/build": postBuild,
"/images/create": postImagesCreate,
"/images/{name:.*}/insert": postImagesInsert,
"/images/load": postImagesLoad,
"/images/{name:.*}/push": postImagesPush,
"/images/{name:.*}/tag": postImagesTag,
"/containers/create": postContainersCreate,
+9 -11
View File
@@ -33,17 +33,15 @@ type (
Debug bool
Containers int
Images int
Driver string `json:",omitempty"`
DriverStatus [][2]string `json:",omitempty"`
NFd int `json:",omitempty"`
NGoroutines int `json:",omitempty"`
MemoryLimit bool `json:",omitempty"`
SwapLimit bool `json:",omitempty"`
IPv4Forwarding bool `json:",omitempty"`
LXCVersion string `json:",omitempty"`
NEventsListener int `json:",omitempty"`
KernelVersion string `json:",omitempty"`
IndexServerAddress string `json:",omitempty"`
NFd int `json:",omitempty"`
NGoroutines int `json:",omitempty"`
MemoryLimit bool `json:",omitempty"`
SwapLimit bool `json:",omitempty"`
IPv4Forwarding bool `json:",omitempty"`
LXCVersion string `json:",omitempty"`
NEventsListener int `json:",omitempty"`
KernelVersion string `json:",omitempty"`
IndexServerAddress string `json:",omitempty"`
}
APITop struct {
+16 -112
View File
@@ -15,15 +15,7 @@ import (
type Archive io.Reader
type Compression int
type TarOptions struct {
Includes []string
Excludes []string
Recursive bool
Compression Compression
CreateFiles []string
}
type Compression uint32
const (
Uncompressed Compression = iota
@@ -88,78 +80,20 @@ func (compression *Compression) Extension() string {
// Tar creates an archive from the directory at `path`, and returns it as a
// stream of bytes.
func Tar(path string, compression Compression) (io.Reader, error) {
return TarFilter(path, &TarOptions{Recursive: true, Compression: compression})
}
func escapeName(name string) string {
escaped := make([]byte, 0)
for i, c := range []byte(name) {
if i == 0 && c == '/' {
continue
}
// all printable chars except "-" which is 0x2d
if (0x20 <= c && c <= 0x7E) && c != 0x2d {
escaped = append(escaped, c)
} else {
escaped = append(escaped, fmt.Sprintf("\\%03o", c)...)
}
}
return string(escaped)
return TarFilter(path, compression, nil)
}
// Tar creates an archive from the directory at `path`, only including files whose relative
// paths are included in `filter`. If `filter` is nil, then all files are included.
func TarFilter(path string, options *TarOptions) (io.Reader, error) {
args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path, "-T", "-"}
if options.Includes == nil {
options.Includes = []string{"."}
func TarFilter(path string, compression Compression, filter []string) (io.Reader, error) {
args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path}
if filter == nil {
filter = []string{"."}
}
args = append(args, "-c"+options.Compression.Flag())
for _, exclude := range options.Excludes {
args = append(args, fmt.Sprintf("--exclude=%s", exclude))
for _, f := range filter {
args = append(args, "-c"+compression.Flag(), f)
}
if !options.Recursive {
args = append(args, "--no-recursion")
}
files := ""
for _, f := range options.Includes {
files = files + escapeName(f) + "\n"
}
tmpDir := ""
if options.CreateFiles != nil {
var err error // Can't use := here or we override the outer tmpDir
tmpDir, err = ioutil.TempDir("", "docker-tar")
if err != nil {
return nil, err
}
files = files + "-C" + tmpDir + "\n"
for _, f := range options.CreateFiles {
path := filepath.Join(tmpDir, f)
err := os.MkdirAll(filepath.Dir(path), 0600)
if err != nil {
return nil, err
}
if file, err := os.OpenFile(path, os.O_CREATE, 0600); err != nil {
return nil, err
} else {
file.Close()
}
files = files + escapeName(f) + "\n"
}
}
return CmdStream(exec.Command(args[0], args[1:]...), &files, func() {
if tmpDir != "" {
_ = os.RemoveAll(tmpDir)
}
})
return CmdStream(exec.Command(args[0], args[1:]...))
}
// Untar reads a stream of bytes from `archive`, parses it as a tar archive,
@@ -167,7 +101,7 @@ func TarFilter(path string, options *TarOptions) (io.Reader, error) {
// The archive may be compressed with one of the following algorithms:
// identity (uncompressed), gzip, bzip2, xz.
// FIXME: specify behavior when target path exists vs. doesn't exist.
func Untar(archive io.Reader, path string, options *TarOptions) error {
func Untar(archive io.Reader, path string) error {
if archive == nil {
return fmt.Errorf("Empty archive")
}
@@ -189,15 +123,8 @@ func Untar(archive io.Reader, path string, options *TarOptions) error {
compression := DetectCompression(buf)
utils.Debugf("Archive compression detected: %s", compression.Extension())
args := []string{"--numeric-owner", "-f", "-", "-C", path, "-x" + compression.Flag()}
if options != nil {
for _, exclude := range options.Excludes {
args = append(args, fmt.Sprintf("--exclude=%s", exclude))
}
}
cmd := exec.Command("tar", args...)
cmd := exec.Command("tar", "--numeric-owner", "-f", "-", "-C", path, "-x"+compression.Flag())
cmd.Stdin = io.MultiReader(bytes.NewReader(buf), archive)
// Hardcode locale environment for predictable outcome regardless of host configuration.
// (see https://github.com/dotcloud/docker/issues/355)
@@ -214,11 +141,11 @@ func Untar(archive io.Reader, path string, options *TarOptions) error {
// TarUntar aborts and returns the error.
func TarUntar(src string, filter []string, dst string) error {
utils.Debugf("TarUntar(%s %s %s)", src, filter, dst)
archive, err := TarFilter(src, &TarOptions{Compression: Uncompressed, Includes: filter, Recursive: true})
archive, err := TarFilter(src, Uncompressed, filter)
if err != nil {
return err
}
return Untar(archive, dst, nil)
return Untar(archive, dst)
}
// UntarPath is a convenience function which looks for an archive
@@ -226,7 +153,7 @@ func TarUntar(src string, filter []string, dst string) error {
func UntarPath(src, dst string) error {
if archive, err := os.Open(src); err != nil {
return err
} else if err := Untar(archive, dst, nil); err != nil {
} else if err := Untar(archive, dst); err != nil {
return err
}
return nil
@@ -295,39 +222,19 @@ func CopyFileWithTar(src, dst string) error {
return err
}
tw.Close()
return Untar(buf, filepath.Dir(dst), nil)
return Untar(buf, filepath.Dir(dst))
}
// CmdStream executes a command, and returns its stdout as a stream.
// If the command fails to run or doesn't complete successfully, an error
// will be returned, including anything written on stderr.
func CmdStream(cmd *exec.Cmd, input *string, atEnd func()) (io.Reader, error) {
if input != nil {
stdin, err := cmd.StdinPipe()
if err != nil {
if atEnd != nil {
atEnd()
}
return nil, err
}
// Write stdin if any
go func() {
_, _ = stdin.Write([]byte(*input))
stdin.Close()
}()
}
func CmdStream(cmd *exec.Cmd) (io.Reader, error) {
stdout, err := cmd.StdoutPipe()
if err != nil {
if atEnd != nil {
atEnd()
}
return nil, err
}
stderr, err := cmd.StderrPipe()
if err != nil {
if atEnd != nil {
atEnd()
}
return nil, err
}
pipeR, pipeW := io.Pipe()
@@ -352,9 +259,6 @@ func CmdStream(cmd *exec.Cmd, input *string, atEnd func()) (io.Reader, error) {
} else {
pipeW.Close()
}
if atEnd != nil {
atEnd()
}
}()
// Run the command and return the pipe
if err := cmd.Start(); err != nil {
+4 -4
View File
@@ -14,7 +14,7 @@ import (
func TestCmdStreamLargeStderr(t *testing.T) {
cmd := exec.Command("/bin/sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello")
out, err := CmdStream(cmd, nil, nil)
out, err := CmdStream(cmd)
if err != nil {
t.Fatalf("Failed to start command: %s", err)
}
@@ -35,7 +35,7 @@ func TestCmdStreamLargeStderr(t *testing.T) {
func TestCmdStreamBad(t *testing.T) {
badCmd := exec.Command("/bin/sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1")
out, err := CmdStream(badCmd, nil, nil)
out, err := CmdStream(badCmd)
if err != nil {
t.Fatalf("Failed to start command: %s", err)
}
@@ -50,7 +50,7 @@ func TestCmdStreamBad(t *testing.T) {
func TestCmdStreamGood(t *testing.T) {
cmd := exec.Command("/bin/sh", "-c", "echo hello; exit 0")
out, err := CmdStream(cmd, nil, nil)
out, err := CmdStream(cmd)
if err != nil {
t.Fatal(err)
}
@@ -83,7 +83,7 @@ func tarUntar(t *testing.T, origin string, compression Compression) error {
return err
}
defer os.RemoveAll(tmp)
if err := Untar(archive, tmp, nil); err != nil {
if err := Untar(archive, tmp); err != nil {
return err
}
if _, err := os.Stat(tmp); err != nil {
-317
View File
@@ -1,317 +0,0 @@
package archive
import (
"fmt"
"os"
"path/filepath"
"strings"
"syscall"
)
type ChangeType int
const (
ChangeModify = iota
ChangeAdd
ChangeDelete
)
type Change struct {
Path string
Kind ChangeType
}
func (change *Change) String() string {
var kind string
switch change.Kind {
case ChangeModify:
kind = "C"
case ChangeAdd:
kind = "A"
case ChangeDelete:
kind = "D"
}
return fmt.Sprintf("%s %s", kind, change.Path)
}
func Changes(layers []string, rw string) ([]Change, error) {
var changes []Change
err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
// Rebase path
path, err = filepath.Rel(rw, path)
if err != nil {
return err
}
path = filepath.Join("/", path)
// Skip root
if path == "/" {
return nil
}
// Skip AUFS metadata
if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched {
return err
}
change := Change{
Path: path,
}
// Find out what kind of modification happened
file := filepath.Base(path)
// If there is a whiteout, then the file was removed
if strings.HasPrefix(file, ".wh.") {
originalFile := file[len(".wh."):]
change.Path = filepath.Join(filepath.Dir(path), originalFile)
change.Kind = ChangeDelete
} else {
// Otherwise, the file was added
change.Kind = ChangeAdd
// ...Unless it already existed in a top layer, in which case, it's a modification
for _, layer := range layers {
stat, err := os.Stat(filepath.Join(layer, path))
if err != nil && !os.IsNotExist(err) {
return err
}
if err == nil {
// The file existed in the top layer, so that's a modification
// However, if it's a directory, maybe it wasn't actually modified.
// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
if stat.IsDir() && f.IsDir() {
if f.Size() == stat.Size() && f.Mode() == stat.Mode() && f.ModTime() == stat.ModTime() {
// Both directories are the same, don't record the change
return nil
}
}
change.Kind = ChangeModify
break
}
}
}
// Record change
changes = append(changes, change)
return nil
})
if err != nil && !os.IsNotExist(err) {
return nil, err
}
return changes, nil
}
type FileInfo struct {
parent *FileInfo
name string
stat syscall.Stat_t
children map[string]*FileInfo
}
func (root *FileInfo) LookUp(path string) *FileInfo {
parent := root
if path == "/" {
return root
}
pathElements := strings.Split(path, "/")
for _, elem := range pathElements {
if elem != "" {
child := parent.children[elem]
if child == nil {
return nil
}
parent = child
}
}
return parent
}
func (info *FileInfo) path() string {
if info.parent == nil {
return "/"
}
return filepath.Join(info.parent.path(), info.name)
}
func (info *FileInfo) isDir() bool {
return info.parent == nil || info.stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR
}
func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
if oldInfo == nil {
// add
change := Change{
Path: info.path(),
Kind: ChangeAdd,
}
*changes = append(*changes, change)
}
// We make a copy so we can modify it to detect additions
// also, we only recurse on the old dir if the new info is a directory
// otherwise any previous delete/change is considered recursive
oldChildren := make(map[string]*FileInfo)
if oldInfo != nil && info.isDir() {
for k, v := range oldInfo.children {
oldChildren[k] = v
}
}
for name, newChild := range info.children {
oldChild, _ := oldChildren[name]
if oldChild != nil {
// change?
oldStat := &oldChild.stat
newStat := &newChild.stat
// Note: We can't compare inode or ctime or blocksize here, because these change
// when copying a file into a container. However, that is not generally a problem
// because any content change will change mtime, and any status change should
// be visible when actually comparing the stat fields. The only time this
// breaks down is if some code intentionally hides a change by setting
// back mtime
if oldStat.Mode != newStat.Mode ||
oldStat.Uid != newStat.Uid ||
oldStat.Gid != newStat.Gid ||
oldStat.Rdev != newStat.Rdev ||
// Don't look at size for dirs, its not a good measure of change
(oldStat.Size != newStat.Size && oldStat.Mode&syscall.S_IFDIR != syscall.S_IFDIR) ||
oldStat.Mtim != newStat.Mtim {
change := Change{
Path: newChild.path(),
Kind: ChangeModify,
}
*changes = append(*changes, change)
}
// Remove from copy so we can detect deletions
delete(oldChildren, name)
}
newChild.addChanges(oldChild, changes)
}
for _, oldChild := range oldChildren {
// delete
change := Change{
Path: oldChild.path(),
Kind: ChangeDelete,
}
*changes = append(*changes, change)
}
}
func (info *FileInfo) Changes(oldInfo *FileInfo) []Change {
var changes []Change
info.addChanges(oldInfo, &changes)
return changes
}
func newRootFileInfo() *FileInfo {
root := &FileInfo{
name: "/",
children: make(map[string]*FileInfo),
}
return root
}
func collectFileInfo(sourceDir string) (*FileInfo, error) {
root := newRootFileInfo()
err := filepath.Walk(sourceDir, func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
// Rebase path
relPath, err := filepath.Rel(sourceDir, path)
if err != nil {
return err
}
relPath = filepath.Join("/", relPath)
if relPath == "/" {
return nil
}
parent := root.LookUp(filepath.Dir(relPath))
if parent == nil {
return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath)
}
info := &FileInfo{
name: filepath.Base(relPath),
children: make(map[string]*FileInfo),
parent: parent,
}
if err := syscall.Lstat(path, &info.stat); err != nil {
return err
}
parent.children[info.name] = info
return nil
})
if err != nil {
return nil, err
}
return root, nil
}
// Compare two directories and generate an array of Change objects describing the changes
func ChangesDirs(newDir, oldDir string) ([]Change, error) {
oldRoot, err := collectFileInfo(oldDir)
if err != nil {
return nil, err
}
newRoot, err := collectFileInfo(newDir)
if err != nil {
return nil, err
}
return newRoot.Changes(oldRoot), nil
}
func ChangesSize(newDir string, changes []Change) int64 {
var size int64
for _, change := range changes {
if change.Kind == ChangeModify || change.Kind == ChangeAdd {
file := filepath.Join(newDir, change.Path)
fileInfo, _ := os.Lstat(file)
if fileInfo != nil && !fileInfo.IsDir() {
size += fileInfo.Size()
}
}
}
return size
}
func ExportChanges(dir string, changes []Change) (Archive, error) {
files := make([]string, 0)
deletions := make([]string, 0)
for _, change := range changes {
if change.Kind == ChangeModify || change.Kind == ChangeAdd {
files = append(files, change.Path)
}
if change.Kind == ChangeDelete {
base := filepath.Base(change.Path)
dir := filepath.Dir(change.Path)
deletions = append(deletions, filepath.Join(dir, ".wh."+base))
}
}
// FIXME: Why do we create whiteout files inside Tar code ?
return TarFilter(dir, &TarOptions{
Compression: Uncompressed,
Includes: files,
Recursive: false,
CreateFiles: deletions,
})
}
-298
View File
@@ -1,298 +0,0 @@
package archive
import (
"io/ioutil"
"os"
"os/exec"
"path"
"sort"
"testing"
"time"
)
func max(x, y int) int {
if x >= y {
return x
}
return y
}
func copyDir(src, dst string) error {
cmd := exec.Command("cp", "-a", src, dst)
if err := cmd.Run(); err != nil {
return err
}
return nil
}
// Helper to sort []Change by path
type byPath struct{ changes []Change }
func (b byPath) Less(i, j int) bool { return b.changes[i].Path < b.changes[j].Path }
func (b byPath) Len() int { return len(b.changes) }
func (b byPath) Swap(i, j int) { b.changes[i], b.changes[j] = b.changes[j], b.changes[i] }
type FileType uint32
const (
Regular FileType = iota
Dir
Symlink
)
type FileData struct {
filetype FileType
path string
contents string
permissions os.FileMode
}
func createSampleDir(t *testing.T, root string) {
files := []FileData{
{Regular, "file1", "file1\n", 0600},
{Regular, "file2", "file2\n", 0666},
{Regular, "file3", "file3\n", 0404},
{Regular, "file4", "file4\n", 0600},
{Regular, "file5", "file5\n", 0600},
{Regular, "file6", "file6\n", 0600},
{Regular, "file7", "file7\n", 0600},
{Dir, "dir1", "", 0740},
{Regular, "dir1/file1-1", "file1-1\n", 01444},
{Regular, "dir1/file1-2", "file1-2\n", 0666},
{Dir, "dir2", "", 0700},
{Regular, "dir2/file2-1", "file2-1\n", 0666},
{Regular, "dir2/file2-2", "file2-2\n", 0666},
{Dir, "dir3", "", 0700},
{Regular, "dir3/file3-1", "file3-1\n", 0666},
{Regular, "dir3/file3-2", "file3-2\n", 0666},
{Dir, "dir4", "", 0700},
{Regular, "dir4/file3-1", "file4-1\n", 0666},
{Regular, "dir4/file3-2", "file4-2\n", 0666},
{Symlink, "symlink1", "target1", 0666},
{Symlink, "symlink2", "target2", 0666},
}
for _, info := range files {
if info.filetype == Dir {
if err := os.MkdirAll(path.Join(root, info.path), info.permissions); err != nil {
t.Fatal(err)
}
} else if info.filetype == Regular {
if err := ioutil.WriteFile(path.Join(root, info.path), []byte(info.contents), info.permissions); err != nil {
t.Fatal(err)
}
} else if info.filetype == Symlink {
if err := os.Symlink(info.contents, path.Join(root, info.path)); err != nil {
t.Fatal(err)
}
}
}
}
// Create an directory, copy it, make sure we report no changes between the two
func TestChangesDirsEmpty(t *testing.T) {
src, err := ioutil.TempDir("", "docker-changes-test")
if err != nil {
t.Fatal(err)
}
createSampleDir(t, src)
dst := src + "-copy"
if err := copyDir(src, dst); err != nil {
t.Fatal(err)
}
changes, err := ChangesDirs(dst, src)
if err != nil {
t.Fatal(err)
}
if len(changes) != 0 {
t.Fatalf("Reported changes for identical dirs: %v", changes)
}
os.RemoveAll(src)
os.RemoveAll(dst)
}
func mutateSampleDir(t *testing.T, root string) {
// Remove a regular file
if err := os.RemoveAll(path.Join(root, "file1")); err != nil {
t.Fatal(err)
}
// Remove a directory
if err := os.RemoveAll(path.Join(root, "dir1")); err != nil {
t.Fatal(err)
}
// Remove a symlink
if err := os.RemoveAll(path.Join(root, "symlink1")); err != nil {
t.Fatal(err)
}
// Rewrite a file
if err := ioutil.WriteFile(path.Join(root, "file2"), []byte("fileN\n"), 0777); err != nil {
t.Fatal(err)
}
// Replace a file
if err := os.RemoveAll(path.Join(root, "file3")); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(path.Join(root, "file3"), []byte("fileM\n"), 0404); err != nil {
t.Fatal(err)
}
// Touch file
if err := os.Chtimes(path.Join(root, "file4"), time.Now(), time.Now()); err != nil {
t.Fatal(err)
}
// Replace file with dir
if err := os.RemoveAll(path.Join(root, "file5")); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(path.Join(root, "file5"), 0666); err != nil {
t.Fatal(err)
}
// Create new file
if err := ioutil.WriteFile(path.Join(root, "filenew"), []byte("filenew\n"), 0777); err != nil {
t.Fatal(err)
}
// Create new dir
if err := os.MkdirAll(path.Join(root, "dirnew"), 0766); err != nil {
t.Fatal(err)
}
// Create a new symlink
if err := os.Symlink("targetnew", path.Join(root, "symlinknew")); err != nil {
t.Fatal(err)
}
// Change a symlink
if err := os.RemoveAll(path.Join(root, "symlink2")); err != nil {
t.Fatal(err)
}
if err := os.Symlink("target2change", path.Join(root, "symlink2")); err != nil {
t.Fatal(err)
}
// Replace dir with file
if err := os.RemoveAll(path.Join(root, "dir2")); err != nil {
t.Fatal(err)
}
if err := ioutil.WriteFile(path.Join(root, "dir2"), []byte("dir2\n"), 0777); err != nil {
t.Fatal(err)
}
// Touch dir
if err := os.Chtimes(path.Join(root, "dir3"), time.Now(), time.Now()); err != nil {
t.Fatal(err)
}
}
func TestChangesDirsMutated(t *testing.T) {
src, err := ioutil.TempDir("", "docker-changes-test")
if err != nil {
t.Fatal(err)
}
createSampleDir(t, src)
dst := src + "-copy"
if err := copyDir(src, dst); err != nil {
t.Fatal(err)
}
mutateSampleDir(t, dst)
changes, err := ChangesDirs(dst, src)
if err != nil {
t.Fatal(err)
}
sort.Sort(byPath{changes})
expectedChanges := []Change{
{"/dir1", ChangeDelete},
{"/dir2", ChangeModify},
{"/dir3", ChangeModify},
{"/dirnew", ChangeAdd},
{"/file1", ChangeDelete},
{"/file2", ChangeModify},
{"/file3", ChangeModify},
{"/file4", ChangeModify},
{"/file5", ChangeModify},
{"/filenew", ChangeAdd},
{"/symlink1", ChangeDelete},
{"/symlink2", ChangeModify},
{"/symlinknew", ChangeAdd},
}
i := 0
for ; i < max(len(changes), len(expectedChanges)); i++ {
if i >= len(expectedChanges) {
t.Fatalf("unexpected change %s\n", changes[i].String())
}
if i >= len(changes) {
t.Fatalf("no change for expected change %s\n", expectedChanges[i].String())
}
if changes[i].Path == expectedChanges[i].Path {
if changes[i] != expectedChanges[i] {
t.Fatalf("Wrong change for %s, expected %s, got %d\n", changes[i].Path, changes[i].String(), expectedChanges[i].String())
}
} else if changes[i].Path < expectedChanges[i].Path {
t.Fatalf("unexpected change %s\n", changes[i].String())
} else {
t.Fatalf("no change for expected change %s\n", expectedChanges[i].String())
}
}
for ; i < len(expectedChanges); i++ {
}
os.RemoveAll(src)
os.RemoveAll(dst)
}
func TestApplyLayer(t *testing.T) {
t.Skip("Skipping TestApplyLayer due to known failures") // Disable this for now as it is broken
return
src, err := ioutil.TempDir("", "docker-changes-test")
if err != nil {
t.Fatal(err)
}
createSampleDir(t, src)
dst := src + "-copy"
if err := copyDir(src, dst); err != nil {
t.Fatal(err)
}
mutateSampleDir(t, dst)
changes, err := ChangesDirs(dst, src)
if err != nil {
t.Fatal(err)
}
layer, err := ExportChanges(dst, changes)
if err != nil {
t.Fatal(err)
}
layerCopy, err := NewTempArchive(layer, "")
if err != nil {
t.Fatal(err)
}
if err := ApplyLayer(src, layerCopy); err != nil {
t.Fatal(err)
}
changes2, err := ChangesDirs(src, dst)
if err != nil {
t.Fatal(err)
}
if len(changes2) != 0 {
t.Fatalf("Unexpected differences after re applying mutation: %v", changes)
}
os.RemoveAll(src)
os.RemoveAll(dst)
}
-95
View File
@@ -1,95 +0,0 @@
package archive
import (
"os"
"path/filepath"
"strings"
"syscall"
"time"
)
// ApplyLayer parses a diff in the standard layer format from `layer`, and
// applies it to the directory `dest`.
func ApplyLayer(dest string, layer Archive) error {
// Poor man's diff applyer in 2 steps:
// Step 1: untar everything in place
if err := Untar(layer, dest, nil); err != nil {
return err
}
modifiedDirs := make(map[string]*syscall.Stat_t)
addDir := func(file string) {
d := filepath.Dir(file)
if _, exists := modifiedDirs[d]; !exists {
if s, err := os.Lstat(d); err == nil {
if sys := s.Sys(); sys != nil {
if stat, ok := sys.(*syscall.Stat_t); ok {
modifiedDirs[d] = stat
}
}
}
}
}
// Step 2: walk for whiteouts and apply them, removing them in the process
err := filepath.Walk(dest, func(fullPath string, f os.FileInfo, err error) error {
if err != nil {
if os.IsNotExist(err) {
// This happens in the case of whiteouts in parent dir removing a directory
// We just ignore it
return filepath.SkipDir
}
return err
}
// Rebase path
path, err := filepath.Rel(dest, fullPath)
if err != nil {
return err
}
path = filepath.Join("/", path)
// Skip AUFS metadata
if matched, err := filepath.Match("/.wh..wh.*", path); err != nil {
return err
} else if matched {
addDir(fullPath)
if err := os.RemoveAll(fullPath); err != nil {
return err
}
}
filename := filepath.Base(path)
if strings.HasPrefix(filename, ".wh.") {
rmTargetName := filename[len(".wh."):]
rmTargetPath := filepath.Join(filepath.Dir(fullPath), rmTargetName)
// Remove the file targeted by the whiteout
addDir(rmTargetPath)
if err := os.RemoveAll(rmTargetPath); err != nil {
return err
}
// Remove the whiteout itself
addDir(fullPath)
if err := os.RemoveAll(fullPath); err != nil {
return err
}
}
return nil
})
if err != nil {
return err
}
for k, v := range modifiedDirs {
aTime := time.Unix(v.Atim.Unix())
mTime := time.Unix(v.Mtim.Unix())
if err := os.Chtimes(k, aTime, mTime); err != nil {
return err
}
}
return nil
}
+1 -1
View File
@@ -476,7 +476,7 @@ func (b *buildFile) Build(context io.Reader) (string, error) {
if err != nil {
return "", err
}
if err := archive.Untar(context, name, nil); err != nil {
if err := archive.Untar(context, name); err != nil {
return "", err
}
defer os.RemoveAll(name)
+106
View File
@@ -0,0 +1,106 @@
package docker
import (
"fmt"
"os"
"path/filepath"
"strings"
)
type ChangeType int
const (
ChangeModify = iota
ChangeAdd
ChangeDelete
)
type Change struct {
Path string
Kind ChangeType
}
func (change *Change) String() string {
var kind string
switch change.Kind {
case ChangeModify:
kind = "C"
case ChangeAdd:
kind = "A"
case ChangeDelete:
kind = "D"
}
return fmt.Sprintf("%s %s", kind, change.Path)
}
func Changes(layers []string, rw string) ([]Change, error) {
var changes []Change
err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
// Rebase path
path, err = filepath.Rel(rw, path)
if err != nil {
return err
}
path = filepath.Join("/", path)
// Skip root
if path == "/" {
return nil
}
// Skip AUFS metadata
if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched {
return err
}
change := Change{
Path: path,
}
// Find out what kind of modification happened
file := filepath.Base(path)
// If there is a whiteout, then the file was removed
if strings.HasPrefix(file, ".wh.") {
originalFile := file[len(".wh."):]
change.Path = filepath.Join(filepath.Dir(path), originalFile)
change.Kind = ChangeDelete
} else {
// Otherwise, the file was added
change.Kind = ChangeAdd
// ...Unless it already existed in a top layer, in which case, it's a modification
for _, layer := range layers {
stat, err := os.Stat(filepath.Join(layer, path))
if err != nil && !os.IsNotExist(err) {
return err
}
if err == nil {
// The file existed in the top layer, so that's a modification
// However, if it's a directory, maybe it wasn't actually modified.
// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
if stat.IsDir() && f.IsDir() {
if f.Size() == stat.Size() && f.Mode() == stat.Mode() && f.ModTime() == stat.ModTime() {
// Both directories are the same, don't record the change
return nil
}
}
change.Kind = ChangeModify
break
}
}
}
// Record change
changes = append(changes, change)
return nil
})
if err != nil && !os.IsNotExist(err) {
return nil, err
}
return changes, nil
}
+308 -58
View File
@@ -22,6 +22,7 @@ import (
"net/url"
"os"
"os/signal"
"path"
"path/filepath"
"reflect"
"regexp"
@@ -92,6 +93,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
{"insert", "Insert a file in an image"},
{"inspect", "Return low-level information on a container"},
{"kill", "Kill a running container"},
{"load", "Load an image from a tar archive"},
{"login", "Register or Login to the docker registry server"},
{"logs", "Fetch the logs of a container"},
{"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"},
@@ -102,6 +104,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
{"rm", "Remove one or more containers"},
{"rmi", "Remove one or more images"},
{"run", "Run a command in a new container"},
{"save", "Save an image to a tar archive"},
{"search", "Search for an image in the docker index"},
{"start", "Start a stopped container"},
{"stop", "Stop a running container"},
@@ -117,7 +120,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
}
func (cli *DockerCli) CmdInsert(args ...string) error {
cmd := Subcmd("insert", "IMAGE URL PATH", "Insert a file from URL in the IMAGE at PATH")
cmd := cli.Subcmd("insert", "IMAGE URL PATH", "Insert a file from URL in the IMAGE at PATH")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -159,7 +162,7 @@ func MkBuildContext(dockerfile string, files [][2]string) (archive.Archive, erro
}
func (cli *DockerCli) CmdBuild(args ...string) error {
cmd := Subcmd("build", "[OPTIONS] PATH | URL | -", "Build a new container image from the source code at PATH")
cmd := cli.Subcmd("build", "[OPTIONS] PATH | URL | -", "Build a new container image from the source code at PATH")
tag := cmd.String("t", "", "Repository name (and optionally a tag) to be applied to the resulting image in case of success")
suppressOutput := cmd.Bool("q", false, "Suppress verbose build output")
noCache := cmd.Bool("no-cache", false, "Do not use cache when building the image")
@@ -257,7 +260,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
// 'docker login': login / register a user to registry service.
func (cli *DockerCli) CmdLogin(args ...string) error {
cmd := Subcmd("login", "[OPTIONS] [SERVER]", "Register or Login to a docker registry server, if no server is specified \""+auth.IndexServerAddress()+"\" is the default.")
cmd := cli.Subcmd("login", "[OPTIONS] [SERVER]", "Register or Login to a docker registry server, if no server is specified \""+auth.IndexServerAddress()+"\" is the default.")
var username, password, email string
@@ -365,7 +368,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
// 'docker wait': block until a container stops
func (cli *DockerCli) CmdWait(args ...string) error {
cmd := Subcmd("wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.")
cmd := cli.Subcmd("wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -388,7 +391,7 @@ func (cli *DockerCli) CmdWait(args ...string) error {
// 'docker version': show version information
func (cli *DockerCli) CmdVersion(args ...string) error {
cmd := Subcmd("version", "", "Show the docker version information.")
cmd := cli.Subcmd("version", "", "Show the docker version information.")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -439,7 +442,7 @@ func (cli *DockerCli) CmdVersion(args ...string) error {
// 'docker info': display system-wide information.
func (cli *DockerCli) CmdInfo(args ...string) error {
cmd := Subcmd("info", "", "Display system-wide information")
cmd := cli.Subcmd("info", "", "Display system-wide information")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -460,10 +463,6 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
fmt.Fprintf(cli.out, "Containers: %d\n", out.Containers)
fmt.Fprintf(cli.out, "Images: %d\n", out.Images)
fmt.Fprintf(cli.out, "Driver: %s\n", out.Driver)
for _, pair := range out.DriverStatus {
fmt.Fprintf(cli.out, " %s: %s\n", pair[0], pair[1])
}
if out.Debug || os.Getenv("DEBUG") != "" {
fmt.Fprintf(cli.out, "Debug mode (server): %v\n", out.Debug)
fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "")
@@ -495,7 +494,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
}
func (cli *DockerCli) CmdStop(args ...string) error {
cmd := Subcmd("stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container (Send SIGTERM, and then SIGKILL after grace period)")
cmd := cli.Subcmd("stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container (Send SIGTERM, and then SIGKILL after grace period)")
nSeconds := cmd.Int("t", 10, "Number of seconds to wait for the container to stop before killing it.")
if err := cmd.Parse(args); err != nil {
return nil
@@ -522,7 +521,7 @@ func (cli *DockerCli) CmdStop(args ...string) error {
}
func (cli *DockerCli) CmdRestart(args ...string) error {
cmd := Subcmd("restart", "[OPTIONS] CONTAINER [CONTAINER...]", "Restart a running container")
cmd := cli.Subcmd("restart", "[OPTIONS] CONTAINER [CONTAINER...]", "Restart a running container")
nSeconds := cmd.Int("t", 10, "Number of seconds to try to stop for before killing the container. Once killed it will then be restarted. Default=10")
if err := cmd.Parse(args); err != nil {
return nil
@@ -565,7 +564,7 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
}
func (cli *DockerCli) CmdStart(args ...string) error {
cmd := Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container")
cmd := cli.Subcmd("start", "CONTAINER [CONTAINER...]", "Restart a stopped container")
attach := cmd.Bool("a", false, "Attach container's stdout/stderr and forward all signals to the process")
openStdin := cmd.Bool("i", false, "Attach container's stdin")
if err := cmd.Parse(args); err != nil {
@@ -577,6 +576,7 @@ func (cli *DockerCli) CmdStart(args ...string) error {
}
var cErr chan error
var tty bool
if *attach || *openStdin {
if cmd.NArg() > 1 {
return fmt.Errorf("Impossible to start and attach multiple containers at once.")
@@ -593,17 +593,13 @@ func (cli *DockerCli) CmdStart(args ...string) error {
return err
}
tty = container.Config.Tty
if !container.Config.Tty {
sigc := cli.forwardAllSignals(cmd.Arg(0))
defer utils.StopCatch(sigc)
}
if container.Config.Tty && cli.isTerminal {
if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
return err
}
}
var in io.ReadCloser
v := url.Values{}
@@ -641,14 +637,20 @@ func (cli *DockerCli) CmdStart(args ...string) error {
}
return encounteredError
}
if *openStdin || *attach {
if tty && cli.isTerminal {
if err := cli.monitorTtySize(cmd.Arg(0)); err != nil {
utils.Errorf("Error monitoring TTY size: %s\n", err)
}
}
return <-cErr
}
return nil
}
func (cli *DockerCli) CmdInspect(args ...string) error {
cmd := Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container/image")
cmd := cli.Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container/image")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -699,7 +701,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
}
func (cli *DockerCli) CmdTop(args ...string) error {
cmd := Subcmd("top", "CONTAINER [ps OPTIONS]", "Lookup the running processes of a container")
cmd := cli.Subcmd("top", "CONTAINER [ps OPTIONS]", "Lookup the running processes of a container")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -731,7 +733,7 @@ func (cli *DockerCli) CmdTop(args ...string) error {
}
func (cli *DockerCli) CmdPort(args ...string) error {
cmd := Subcmd("port", "CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT")
cmd := cli.Subcmd("port", "CONTAINER PRIVATE_PORT", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -773,7 +775,7 @@ func (cli *DockerCli) CmdPort(args ...string) error {
// 'docker rmi IMAGE' removes all images with the name IMAGE
func (cli *DockerCli) CmdRmi(args ...string) error {
cmd := Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images")
cmd := cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -809,7 +811,7 @@ func (cli *DockerCli) CmdRmi(args ...string) error {
}
func (cli *DockerCli) CmdHistory(args ...string) error {
cmd := Subcmd("history", "[OPTIONS] IMAGE", "Show the history of an image")
cmd := cli.Subcmd("history", "[OPTIONS] IMAGE", "Show the history of an image")
quiet := cmd.Bool("q", false, "only show numeric IDs")
noTrunc := cmd.Bool("notrunc", false, "Don't truncate output")
@@ -866,7 +868,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
}
func (cli *DockerCli) CmdRm(args ...string) error {
cmd := Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove one or more containers")
cmd := cli.Subcmd("rm", "[OPTIONS] CONTAINER [CONTAINER...]", "Remove one or more containers")
v := cmd.Bool("v", false, "Remove the volumes associated to the container")
link := cmd.Bool("link", false, "Remove the specified link and not the underlying container")
@@ -900,7 +902,7 @@ func (cli *DockerCli) CmdRm(args ...string) error {
// 'docker kill NAME' kills a running container
func (cli *DockerCli) CmdKill(args ...string) error {
cmd := Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container (send SIGKILL)")
cmd := cli.Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container (send SIGKILL)")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -922,7 +924,7 @@ func (cli *DockerCli) CmdKill(args ...string) error {
}
func (cli *DockerCli) CmdImport(args ...string) error {
cmd := Subcmd("import", "URL|- [REPOSITORY[:TAG]]", "Create a new filesystem image from the contents of a tarball(.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz).")
cmd := cli.Subcmd("import", "URL|- [REPOSITORY[:TAG]]", "Create a new filesystem image from the contents of a tarball(.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz).")
if err := cmd.Parse(args); err != nil {
return nil
@@ -956,7 +958,7 @@ func (cli *DockerCli) CmdImport(args ...string) error {
}
func (cli *DockerCli) CmdPush(args ...string) error {
cmd := Subcmd("push", "NAME", "Push an image or a repository to the registry")
cmd := cli.Subcmd("push", "NAME", "Push an image or a repository to the registry")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -1018,7 +1020,7 @@ func (cli *DockerCli) CmdPush(args ...string) error {
}
func (cli *DockerCli) CmdPull(args ...string) error {
cmd := Subcmd("pull", "NAME", "Pull an image or a repository from the registry")
cmd := cli.Subcmd("pull", "NAME", "Pull an image or a repository from the registry")
tag := cmd.String("t", "", "Download tagged image in repository")
if err := cmd.Parse(args); err != nil {
return nil
@@ -1078,9 +1080,9 @@ func (cli *DockerCli) CmdPull(args ...string) error {
}
func (cli *DockerCli) CmdImages(args ...string) error {
cmd := Subcmd("images", "[OPTIONS] [NAME]", "List images")
cmd := cli.Subcmd("images", "[OPTIONS] [NAME]", "List images")
quiet := cmd.Bool("q", false, "only show numeric IDs")
all := cmd.Bool("a", false, "show all images")
all := cmd.Bool("a", false, "show all images (by default filter out the intermediate images used to build)")
noTrunc := cmd.Bool("notrunc", false, "Don't truncate output")
flViz := cmd.Bool("viz", false, "output graph in graphviz format")
flTree := cmd.Bool("tree", false, "output graph in tree format")
@@ -1126,18 +1128,16 @@ func (cli *DockerCli) CmdImages(args ...string) error {
}
var outs []APIImages
if err := json.Unmarshal(body, &outs); err != nil {
err = json.Unmarshal(body, &outs)
if err != nil {
return err
}
var (
startImageArg = cmd.Arg(0)
startImage APIImages
roots []APIImages
byParent = make(map[string][]APIImages)
)
var startImageArg = cmd.Arg(0)
var startImage APIImages
var roots []APIImages
var byParent = make(map[string][]APIImages)
for _, image := range outs {
if image.ParentId == "" {
roots = append(roots, image)
@@ -1277,7 +1277,7 @@ func displayablePorts(ports []APIPort) string {
}
func (cli *DockerCli) CmdPs(args ...string) error {
cmd := Subcmd("ps", "[OPTIONS]", "List containers")
cmd := cli.Subcmd("ps", "[OPTIONS]", "List containers")
quiet := cmd.Bool("q", false, "Only display numeric IDs")
size := cmd.Bool("s", false, "Display sizes")
all := cmd.Bool("a", false, "Show all containers. Only running containers are shown by default.")
@@ -1366,10 +1366,10 @@ func (cli *DockerCli) CmdPs(args ...string) error {
}
func (cli *DockerCli) CmdCommit(args ...string) error {
cmd := Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes")
cmd := cli.Subcmd("commit", "[OPTIONS] CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes")
flComment := cmd.String("m", "", "Commit message")
flAuthor := cmd.String("author", "", "Author (eg. \"John Hannibal Smith <hannibal@a-team.com>\"")
flConfig := cmd.String("run", "", "Config automatically applied when the image is run. "+`(ex: {"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')`)
flConfig := cmd.String("run", "", "Config automatically applied when the image is run. "+`(ex: -run='{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')`)
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -1418,7 +1418,7 @@ func (cli *DockerCli) CmdCommit(args ...string) error {
}
func (cli *DockerCli) CmdEvents(args ...string) error {
cmd := Subcmd("events", "[OPTIONS]", "Get real time events from the server")
cmd := cli.Subcmd("events", "[OPTIONS]", "Get real time events from the server")
since := cmd.String("since", "", "Show previously created events and then stream.")
if err := cmd.Parse(args); err != nil {
return nil
@@ -1451,7 +1451,7 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
}
func (cli *DockerCli) CmdExport(args ...string) error {
cmd := Subcmd("export", "CONTAINER", "Export the contents of a filesystem as a tar archive")
cmd := cli.Subcmd("export", "CONTAINER", "Export the contents of a filesystem as a tar archive")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -1468,7 +1468,7 @@ func (cli *DockerCli) CmdExport(args ...string) error {
}
func (cli *DockerCli) CmdDiff(args ...string) error {
cmd := Subcmd("diff", "CONTAINER", "Inspect changes on a container's filesystem")
cmd := cli.Subcmd("diff", "CONTAINER", "Inspect changes on a container's filesystem")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -1494,7 +1494,7 @@ func (cli *DockerCli) CmdDiff(args ...string) error {
}
func (cli *DockerCli) CmdLogs(args ...string) error {
cmd := Subcmd("logs", "CONTAINER", "Fetch the logs of a container")
cmd := cli.Subcmd("logs", "CONTAINER", "Fetch the logs of a container")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -1521,7 +1521,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
}
func (cli *DockerCli) CmdAttach(args ...string) error {
cmd := Subcmd("attach", "[OPTIONS] CONTAINER", "Attach to a running container")
cmd := cli.Subcmd("attach", "[OPTIONS] CONTAINER", "Attach to a running container")
noStdin := cmd.Bool("nostdin", false, "Do not attach stdin")
proxy := cmd.Bool("sig-proxy", true, "Proxify all received signal to the process (even in non-tty mode)")
if err := cmd.Parse(args); err != nil {
@@ -1543,7 +1543,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
return err
}
if !container.State.Running {
if !container.State.IsRunning() {
return fmt.Errorf("Impossible to attach to a stopped container, start it first")
}
@@ -1576,7 +1576,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error {
}
func (cli *DockerCli) CmdSearch(args ...string) error {
cmd := Subcmd("search", "TERM", "Search the docker index for images")
cmd := cli.Subcmd("search", "TERM", "Search the docker index for images")
noTrunc := cmd.Bool("notrunc", false, "Don't truncate output")
trusted := cmd.Bool("trusted", false, "Only show trusted builds")
stars := cmd.Int("stars", 0, "Only displays with at least xxx stars")
@@ -1688,7 +1688,7 @@ func (opts PathOpts) Set(val string) error {
}
func (cli *DockerCli) CmdTag(args ...string) error {
cmd := Subcmd("tag", "[OPTIONS] IMAGE REPOSITORY[:TAG]", "Tag an image into a repository")
cmd := cli.Subcmd("tag", "[OPTIONS] IMAGE REPOSITORY[:TAG]", "Tag an image into a repository")
force := cmd.Bool("f", false, "Force")
if err := cmd.Parse(args); err != nil {
return nil
@@ -1721,8 +1721,223 @@ func (cli *DockerCli) CmdTag(args ...string) error {
return nil
}
//FIXME Only used in tests
func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
cmd := flag.NewFlagSet("run", flag.ContinueOnError)
cmd.SetOutput(ioutil.Discard)
cmd.Usage = nil
return parseRun(cmd, args, capabilities)
}
func parseRun(cmd *flag.FlagSet, args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
flHostname := cmd.String("h", "", "Container host name")
flWorkingDir := cmd.String("w", "", "Working directory inside the container")
flUser := cmd.String("u", "", "Username or UID")
flDetach := cmd.Bool("d", false, "Detached mode: Run container in the background, print new container id")
flAttach := NewAttachOpts()
cmd.Var(flAttach, "a", "Attach to stdin, stdout or stderr.")
flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached")
flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
flMemoryString := cmd.String("m", "", "Memory limit (format: <number><optional unit>, where unit = b, k, m or g)")
flContainerIDFile := cmd.String("cidfile", "", "Write the container ID to the file")
flNetwork := cmd.Bool("n", true, "Enable networking for this container")
flPrivileged := cmd.Bool("privileged", false, "Give extended privileges to this container")
flAutoRemove := cmd.Bool("rm", false, "Automatically remove the container when it exits (incompatible with -d)")
cmd.Bool("sig-proxy", true, "Proxify all received signal to the process (even in non-tty mode)")
cmd.String("name", "", "Assign a name to the container")
flPublishAll := cmd.Bool("P", false, "Publish all exposed ports to the host interfaces")
if capabilities != nil && *flMemoryString != "" && !capabilities.MemoryLimit {
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
*flMemoryString = ""
}
flCpuShares := cmd.Int64("c", 0, "CPU shares (relative weight)")
var flPublish utils.ListOpts
cmd.Var(&flPublish, "p", "Publish a container's port to the host (use 'docker port' to see the actual mapping)")
var flExpose utils.ListOpts
cmd.Var(&flExpose, "expose", "Expose a port from the container without publishing it to your host")
var flEnv utils.ListOpts
cmd.Var(&flEnv, "e", "Set environment variables")
var flDns utils.ListOpts
cmd.Var(&flDns, "dns", "Set custom dns servers")
flVolumes := NewPathOpts()
cmd.Var(flVolumes, "v", "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)")
var flVolumesFrom utils.ListOpts
cmd.Var(&flVolumesFrom, "volumes-from", "Mount volumes from the specified container(s)")
flEntrypoint := cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image")
var flLxcOpts utils.ListOpts
cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"")
var flLinks utils.ListOpts
cmd.Var(&flLinks, "link", "Add link to another container (name:alias)")
if err := cmd.Parse(args); err != nil {
return nil, nil, cmd, err
}
if *flDetach && len(flAttach) > 0 {
return nil, nil, cmd, ErrConflictAttachDetach
}
if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) {
return nil, nil, cmd, ErrInvalidWorikingDirectory
}
if *flDetach && *flAutoRemove {
return nil, nil, cmd, ErrConflictDetachAutoRemove
}
// If neither -d or -a are set, attach to everything by default
if len(flAttach) == 0 && !*flDetach {
if !*flDetach {
flAttach.Set("stdout")
flAttach.Set("stderr")
if *flStdin {
flAttach.Set("stdin")
}
}
}
envs := []string{}
for _, env := range flEnv {
arr := strings.Split(env, "=")
if len(arr) > 1 {
envs = append(envs, env)
} else {
v := os.Getenv(env)
envs = append(envs, env+"="+v)
}
}
var flMemory int64
if *flMemoryString != "" {
parsedMemory, err := utils.RAMInBytes(*flMemoryString)
if err != nil {
return nil, nil, cmd, err
}
flMemory = parsedMemory
}
var binds []string
// add any bind targets to the list of container volumes
for bind := range flVolumes {
arr := strings.Split(bind, ":")
if len(arr) > 1 {
if arr[0] == "/" {
return nil, nil, cmd, fmt.Errorf("Invalid bind mount: source can't be '/'")
}
dstDir := arr[1]
flVolumes[dstDir] = struct{}{}
binds = append(binds, bind)
delete(flVolumes, bind)
}
}
parsedArgs := cmd.Args()
runCmd := []string{}
entrypoint := []string{}
image := ""
if len(parsedArgs) >= 1 {
image = cmd.Arg(0)
}
if len(parsedArgs) > 1 {
runCmd = parsedArgs[1:]
}
if *flEntrypoint != "" {
entrypoint = []string{*flEntrypoint}
}
var lxcConf []KeyValuePair
lxcConf, err := parseLxcConfOpts(flLxcOpts)
if err != nil {
return nil, nil, cmd, err
}
hostname := *flHostname
domainname := ""
parts := strings.SplitN(hostname, ".", 2)
if len(parts) > 1 {
hostname = parts[0]
domainname = parts[1]
}
ports, portBindings, err := parsePortSpecs(flPublish)
if err != nil {
return nil, nil, cmd, err
}
// Merge in exposed ports to the map of published ports
for _, e := range flExpose {
if strings.Contains(e, ":") {
return nil, nil, cmd, fmt.Errorf("Invalid port format for -expose: %s", e)
}
p := NewPort(splitProtoPort(e))
if _, exists := ports[p]; !exists {
ports[p] = struct{}{}
}
}
config := &Config{
Hostname: hostname,
Domainname: domainname,
PortSpecs: nil, // Deprecated
ExposedPorts: ports,
User: *flUser,
Tty: *flTty,
NetworkDisabled: !*flNetwork,
OpenStdin: *flStdin,
Memory: flMemory,
CpuShares: *flCpuShares,
AttachStdin: flAttach.Get("stdin"),
AttachStdout: flAttach.Get("stdout"),
AttachStderr: flAttach.Get("stderr"),
Env: envs,
Cmd: runCmd,
Dns: flDns,
Image: image,
Volumes: flVolumes,
VolumesFrom: strings.Join(flVolumesFrom, ","),
Entrypoint: entrypoint,
WorkingDir: *flWorkingDir,
}
hostConfig := &HostConfig{
Binds: binds,
ContainerIDFile: *flContainerIDFile,
LxcConf: lxcConf,
Privileged: *flPrivileged,
PortBindings: portBindings,
Links: flLinks,
PublishAllPorts: *flPublishAll,
}
if capabilities != nil && flMemory > 0 && !capabilities.SwapLimit {
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
config.MemorySwap = -1
}
// When allocating stdin in attached mode, close stdin at client disconnect
if config.OpenStdin && config.AttachStdin {
config.StdinOnce = true
}
return config, hostConfig, cmd, nil
}
func (cli *DockerCli) CmdRun(args ...string) error {
config, hostConfig, cmd, err := ParseRun(args, nil)
config, hostConfig, cmd, err := parseRun(cli.Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container"), args, nil)
if err != nil {
return err
}
@@ -1933,7 +2148,7 @@ func (cli *DockerCli) CmdRun(args ...string) error {
}
func (cli *DockerCli) CmdCp(args ...string) error {
cmd := Subcmd("cp", "CONTAINER:RESOURCE HOSTPATH", "Copy files/folders from the RESOURCE to the HOSTPATH")
cmd := cli.Subcmd("cp", "CONTAINER:PATH HOSTPATH", "Copy files/folders from the PATH to the HOSTPATH")
if err := cmd.Parse(args); err != nil {
return nil
}
@@ -1947,7 +2162,7 @@ func (cli *DockerCli) CmdCp(args ...string) error {
info := strings.Split(cmd.Arg(0), ":")
if len(info) != 2 {
return fmt.Errorf("Error: Resource not specified")
return fmt.Errorf("Error: Path not specified")
}
copyData.Resource = info[1]
@@ -1960,13 +2175,48 @@ func (cli *DockerCli) CmdCp(args ...string) error {
if statusCode == 200 {
r := bytes.NewReader(data)
if err := archive.Untar(r, copyData.HostPath, nil); err != nil {
if err := archive.Untar(r, copyData.HostPath); err != nil {
return err
}
}
return nil
}
func (cli *DockerCli) CmdSave(args ...string) error {
cmd := cli.Subcmd("save", "IMAGE DESTINATION", "Save an image to a tar archive")
if err := cmd.Parse(args); err != nil {
return err
}
if cmd.NArg() != 1 {
cmd.Usage()
return nil
}
image := cmd.Arg(0)
if err := cli.stream("GET", "/images/"+image+"/get", nil, cli.out, nil); err != nil {
return err
}
return nil
}
func (cli *DockerCli) CmdLoad(args ...string) error {
cmd := cli.Subcmd("load", "SOURCE", "Load an image from a tar archive")
if err := cmd.Parse(args); err != nil {
return err
}
if cmd.NArg() != 0 {
cmd.Usage()
return nil
}
if err := cli.stream("POST", "/images/load", cli.in, cli.out, nil); err != nil {
return err
}
return nil
}
func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, error) {
var params io.Reader
if data != nil {
@@ -2217,12 +2467,12 @@ func (cli *DockerCli) monitorTtySize(id string) error {
return nil
}
func Subcmd(name, signature, description string) *flag.FlagSet {
func (cli *DockerCli) Subcmd(name, signature, description string) *flag.FlagSet {
flags := flag.NewFlagSet(name, flag.ContinueOnError)
flags.Usage = func() {
// FIXME: use custom stdout or return error
fmt.Fprintf(os.Stdout, "\nUsage: docker %s %s\n\n%s\n\n", name, signature, description)
fmt.Fprintf(cli.err, "\nUsage: docker %s %s\n\n%s\n\n", name, signature, description)
flags.PrintDefaults()
os.Exit(2)
}
return flags
}
@@ -2267,7 +2517,7 @@ func getExitCode(cli *DockerCli, containerId string) (bool, int, error) {
if err := json.Unmarshal(body, c); err != nil {
return false, -1, err
}
return c.State.Running, c.State.ExitCode, nil
return c.State.IsRunning(), c.State.GetExitCode(), nil
}
func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *DockerCli {
-2
View File
@@ -16,7 +16,6 @@ type DaemonConfig struct {
BridgeIface string
DefaultIp net.IP
InterContainerCommunication bool
GraphDriver string
}
// ConfigFromJob creates and returns a new DaemonConfig object
@@ -38,6 +37,5 @@ func ConfigFromJob(job *engine.Job) *DaemonConfig {
}
config.DefaultIp = net.ParseIP(job.Getenv("DefaultIp"))
config.InterContainerCommunication = job.GetenvBool("InterContainerCommunication")
config.GraphDriver = job.Getenv("GraphDriver")
return &config
}
+112 -304
View File
@@ -4,10 +4,8 @@ import (
"bytes"
"encoding/json"
"errors"
"flag"
"fmt"
"github.com/dotcloud/docker/archive"
"github.com/dotcloud/docker/graphdriver"
"github.com/dotcloud/docker/term"
"github.com/dotcloud/docker/utils"
"github.com/kr/pty"
@@ -21,13 +19,15 @@ import (
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
"time"
)
type Container struct {
root string // Path to the "home" of the container, including metadata.
rootfs string // Path to the root filesystem of the container.
sync.Mutex
root string
ID string
@@ -48,7 +48,6 @@ type Container struct {
HostnamePath string
HostsPath string
Name string
Driver string
cmd *exec.Cmd
stdout *utils.WriteBroadcaster
@@ -159,218 +158,6 @@ func NewPort(proto, port string) Port {
return Port(fmt.Sprintf("%s/%s", port, proto))
}
func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, *flag.FlagSet, error) {
cmd := Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container")
if os.Getenv("TEST") != "" {
cmd.SetOutput(ioutil.Discard)
cmd.Usage = nil
}
flHostname := cmd.String("h", "", "Container host name")
flWorkingDir := cmd.String("w", "", "Working directory inside the container")
flUser := cmd.String("u", "", "Username or UID")
flDetach := cmd.Bool("d", false, "Detached mode: Run container in the background, print new container id")
flAttach := NewAttachOpts()
cmd.Var(flAttach, "a", "Attach to stdin, stdout or stderr.")
flStdin := cmd.Bool("i", false, "Keep stdin open even if not attached")
flTty := cmd.Bool("t", false, "Allocate a pseudo-tty")
flMemoryString := cmd.String("m", "", "Memory limit (format: <number><optional unit>, where unit = b, k, m or g)")
flContainerIDFile := cmd.String("cidfile", "", "Write the container ID to the file")
flNetwork := cmd.Bool("n", true, "Enable networking for this container")
flPrivileged := cmd.Bool("privileged", false, "Give extended privileges to this container")
flAutoRemove := cmd.Bool("rm", false, "Automatically remove the container when it exits (incompatible with -d)")
cmd.Bool("sig-proxy", true, "Proxify all received signal to the process (even in non-tty mode)")
cmd.String("name", "", "Assign a name to the container")
flPublishAll := cmd.Bool("P", false, "Publish all exposed ports to the host interfaces")
if capabilities != nil && *flMemoryString != "" && !capabilities.MemoryLimit {
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support memory limit capabilities. Limitation discarded.\n")
*flMemoryString = ""
}
flCpuShares := cmd.Int64("c", 0, "CPU shares (relative weight)")
var flPublish utils.ListOpts
cmd.Var(&flPublish, "p", "Publish a container's port to the host (use 'docker port' to see the actual mapping)")
var flExpose utils.ListOpts
cmd.Var(&flExpose, "expose", "Expose a port from the container without publishing it to your host")
var flEnv utils.ListOpts
cmd.Var(&flEnv, "e", "Set environment variables")
var flDns utils.ListOpts
cmd.Var(&flDns, "dns", "Set custom dns servers")
flVolumes := NewPathOpts()
cmd.Var(flVolumes, "v", "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)")
var flVolumesFrom utils.ListOpts
cmd.Var(&flVolumesFrom, "volumes-from", "Mount volumes from the specified container(s)")
flEntrypoint := cmd.String("entrypoint", "", "Overwrite the default entrypoint of the image")
var flLxcOpts utils.ListOpts
cmd.Var(&flLxcOpts, "lxc-conf", "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"")
var flLinks utils.ListOpts
cmd.Var(&flLinks, "link", "Add link to another container (name:alias)")
if err := cmd.Parse(args); err != nil {
return nil, nil, cmd, err
}
if *flDetach && len(flAttach) > 0 {
return nil, nil, cmd, ErrConflictAttachDetach
}
if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) {
return nil, nil, cmd, ErrInvalidWorikingDirectory
}
if *flDetach && *flAutoRemove {
return nil, nil, cmd, ErrConflictDetachAutoRemove
}
// If neither -d or -a are set, attach to everything by default
if len(flAttach) == 0 && !*flDetach {
if !*flDetach {
flAttach.Set("stdout")
flAttach.Set("stderr")
if *flStdin {
flAttach.Set("stdin")
}
}
}
envs := []string{}
for _, env := range flEnv {
arr := strings.Split(env, "=")
if len(arr) > 1 {
envs = append(envs, env)
} else {
v := os.Getenv(env)
envs = append(envs, env+"="+v)
}
}
var flMemory int64
if *flMemoryString != "" {
parsedMemory, err := utils.RAMInBytes(*flMemoryString)
if err != nil {
return nil, nil, cmd, err
}
flMemory = parsedMemory
}
var binds []string
// add any bind targets to the list of container volumes
for bind := range flVolumes {
arr := strings.Split(bind, ":")
if len(arr) > 1 {
if arr[0] == "/" {
return nil, nil, cmd, fmt.Errorf("Invalid bind mount: source can't be '/'")
}
dstDir := arr[1]
flVolumes[dstDir] = struct{}{}
binds = append(binds, bind)
delete(flVolumes, bind)
}
}
parsedArgs := cmd.Args()
runCmd := []string{}
entrypoint := []string{}
image := ""
if len(parsedArgs) >= 1 {
image = cmd.Arg(0)
}
if len(parsedArgs) > 1 {
runCmd = parsedArgs[1:]
}
if *flEntrypoint != "" {
entrypoint = []string{*flEntrypoint}
}
var lxcConf []KeyValuePair
lxcConf, err := parseLxcConfOpts(flLxcOpts)
if err != nil {
return nil, nil, cmd, err
}
hostname := *flHostname
domainname := ""
parts := strings.SplitN(hostname, ".", 2)
if len(parts) > 1 {
hostname = parts[0]
domainname = parts[1]
}
ports, portBindings, err := parsePortSpecs(flPublish)
if err != nil {
return nil, nil, cmd, err
}
// Merge in exposed ports to the map of published ports
for _, e := range flExpose {
if strings.Contains(e, ":") {
return nil, nil, cmd, fmt.Errorf("Invalid port format for -expose: %s", e)
}
p := NewPort(splitProtoPort(e))
if _, exists := ports[p]; !exists {
ports[p] = struct{}{}
}
}
config := &Config{
Hostname: hostname,
Domainname: domainname,
PortSpecs: nil, // Deprecated
ExposedPorts: ports,
User: *flUser,
Tty: *flTty,
NetworkDisabled: !*flNetwork,
OpenStdin: *flStdin,
Memory: flMemory,
CpuShares: *flCpuShares,
AttachStdin: flAttach.Get("stdin"),
AttachStdout: flAttach.Get("stdout"),
AttachStderr: flAttach.Get("stderr"),
Env: envs,
Cmd: runCmd,
Dns: flDns,
Image: image,
Volumes: flVolumes,
VolumesFrom: strings.Join(flVolumesFrom, ","),
Entrypoint: entrypoint,
WorkingDir: *flWorkingDir,
}
hostConfig := &HostConfig{
Binds: binds,
ContainerIDFile: *flContainerIDFile,
LxcConf: lxcConf,
Privileged: *flPrivileged,
PortBindings: portBindings,
Links: flLinks,
PublishAllPorts: *flPublishAll,
}
if capabilities != nil && flMemory > 0 && !capabilities.SwapLimit {
//fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n")
config.MemorySwap = -1
}
// When allocating stdin in attached mode, close stdin at client disconnect
if config.OpenStdin && config.AttachStdin {
config.StdinOnce = true
}
return config, hostConfig, cmd, nil
}
type PortMapping map[string]string // Deprecated
type NetworkSettings struct {
@@ -409,13 +196,8 @@ func (settings *NetworkSettings) PortMappingAPI() []APIPort {
// Inject the io.Reader at the given path. Note: do not close the reader
func (container *Container) Inject(file io.Reader, pth string) error {
if err := container.EnsureMounted(); err != nil {
return fmt.Errorf("inject: error mounting container %s: %s", container.ID, err)
}
// Return error if path exists
destPath := path.Join(container.RootfsPath(), pth)
if _, err := os.Stat(destPath); err == nil {
if _, err := os.Stat(path.Join(container.rwPath(), pth)); err == nil {
// Since err is nil, the path could be stat'd and it exists
return fmt.Errorf("%s exists", pth)
} else if !os.IsNotExist(err) {
@@ -426,16 +208,14 @@ func (container *Container) Inject(file io.Reader, pth string) error {
}
// Make sure the directory exists
if err := os.MkdirAll(path.Join(container.RootfsPath(), path.Dir(pth)), 0755); err != nil {
if err := os.MkdirAll(path.Join(container.rwPath(), path.Dir(pth)), 0755); err != nil {
return err
}
dest, err := os.Create(destPath)
dest, err := os.Create(path.Join(container.rwPath(), pth))
if err != nil {
return err
}
defer dest.Close()
if _, err := io.Copy(dest, file); err != nil {
return err
}
@@ -710,9 +490,10 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
}
func (container *Container) Start() (err error) {
container.State.Lock()
defer container.State.Unlock()
if container.State.Running {
container.Lock()
defer container.Unlock()
if container.State.IsRunning() {
return fmt.Errorf("The container %s is already running.", container.ID)
}
defer func() {
@@ -826,7 +607,6 @@ func (container *Container) Start() (err error) {
}
}
volumesDriver := container.runtime.volumes.driver
// Create the requested volumes if they don't exist
for volPath := range container.Config.Volumes {
volPath = path.Clean(volPath)
@@ -846,17 +626,13 @@ func (container *Container) Start() (err error) {
}
// Otherwise create an directory in $ROOT/volumes/ and use that
} else {
// Do not pass a container as the parameter for the volume creation.
// The graph driver using the container's information ( Image ) to
// create the parent.
c, err := container.runtime.volumes.Create(nil, nil, "", "", nil)
c, err := container.runtime.volumes.Create(nil, container, "", "", nil)
if err != nil {
return err
}
srcPath, err = volumesDriver.Get(c.ID)
srcPath, err = c.layer()
if err != nil {
return fmt.Errorf("Driver %s failed to get volume rootfs %s: %s", volumesDriver, c.ID, err)
return err
}
srcRW = true // RW by default
}
@@ -1046,7 +822,7 @@ func (container *Container) Start() (err error) {
}
// FIXME: save state on disk *first*, then converge
// this way disk state is used as a journal, eg. we can restore after crash etc.
container.State.setRunning(container.cmd.Process.Pid)
container.State.SetRunning(container.cmd.Process.Pid)
// Init the lock
container.waitLock = make(chan struct{})
@@ -1054,14 +830,14 @@ func (container *Container) Start() (err error) {
container.ToDisk()
go container.monitor()
defer utils.Debugf("Container running: %v", container.State.Running)
defer utils.Debugf("Container running: %v", container.State.IsRunning())
// We wait for the container to be fully running.
// Timeout after 5 seconds. In case of broken pipe, just retry.
// Note: The container can run and finish correctly before
// the end of this loop
for now := time.Now(); time.Since(now) < 5*time.Second; {
// If the container dies while waiting for it, just return
if !container.State.Running {
if !container.State.IsRunning() {
return nil
}
output, err := exec.Command("lxc-info", "-s", "-n", container.ID).CombinedOutput()
@@ -1078,11 +854,11 @@ func (container *Container) Start() (err error) {
if strings.Contains(string(output), "RUNNING") {
return nil
}
utils.Debugf("Waiting for the container to start (running: %v): %s", container.State.Running, bytes.TrimSpace(output))
utils.Debugf("Waiting for the container to start (running: %v): %s", container.State.IsRunning(), bytes.TrimSpace(output))
time.Sleep(50 * time.Millisecond)
}
if container.State.Running {
if container.State.IsRunning() {
return ErrContainerStartTimeout
}
return ErrContainerStart
@@ -1163,11 +939,12 @@ func (container *Container) allocateNetwork() error {
return nil
}
var iface *NetworkInterface
var err error
if container.State.Ghost {
manager := container.runtime.networkManager
if manager.disabled {
var (
iface *NetworkInterface
err error
)
if container.State.IsGhost() {
if manager := container.runtime.networkManager; manager.disabled {
iface = &NetworkInterface{disabled: true}
} else {
iface = &NetworkInterface{
@@ -1203,10 +980,12 @@ func (container *Container) allocateNetwork() error {
}
}
portSpecs := make(map[Port]struct{})
bindings := make(map[Port][]PortBinding)
var (
portSpecs = make(map[Port]struct{})
bindings = make(map[Port][]PortBinding)
)
if !container.State.Ghost {
if !container.State.IsGhost() {
if container.Config.ExposedPorts != nil {
portSpecs = container.Config.ExposedPorts
}
@@ -1315,7 +1094,7 @@ func (container *Container) monitor() {
}
// Report status back
container.State.setStopped(exitCode)
container.State.SetStopped(exitCode)
// Release the lock
close(container.waitLock)
@@ -1365,10 +1144,10 @@ func (container *Container) cleanup() {
}
func (container *Container) kill(sig int) error {
container.State.Lock()
defer container.State.Unlock()
container.Lock()
defer container.Unlock()
if !container.State.Running {
if !container.State.IsRunning() {
return nil
}
@@ -1381,7 +1160,7 @@ func (container *Container) kill(sig int) error {
}
func (container *Container) Kill() error {
if !container.State.Running {
if !container.State.IsRunning() {
return nil
}
@@ -1406,7 +1185,7 @@ func (container *Container) Kill() error {
}
func (container *Container) Stop(seconds int) error {
if !container.State.Running {
if !container.State.IsRunning() {
return nil
}
@@ -1440,7 +1219,7 @@ func (container *Container) Restart(seconds int) error {
// Wait blocks until the container stops running, then returns its exit code.
func (container *Container) Wait() int {
<-container.waitLock
return container.State.ExitCode
return container.State.GetExitCode()
}
func (container *Container) Resize(h, w int) error {
@@ -1452,14 +1231,15 @@ func (container *Container) Resize(h, w int) error {
}
func (container *Container) ExportRw() (archive.Archive, error) {
if err := container.EnsureMounted(); err != nil {
return nil, err
}
if container.runtime == nil {
return nil, fmt.Errorf("Can't load storage driver for unregistered container %s", container.ID)
}
return archive.Tar(container.rwPath(), archive.Uncompressed)
}
return container.runtime.Diff(container)
func (container *Container) RwChecksum() (string, error) {
rwData, err := archive.Tar(container.rwPath(), archive.Xz)
if err != nil {
return "", err
}
return utils.HashData(rwData)
}
func (container *Container) Export() (archive.Archive, error) {
@@ -1485,17 +1265,28 @@ func (container *Container) WaitTimeout(timeout time.Duration) error {
}
func (container *Container) EnsureMounted() error {
// FIXME: EnsureMounted is deprecated because drivers are now responsible
// for re-entrant mounting in their Get() method.
if mounted, err := container.Mounted(); err != nil {
return err
} else if mounted {
return nil
}
return container.Mount()
}
func (container *Container) Mount() error {
return container.runtime.Mount(container)
image, err := container.GetImage()
if err != nil {
return err
}
return image.Mount(container.RootfsPath(), container.rwPath())
}
func (container *Container) Changes() ([]archive.Change, error) {
return container.runtime.Changes(container)
func (container *Container) Changes() ([]Change, error) {
image, err := container.GetImage()
if err != nil {
return nil, err
}
return image.Changes(container.rwPath())
}
func (container *Container) GetImage() (*Image, error) {
@@ -1505,8 +1296,18 @@ func (container *Container) GetImage() (*Image, error) {
return container.runtime.graph.Get(container.Image)
}
func (container *Container) Mounted() (bool, error) {
return Mounted(container.RootfsPath())
}
func (container *Container) Unmount() error {
return container.runtime.Unmount(container)
if _, err := os.Stat(container.RootfsPath()); err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
return Unmount(container.RootfsPath())
}
func (container *Container) logPath(name string) string {
@@ -1535,7 +1336,11 @@ func (container *Container) lxcConfigPath() string {
// This method must be exported to be used from the lxc template
func (container *Container) RootfsPath() string {
return container.rootfs
return path.Join(container.root, "rootfs")
}
func (container *Container) rwPath() string {
return path.Join(container.root, "rw")
}
func validateID(id string) error {
@@ -1547,40 +1352,47 @@ func validateID(id string) error {
// GetSize, return real size, virtual size
func (container *Container) GetSize() (int64, int64) {
var (
sizeRw, sizeRootfs int64
err error
driver = container.runtime.driver
)
var sizeRw, sizeRootfs int64
data := make(map[uint64]bool)
if err := container.EnsureMounted(); err != nil {
utils.Errorf("Warning: failed to compute size of container rootfs %s: %s", container.ID, err)
return sizeRw, sizeRootfs
}
if differ, ok := container.runtime.driver.(graphdriver.Differ); ok {
sizeRw, err = differ.DiffSize(container.ID)
if err != nil {
utils.Errorf("Warning: driver %s couldn't return diff size of container %s: %s", driver, container.ID, err)
// FIXME: GetSize should return an error. Not changing it now in case
// there is a side-effect.
sizeRw = -1
filepath.Walk(container.rwPath(), func(path string, fileInfo os.FileInfo, err error) error {
if fileInfo == nil {
return nil
}
} else {
changes, _ := container.Changes()
if changes != nil {
sizeRw = archive.ChangesSize(container.RootfsPath(), changes)
} else {
sizeRw = -1
size := fileInfo.Size()
if size == 0 {
return nil
}
}
_, err = os.Stat(container.RootfsPath())
inode := fileInfo.Sys().(*syscall.Stat_t).Ino
if _, entryExists := data[inode]; entryExists {
return nil
}
data[inode] = false
sizeRw += size
return nil
})
data = make(map[uint64]bool)
_, err := os.Stat(container.RootfsPath())
if err == nil {
filepath.Walk(container.RootfsPath(), func(path string, fileInfo os.FileInfo, err error) error {
if fileInfo != nil {
sizeRootfs += fileInfo.Size()
if fileInfo == nil {
return nil
}
size := fileInfo.Size()
if size == 0 {
return nil
}
inode := fileInfo.Sys().(*syscall.Stat_t).Ino
if _, entryExists := data[inode]; entryExists {
return nil
}
data[inode] = false
sizeRootfs += size
return nil
})
}
@@ -1605,11 +1417,7 @@ func (container *Container) Copy(resource string) (archive.Archive, error) {
filter = []string{path.Base(basePath)}
basePath = path.Dir(basePath)
}
return archive.TarFilter(basePath, &archive.TarOptions{
Compression: archive.Uncompressed,
Includes: filter,
Recursive: true,
})
return archive.TarFilter(basePath, archive.Uncompressed, filter)
}
// Returns true if the container exposes a certain port
-170
View File
@@ -1,170 +0,0 @@
package main
import (
"flag"
"fmt"
"github.com/dotcloud/docker/graphdriver/devmapper"
"os"
"path"
"sort"
"strconv"
"strings"
)
func usage() {
fmt.Fprintf(os.Stderr, "Usage: %s <flags> [status] | [list] | [device id] | [resize new-pool-size] | [snap new-id base-id] | [remove id] | [mount id mountpoint]\n", os.Args[0])
flag.PrintDefaults()
os.Exit(1)
}
func byteSizeFromString(arg string) (int64, error) {
digits := ""
rest := ""
last := strings.LastIndexAny(arg, "0123456789")
if last >= 0 {
digits = arg[:last+1]
rest = arg[last+1:]
}
val, err := strconv.ParseInt(digits, 10, 64)
if err != nil {
return val, err
}
rest = strings.ToLower(strings.TrimSpace(rest))
var multiplier int64 = 1
switch rest {
case "":
multiplier = 1
case "k", "kb":
multiplier = 1024
case "m", "mb":
multiplier = 1024 * 1024
case "g", "gb":
multiplier = 1024 * 1024 * 1024
case "t", "tb":
multiplier = 1024 * 1024 * 1024 * 1024
default:
return 0, fmt.Errorf("Unknown size unit: %s", rest)
}
return val * multiplier, nil
}
func main() {
root := flag.String("r", "/var/lib/docker", "Docker root dir")
flDebug := flag.Bool("D", false, "Debug mode")
flag.Parse()
if *flDebug {
os.Setenv("DEBUG", "1")
}
if flag.NArg() < 1 {
usage()
}
args := flag.Args()
home := path.Join(*root, "devicemapper")
devices, err := devmapper.NewDeviceSet(home, false)
if err != nil {
fmt.Println("Can't initialize device mapper: ", err)
os.Exit(1)
}
switch args[0] {
case "status":
status := devices.Status()
fmt.Printf("Pool name: %s\n", status.PoolName)
fmt.Printf("Data Loopback file: %s\n", status.DataLoopback)
fmt.Printf("Metadata Loopback file: %s\n", status.MetadataLoopback)
fmt.Printf("Sector size: %d\n", status.SectorSize)
fmt.Printf("Data use: %d of %d (%.1f %%)\n", status.Data.Used, status.Data.Total, 100.0*float64(status.Data.Used)/float64(status.Data.Total))
fmt.Printf("Metadata use: %d of %d (%.1f %%)\n", status.Metadata.Used, status.Metadata.Total, 100.0*float64(status.Metadata.Used)/float64(status.Metadata.Total))
break
case "list":
ids := devices.List()
sort.Strings(ids)
for _, id := range ids {
fmt.Println(id)
}
break
case "device":
if flag.NArg() < 2 {
usage()
}
status, err := devices.GetDeviceStatus(args[1])
if err != nil {
fmt.Println("Can't get device info: ", err)
os.Exit(1)
}
fmt.Printf("Id: %d\n", status.DeviceId)
fmt.Printf("Size: %d\n", status.Size)
fmt.Printf("Transaction Id: %d\n", status.TransactionId)
fmt.Printf("Size in Sectors: %d\n", status.SizeInSectors)
fmt.Printf("Mapped Sectors: %d\n", status.MappedSectors)
fmt.Printf("Highest Mapped Sector: %d\n", status.HighestMappedSector)
break
case "resize":
if flag.NArg() < 2 {
usage()
}
size, err := byteSizeFromString(args[1])
if err != nil {
fmt.Println("Invalid size: ", err)
os.Exit(1)
}
err = devices.ResizePool(size)
if err != nil {
fmt.Println("Error resizeing pool: ", err)
os.Exit(1)
}
break
case "snap":
if flag.NArg() < 3 {
usage()
}
err := devices.AddDevice(args[1], args[2])
if err != nil {
fmt.Println("Can't create snap device: ", err)
os.Exit(1)
}
break
case "remove":
if flag.NArg() < 2 {
usage()
}
err := devices.RemoveDevice(args[1])
if err != nil {
fmt.Println("Can't remove device: ", err)
os.Exit(1)
}
break
case "mount":
if flag.NArg() < 3 {
usage()
}
err := devices.MountDevice(args[1], args[2], false)
if err != nil {
fmt.Println("Can't create snap device: ", err)
os.Exit(1)
}
break
default:
fmt.Printf("Unknown command %s\n", args[0])
usage()
os.Exit(1)
}
return
}
-2
View File
@@ -38,7 +38,6 @@ func main() {
flEnableIptables := flag.Bool("iptables", true, "Disable iptables within docker")
flDefaultIp := flag.String("ip", "0.0.0.0", "Default ip address to use when binding a containers ports")
flInterContainerComm := flag.Bool("icc", true, "Enable inter-container communication")
flGraphDriver := flag.String("graph-driver", "", "For docker to use a specific graph driver")
flag.Parse()
@@ -83,7 +82,6 @@ func main() {
job.Setenv("BridgeIface", *bridgeName)
job.Setenv("DefaultIp", *flDefaultIp)
job.SetenvBool("InterContainerCommunication", *flInterContainerComm)
job.Setenv("GraphDriver", *flGraphDriver)
if err := job.Run(); err != nil {
log.Fatal(err)
}
@@ -1171,6 +1171,53 @@ Monitor Docker's events
:statuscode 200: no error
:statuscode 500: server error
Get a tarball containing all images and tags in a repository
************************************************************
.. http:get:: /images/(name)/get
Get a tarball containing all images and metadata for the repository specified by ``name``.
**Example request**
.. sourcecode:: http
GET /images/ubuntu/get
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/x-tar
Binary data stream
:statuscode 200: no error
:statuscode 500: server error
Load a tarball with a set of images and tags into docker
********************************************************
.. http:post:: /images/load
Load a set of images and tags into the docker repository.
**Example request**
.. sourcecode:: http
POST /images/load
Tarball in body
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
:statuscode 200: no error
:statuscode 500: server error
3. Going further
================
@@ -1,4 +1,4 @@
:title: Registry API
:title: Remote API Client Libraries
:description: Various client libraries available to use with the Docker remote API
:keywords: API, Docker, index, registry, REST, documentation, clients, Python, Ruby, Javascript, Erlang, Go
+119 -24
View File
@@ -88,31 +88,65 @@ Examples:
Usage: docker build [OPTIONS] PATH | URL | -
Build a new container image from the source code at PATH
-t="": Repository name (and optionally a tag) to be applied to the resulting image in case of success.
-t="": Repository name (and optionally a tag) to be applied
to the resulting image in case of success.
-q=false: Suppress verbose build output.
-no-cache: Do not use the cache when building the image.
-rm: Remove intermediate containers after a successful build
When a single Dockerfile is given as URL, then no context is set. When a git repository is set as URL, the repository is used as context
The files at PATH or URL are called the "context" of the build. The
build process may refer to any of the files in the context, for
example when using an :ref:`ADD <dockerfile_add>` instruction. When a
single ``Dockerfile`` is given as URL, then no context is set. When a
git repository is set as URL, then the repository is used as the
context
.. _cli_build_examples:
.. seealso:: :ref:`dockerbuilder`.
Examples:
~~~~~~~~~
.. code-block:: bash
sudo docker build .
Uploading context 10240 bytes
Step 1 : FROM busybox
Pulling repository busybox
---> e9aa60c60128MB/2.284 MB (100%) endpoint: https://cdn-registry-1.docker.io/v1/
Step 2 : RUN ls -lh /
---> Running in 9c9e81692ae9
total 24
drwxr-xr-x 2 root root 4.0K Mar 12 2013 bin
drwxr-xr-x 5 root root 4.0K Oct 19 00:19 dev
drwxr-xr-x 2 root root 4.0K Oct 19 00:19 etc
drwxr-xr-x 2 root root 4.0K Nov 15 23:34 lib
lrwxrwxrwx 1 root root 3 Mar 12 2013 lib64 -> lib
dr-xr-xr-x 116 root root 0 Nov 15 23:34 proc
lrwxrwxrwx 1 root root 3 Mar 12 2013 sbin -> bin
dr-xr-xr-x 13 root root 0 Nov 15 23:34 sys
drwxr-xr-x 2 root root 4.0K Mar 12 2013 tmp
drwxr-xr-x 2 root root 4.0K Nov 15 23:34 usr
---> b35f4035db3f
Step 3 : CMD echo Hello World
---> Running in 02071fceb21b
---> f52f38b7823e
Successfully built f52f38b7823e
This will read the ``Dockerfile`` from the current directory. It will
also send any other files and directories found in the current
directory to the ``docker`` daemon.
This example specifies that the PATH is ``.``, and so all the files in
the local directory get tar'd and sent to the Docker daemon. The PATH
specifies where to find the files for the "context" of the build on
the Docker daemon. Remember that the daemon could be running on a
remote machine and that no parsing of the Dockerfile happens at the
client side (where you're running ``docker build``). That means that
*all* the files at PATH get sent, not just the ones listed to
:ref:`ADD <dockerfile_add>` in the ``Dockerfile``.
The transfer of context from the local machine to the Docker daemon is
what the ``docker`` client means when you see the "Uploading context"
message.
The contents of this directory would be used by ``ADD`` commands found
within the ``Dockerfile``. This will send a lot of data to the
``docker`` daemon if the current directory contains a lot of data. If
the absolute path is provided instead of ``.`` then only the files and
directories required by the ADD commands from the ``Dockerfile`` will be
added to the context and transferred to the ``docker`` daemon.
.. code-block:: bash
@@ -129,16 +163,15 @@ tag will be ``2.0``
This will read a ``Dockerfile`` from *stdin* without context. Due to
the lack of a context, no contents of any local directory will be sent
to the ``docker`` daemon. ``ADD`` doesn't work when running in this
mode because the absence of the context provides no source files to
copy to the container.
to the ``docker`` daemon. Since there is no context, a Dockerfile
``ADD`` only works if it refers to a remote URL.
.. code-block:: bash
sudo docker build github.com/creack/docker-firefox
This will clone the Github repository and use it as context. The
``Dockerfile`` at the root of the repository is used as
This will clone the Github repository and use the cloned repository as
context. The ``Dockerfile`` at the root of the repository is used as
``Dockerfile``. Note that you can specify an arbitrary git repository
by using the ``git://`` schema.
@@ -157,7 +190,7 @@ by using the ``git://`` schema.
-m="": Commit message
-author="": Author (eg. "John Hannibal Smith <hannibal@a-team.com>"
-run="": Configuration to be applied when the image is launched with `docker run`.
(ex: '{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')
(ex: -run='{"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')
Simple commit of an existing container
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -173,7 +206,7 @@ Simple commit of an existing container
$ docker images | head
REPOSITORY TAG ID CREATED SIZE
SvenDowideit/testimage version3 f5283438590d 16 seconds ago 204.2 MB (virtual 335.7 MB)
S
Full -run example
.................
@@ -219,10 +252,15 @@ Full -run example
::
Usage: docker cp CONTAINER:RESOURCE HOSTPATH
Usage: docker cp CONTAINER:PATH HOSTPATH
Copy files/folders from the containers filesystem to the host
path. Paths are relative to the root of the filesystem.
.. code-block:: bash
$ sudo docker cp 7bb0e258aefe:/etc/debian_version .
$ sudo docker cp blue_frog:/etc/hosts .
.. _cli_diff:
@@ -392,18 +430,52 @@ To see how the docker:latest image was built:
List images
-a=false: show all images
-a=false: show all images (by default filter out the intermediate images used to build)
-notrunc=false: Don't truncate output
-q=false: only show numeric IDs
-tree=false: output graph in tree format
-viz=false: output graph in graphviz format
Listing the most recently created images
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: bash
$ sudo docker images | head
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> 77af4d6b9913 19 hours ago 30.53 MB (virtual 1.089 GB)
committest latest b6fa739cedf5 19 hours ago 30.53 MB (virtual 1.089 GB)
<none> <none> 78a85c484f71 19 hours ago 30.53 MB (virtual 1.089 GB)
docker latest 30557a29d5ab 20 hours ago 30.53 MB (virtual 1.089 GB)
<none> <none> 0124422dd9f9 20 hours ago 30.53 MB (virtual 1.089 GB)
<none> <none> 18ad6fad3402 22 hours ago 23.68 MB (virtual 1.082 GB)
<none> <none> f9f1e26352f0 23 hours ago 30.46 MB (virtual 1.089 GB)
tryout latest 2629d1fa0b81 23 hours ago 16.4 kB (virtual 131.5 MB)
<none> <none> 5ed6274db6ce 24 hours ago 30.44 MB (virtual 1.089 GB)
Listing the full length image IDs
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: bash
$ sudo docker images -notrunc | head
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> 77af4d6b9913e693e8d0b4b294fa62ade6054e6b2f1ffb617ac955dd63fb0182 19 hours ago 30.53 MB (virtual 1.089 GB)
committest latest b6fa739cedf5ea12a620a439402b6004d057da800f91c7524b5086a5e4749c9f 19 hours ago 30.53 MB (virtual 1.089 GB)
<none> <none> 78a85c484f71509adeaace20e72e941f6bdd2b25b4c75da8693efd9f61a37921 19 hours ago 30.53 MB (virtual 1.089 GB)
docker latest 30557a29d5abc51e5f1d5b472e79b7e296f595abcf19fe6b9199dbbc809c6ff4 20 hours ago 30.53 MB (virtual 1.089 GB)
<none> <none> 0124422dd9f9cf7ef15c0617cda3931ee68346455441d66ab8bdc5b05e9fdce5 20 hours ago 30.53 MB (virtual 1.089 GB)
<none> <none> 18ad6fad340262ac2a636efd98a6d1f0ea775ae3d45240d3418466495a19a81b 22 hours ago 23.68 MB (virtual 1.082 GB)
<none> <none> f9f1e26352f0a3ba6a0ff68167559f64f3e21ff7ada60366e2d44a04befd1d3a 23 hours ago 30.46 MB (virtual 1.089 GB)
tryout latest 2629d1fa0b81b222fca63371ca16cbf6a0772d07759ff80e8d1369b926940074 23 hours ago 16.4 kB (virtual 131.5 MB)
<none> <none> 5ed6274db6ceb2397844896966ea239290555e74ef307030ebb01ff91b1914df 24 hours ago 30.44 MB (virtual 1.089 GB)
Displaying images visually
~~~~~~~~~~~~~~~~~~~~~~~~~~
::
.. code-block:: bash
sudo docker images -viz | dot -Tpng -o docker.png
$ sudo docker images -viz | dot -Tpng -o docker.png
.. image:: docker_images.gif
:alt: Example inheritance graph of Docker images.
@@ -412,9 +484,9 @@ Displaying images visually
Displaying image hierarchy
~~~~~~~~~~~~~~~~~~~~~~~~~~
::
.. code-block:: bash
sudo docker images -tree
$ sudo docker images -tree
|─8dbd9e392a96 Size: 131.5 MB (virtual 131.5 MB) Tags: ubuntu:12.04,ubuntu:latest,ubuntu:precise
└─27cf78414709 Size: 180.1 MB (virtual 180.1 MB)
@@ -559,6 +631,18 @@ Known Issues (kill)
* :issue:`197` indicates that ``docker kill`` may leave directories
behind and make it difficult to remove the container.
.. _cli_load:
``load``
--------
::
Usage: docker load < repository.tar
Loads a tarred repository from the standard input stream.
Restores both images and tags.
.. _cli_login:
``login``
@@ -852,6 +936,17 @@ Known Issues (run -volumes-from)
could indicate a permissions problem with AppArmor. Please see the
issue for a workaround.
.. _cli_save:
``save``
::
Usage: docker save image > repository.tar
Streams a tarred repository to the standard output stream.
Contains all parent layers, and all tags + versions.
.. _cli_search:
``search``
+4 -6
View File
@@ -22,12 +22,10 @@ Amazon QuickStart
1. **Choose an image:**
* Open http://cloud-images.ubuntu.com/locator/ec2/
* Enter ``amd64 precise`` in the search field (it will search as you
type)
* Pick an image by clicking on the image name. *An EBS-enabled
image will let you use a t1.micro instance.* Clicking on the image
name will take you to your AWS Console.
* Launch the `Create Instance Wizard` <https://console.aws.amazon.com/ec2/v2/home?#LaunchInstanceWizard:> menu on your AWS Console
* Select "Community AMIs" option and serch for ``amd64 precise`` (click enter to search)
* If you choose a EBS enabled AMI you will be able to launch a `t1.micro` instance (more info on `pricing` <http://aws.amazon.com/en/ec2/pricing/> )
* When you click select you'll be taken to the instance setup, and you're one click away from having your Ubuntu VM up and running.
2. **Tell CloudInit to install Docker:**
+42 -10
View File
@@ -15,27 +15,39 @@ commit them along the way, giving you a final image.
.. contents:: Table of Contents
.. _dockerfile_usage:
1. Usage
========
To build an image from a source repository, create a description file
called ``Dockerfile`` at the root of your repository. This file will
describe the steps to assemble the image.
To :ref:`build <cli_build>` an image from a source repository, create
a description file called ``Dockerfile`` at the root of your
repository. This file will describe the steps to assemble the image.
Then call ``docker build`` with the path of your source repository as
argument:
argument (for example, ``.``):
``sudo docker build .``
The path to the source repository defines where to find the *context*
of the build. The build is run by the Docker daemon, not by the CLI,
so the whole context must be transferred to the daemon. The Docker CLI
reports "Uploading context" when the context is sent to the daemon.
You can specify a repository and tag at which to save the new image if the
build succeeds:
``sudo docker build -t shykes/myapp .``
Docker will run your steps one-by-one, committing the result if necessary,
before finally outputting the ID of your new image.
The Docker daemon will run your steps one-by-one, committing the
result if necessary, before finally outputting the ID of your new
image. The Docker daemon will automatically clean up the context you
sent.
When you're done with your build, you're ready to look into :ref:`image_push`.
When you're done with your build, you're ready to look into
:ref:`image_push`.
.. _dockerfile_format:
2. Format
=========
@@ -63,12 +75,16 @@ allows statements like:
# Comment
RUN echo 'we are running some # of cool things'
.. _dockerfile_instructions:
3. Instructions
===============
Here is the set of instructions you can use in a ``Dockerfile`` for
building images.
.. _dockerfile_from:
3.1 FROM
--------
@@ -94,6 +110,8 @@ output by the commit before each new ``FROM`` command.
If no ``tag`` is given to the ``FROM`` instruction, ``latest`` is
assumed. If the used tag does not exist, an error will be returned.
.. _dockerfile_maintainer:
3.2 MAINTAINER
--------------
@@ -102,6 +120,8 @@ assumed. If the used tag does not exist, an error will be returned.
The ``MAINTAINER`` instruction allows you to set the *Author* field of
the generated images.
.. _dockerfile_run:
3.3 RUN
-------
@@ -124,7 +144,7 @@ Known Issues (RUN)
``rm`` a file, for example. The issue describes a workaround.
* :issue:`2424` Locale will not be set automatically.
.. _dockerfile_cmd:
3.4 CMD
-------
@@ -169,7 +189,7 @@ array:
If you would like your container to run the same executable every
time, then you should consider using ``ENTRYPOINT`` in combination
with ``CMD``. See :ref:`entrypoint_def`.
with ``CMD``. See :ref:`dockerfile_entrypoint`.
If the user specifies arguments to ``docker run`` then they will
override the default specified in CMD.
@@ -179,6 +199,8 @@ override the default specified in CMD.
command and commits the result; ``CMD`` does not execute anything at
build time, but specifies the intended command for the image.
.. _dockerfile_expose:
3.5 EXPOSE
----------
@@ -189,6 +211,8 @@ functionally equivalent to running ``docker commit -run '{"PortSpecs":
["<port>", "<port2>"]}'`` outside the builder. Refer to
:ref:`port_redirection` for detailed information.
.. _dockerfile_env:
3.6 ENV
-------
@@ -203,6 +227,8 @@ with ``<key>=<value>``
The environment variables will persist when a container is run
from the resulting image.
.. _dockerfile_add:
3.7 ADD
-------
@@ -263,7 +289,7 @@ The copy obeys the following rules:
* If ``<dest>`` doesn't exist, it is created along with all missing
directories in its path.
.. _entrypoint_def:
.. _dockerfile_entrypoint:
3.8 ENTRYPOINT
--------------
@@ -312,6 +338,7 @@ this optional but default, you could use a CMD:
CMD ["-l", "-"]
ENTRYPOINT ["/usr/bin/wc"]
.. _dockerfile_volume:
3.9 VOLUME
----------
@@ -322,6 +349,8 @@ The ``VOLUME`` instruction will create a mount point with the specified name and
as holding externally mounted volumes from native host or other containers. For more information/examples
and mounting instructions via docker client, refer to :ref:`volume_def` documentation.
.. _dockerfile_user:
3.10 USER
---------
@@ -330,6 +359,8 @@ and mounting instructions via docker client, refer to :ref:`volume_def` document
The ``USER`` instruction sets the username or UID to use when running
the image.
.. _dockerfile_workdir:
3.11 WORKDIR
------------
@@ -338,6 +369,7 @@ the image.
The ``WORKDIR`` instruction sets the working directory in which
the command given by ``CMD`` is executed.
.. _dockerfile_examples:
4. Dockerfile Examples
======================
+55
View File
@@ -0,0 +1,55 @@
package engine
import (
"testing"
)
func TestRegister(t *testing.T) {
if err := Register("dummy1", nil); err != nil {
t.Fatal(err)
}
if err := Register("dummy1", nil); err == nil {
t.Fatalf("Expecting error, got none")
}
eng := newTestEngine(t)
//Should fail because globan handlers are copied
//at the engine creation
if err := eng.Register("dummy1", nil); err == nil {
t.Fatalf("Expecting error, got none")
}
if err := eng.Register("dummy2", nil); err != nil {
t.Fatal(err)
}
if err := eng.Register("dummy2", nil); err == nil {
t.Fatalf("Expecting error, got none")
}
}
func TestJob(t *testing.T) {
eng := newTestEngine(t)
job1 := eng.Job("dummy1", "--level=awesome")
if job1.handler != nil {
t.Fatalf("job1.handler should be empty")
}
h := func(j *Job) string {
return j.Name
}
eng.Register("dummy2", h)
job2 := eng.Job("dummy2", "--level=awesome")
if job2.handler == nil {
t.Fatalf("job2.handler shouldn't be nil")
}
if job2.handler(job2) != job2.Name {
t.Fatalf("handler dummy2 was not found in job2")
}
}
+94
View File
@@ -23,7 +23,101 @@ func TestSetenv(t *testing.T) {
if val := job.Getenv("foo"); val != "bar" {
t.Fatalf("Getenv returns incorrect value: %s", val)
}
job.Setenv("bar", "")
if val := job.Getenv("bar"); val != "" {
t.Fatalf("Getenv returns incorrect value: %s", val)
}
if val := job.Getenv("nonexistent"); val != "" {
t.Fatalf("Getenv returns incorrect value: %s", val)
}
}
func TestSetenvBool(t *testing.T) {
job := mkJob(t, "dummy")
job.SetenvBool("foo", true)
if val := job.GetenvBool("foo"); !val {
t.Fatalf("GetenvBool returns incorrect value: %b", val)
}
job.SetenvBool("bar", false)
if val := job.GetenvBool("bar"); val {
t.Fatalf("GetenvBool returns incorrect value: %b", val)
}
if val := job.GetenvBool("nonexistent"); val {
t.Fatalf("GetenvBool returns incorrect value: %b", val)
}
}
func TestSetenvInt(t *testing.T) {
job := mkJob(t, "dummy")
job.SetenvInt("foo", -42)
if val := job.GetenvInt("foo"); val != -42 {
t.Fatalf("GetenvInt returns incorrect value: %d", val)
}
job.SetenvInt("bar", 42)
if val := job.GetenvInt("bar"); val != 42 {
t.Fatalf("GetenvInt returns incorrect value: %d", val)
}
if val := job.GetenvInt("nonexistent"); val != -1 {
t.Fatalf("GetenvInt returns incorrect value: %d", val)
}
}
func TestSetenvList(t *testing.T) {
job := mkJob(t, "dummy")
job.SetenvList("foo", []string{"bar"})
if val := job.GetenvList("foo"); len(val) != 1 || val[0] != "bar" {
t.Fatalf("GetenvList returns incorrect value: %v", val)
}
job.SetenvList("bar", nil)
if val := job.GetenvList("bar"); val != nil {
t.Fatalf("GetenvList returns incorrect value: %v", val)
}
if val := job.GetenvList("nonexistent"); val != nil {
t.Fatalf("GetenvList returns incorrect value: %v", val)
}
}
func TestImportEnv(t *testing.T) {
type dummy struct {
DummyInt int
DummyStringArray []string
}
job := mkJob(t, "dummy")
if err := job.ImportEnv(&dummy{42, []string{"foo", "bar"}}); err != nil {
t.Fatal(err)
}
dmy := dummy{}
if err := job.ExportEnv(&dmy); err != nil {
t.Fatal(err)
}
if dmy.DummyInt != 42 {
t.Fatalf("Expected 42, got %d", dmy.DummyInt)
}
if len(dmy.DummyStringArray) != 2 || dmy.DummyStringArray[0] != "foo" || dmy.DummyStringArray[1] != "bar" {
t.Fatalf("Expected {foo, bar}, got %v", dmy.DummyStringArray)
}
}
func TestEnviron(t *testing.T) {
job := mkJob(t, "dummy")
job.Setenv("foo", "bar")
val, exists := job.Environ()["foo"]
if !exists {
t.Fatalf("foo not found in the environ")
}
if val != "bar" {
t.Fatalf("bar not found in the environ")
}
}
@@ -11,10 +11,6 @@ import (
var globalTestID string
func init() {
Register("dummy", func(job *Job) string { return "" })
}
func newTestEngine(t *testing.T) *Engine {
// Use the caller function name as a prefix.
// This helps trace temp directories back to their test.
+5 -1
View File
@@ -205,8 +205,12 @@ func (job *Job) SetenvInt(key string, value int64) {
job.Setenv(key, fmt.Sprintf("%d", value))
}
// Returns nil if key not found
func (job *Job) GetenvList(key string) []string {
sval := job.Getenv(key)
if sval == "" {
return nil
}
l := make([]string, 0, 1)
if err := json.Unmarshal([]byte(sval), &l); err != nil {
l = append(l, sval)
@@ -234,7 +238,7 @@ func (job *Job) Setenv(key, value string) {
// DecodeEnv decodes `src` as a json dictionary, and adds
// each decoded key-value pair to the environment.
//
// If `text` cannot be decoded as a json dictionary, an error
// If `src` cannot be decoded as a json dictionary, an error
// is returned.
func (job *Job) DecodeEnv(src io.Reader) error {
m := make(map[string]interface{})
+1 -1
View File
@@ -1,4 +1,4 @@
package graphdb
package gograph
import (
"database/sql"
@@ -1,4 +1,4 @@
package graphdb
package gograph
import (
_ "code.google.com/p/gosqlite/sqlite3"
+1 -1
View File
@@ -1,4 +1,4 @@
package graphdb
package gograph
import "sort"
@@ -1,4 +1,4 @@
package graphdb
package gograph
import (
"testing"
+1 -1
View File
@@ -1,4 +1,4 @@
package graphdb
package gograph
import (
"path"
+41 -50
View File
@@ -3,7 +3,6 @@ package docker
import (
"fmt"
"github.com/dotcloud/docker/archive"
"github.com/dotcloud/docker/graphdriver"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
@@ -11,7 +10,6 @@ import (
"path"
"path/filepath"
"strings"
"syscall"
"time"
)
@@ -19,12 +17,11 @@ import (
type Graph struct {
Root string
idIndex *utils.TruncIndex
driver graphdriver.Driver
}
// NewGraph instantiates a new graph at the given root path in the filesystem.
// `root` will be created if it doesn't exist.
func NewGraph(root string, driver graphdriver.Driver) (*Graph, error) {
func NewGraph(root string) (*Graph, error) {
abspath, err := filepath.Abs(root)
if err != nil {
return nil, err
@@ -33,11 +30,9 @@ func NewGraph(root string, driver graphdriver.Driver) (*Graph, error) {
if err := os.MkdirAll(root, 0700); err != nil && !os.IsExist(err) {
return nil, err
}
graph := &Graph{
Root: abspath,
idIndex: utils.NewTruncIndex(),
driver: driver,
}
if err := graph.restore(); err != nil {
return nil, err
@@ -83,22 +78,16 @@ func (graph *Graph) Get(name string) (*Image, error) {
if err != nil {
return nil, err
}
// Check that the filesystem layer exists
rootfs, err := graph.driver.Get(img.ID)
if err != nil {
return nil, fmt.Errorf("Driver %s failed to get image rootfs %s: %s", graph.driver, img.ID, err)
}
if img.ID != id {
return nil, fmt.Errorf("Image stored at '%s' has wrong id '%s'", id, img.ID)
}
img.graph = graph
if img.Size == 0 {
size, err := utils.TreeSize(rootfs)
root, err := img.root()
if err != nil {
return nil, fmt.Errorf("Error computing size of rootfs %s: %s", img.ID, err)
return nil, err
}
img.Size = size
if err := img.SaveSize(graph.imageRoot(id)); err != nil {
if err := StoreSize(img, root); err != nil {
return nil, err
}
}
@@ -142,24 +131,14 @@ func (graph *Graph) Register(jsonData []byte, layerData archive.Archive, img *Im
if err != nil {
return fmt.Errorf("Mktemp failed: %s", err)
}
// Create root filesystem in the driver
if err := graph.driver.Create(img.ID, img.Parent); err != nil {
return fmt.Errorf("Driver %s failed to create image rootfs %s: %s", graph.driver, img.ID, err)
}
// Mount the root filesystem so we can apply the diff/layer
rootfs, err := graph.driver.Get(img.ID)
if err != nil {
return fmt.Errorf("Driver %s failed to get image rootfs %s: %s", graph.driver, img.ID, err)
}
img.graph = graph
if err := StoreImage(img, jsonData, layerData, tmp, rootfs); err != nil {
if err := StoreImage(img, jsonData, layerData, tmp); err != nil {
return err
}
// Commit
if err := os.Rename(tmp, graph.imageRoot(img.ID)); err != nil {
return err
}
img.graph = graph
graph.idIndex.Add(img.ID)
return nil
}
@@ -173,7 +152,7 @@ func (graph *Graph) TempLayerArchive(id string, compression archive.Compression,
if err != nil {
return nil, err
}
tmp, err := graph.Mktemp("")
tmp, err := graph.tmp()
if err != nil {
return nil, err
}
@@ -181,25 +160,42 @@ func (graph *Graph) TempLayerArchive(id string, compression archive.Compression,
if err != nil {
return nil, err
}
return archive.NewTempArchive(utils.ProgressReader(ioutil.NopCloser(a), 0, output, sf.FormatProgress("", "Buffering to disk", "%v/%v (%v)"), sf, true), tmp)
return archive.NewTempArchive(utils.ProgressReader(ioutil.NopCloser(a), 0, output, sf.FormatProgress("", "Buffering to disk", "%v/%v (%v)"), sf, true), tmp.Root)
}
// Mktemp creates a temporary sub-directory inside the graph's filesystem.
func (graph *Graph) Mktemp(id string) (string, error) {
dir := path.Join(graph.Root, "_tmp", GenerateID())
if err := os.MkdirAll(dir, 0700); err != nil {
return "", err
if id == "" {
id = GenerateID()
}
return dir, nil
tmp, err := graph.tmp()
if err != nil {
return "", fmt.Errorf("Couldn't create temp: %s", err)
}
if tmp.Exists(id) {
return "", fmt.Errorf("Image %s already exists", id)
}
return tmp.imageRoot(id), nil
}
// setupInitLayer populates a directory with mountpoints suitable
// getDockerInitLayer returns the path of a layer containing a mountpoint suitable
// for bind-mounting dockerinit into the container. The mountpoint is simply an
// empty file at /.dockerinit
//
// This extra layer is used by all containers as the top-most ro layer. It protects
// the container from unwanted side-effects on the rw layer.
func setupInitLayer(initLayer string) error {
func (graph *Graph) getDockerInitLayer() (string, error) {
tmp, err := graph.tmp()
if err != nil {
return "", err
}
initLayer := tmp.imageRoot("_dockerinit")
if err := os.Mkdir(initLayer, 0755); err != nil && !os.IsExist(err) {
// If directory already existed, keep going.
// For all other errors, abort.
return "", err
}
for pth, typ := range map[string]string{
"/dev/pts": "dir",
"/dev/shm": "dir",
@@ -213,38 +209,36 @@ func setupInitLayer(initLayer string) error {
// "var/run": "dir",
// "var/lock": "dir",
} {
parts := strings.Split(pth, "/")
prev := "/"
for _, p := range parts[1:] {
prev = path.Join(prev, p)
syscall.Unlink(path.Join(initLayer, prev))
}
if _, err := os.Stat(path.Join(initLayer, pth)); err != nil {
if os.IsNotExist(err) {
switch typ {
case "dir":
if err := os.MkdirAll(path.Join(initLayer, pth), 0755); err != nil {
return err
return "", err
}
case "file":
if err := os.MkdirAll(path.Join(initLayer, path.Dir(pth)), 0755); err != nil {
return err
return "", err
}
f, err := os.OpenFile(path.Join(initLayer, pth), os.O_CREATE, 0755)
if err != nil {
return err
return "", err
}
f.Close()
}
} else {
return err
return "", err
}
}
}
// Layer is ready to use, if it wasn't before.
return nil
return initLayer, nil
}
func (graph *Graph) tmp() (*Graph, error) {
// Changed to _tmp from :tmp:, because it messed with ":" separators in aufs branch syntax...
return NewGraph(path.Join(graph.Root, "_tmp"))
}
// Check if given error is "not empty".
@@ -276,9 +270,6 @@ func (graph *Graph) Delete(name string) error {
if err != nil {
return err
}
// Remove rootfs data from the driver
graph.driver.Remove(id)
// Remove the trashed image directory
return os.RemoveAll(tmp)
}
+1 -6
View File
@@ -5,7 +5,6 @@ import (
"bytes"
"errors"
"github.com/dotcloud/docker/archive"
"github.com/dotcloud/docker/graphdriver"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
@@ -260,11 +259,7 @@ func tempGraph(t *testing.T) *Graph {
if err != nil {
t.Fatal(err)
}
backend, err := graphdriver.New(tmp)
if err != nil {
t.Fatal(err)
}
graph, err := NewGraph(tmp, backend)
graph, err := NewGraph(tmp)
if err != nil {
t.Fatal(err)
}
-332
View File
@@ -1,332 +0,0 @@
/*
aufs driver directory structure
.
├── layers // Metadata of layers
│   ├── 1
│   ├── 2
│   └── 3
├── diffs // Content of the layer
│   ├── 1 // Contains layers that need to be mounted for the id
│   ├── 2
│   └── 3
└── mnt // Mount points for the rw layers to be mounted
├── 1
├── 2
└── 3
*/
package aufs
import (
"bufio"
"fmt"
"github.com/dotcloud/docker/archive"
"github.com/dotcloud/docker/graphdriver"
"github.com/dotcloud/docker/utils"
"log"
"os"
"os/exec"
"path"
"strings"
)
func init() {
graphdriver.Register("aufs", Init)
}
type Driver struct {
root string
}
// New returns a new AUFS driver.
// An error is returned if AUFS is not supported.
func Init(root string) (graphdriver.Driver, error) {
// Try to load the aufs kernel module
if err := supportsAufs(); err != nil {
return nil, err
}
paths := []string{
"mnt",
"diff",
"layers",
}
// Create the root aufs driver dir and return
// if it already exists
// If not populate the dir structure
if err := os.MkdirAll(root, 0755); err != nil {
if os.IsExist(err) {
return &Driver{root}, nil
}
return nil, err
}
for _, p := range paths {
if err := os.MkdirAll(path.Join(root, p), 0755); err != nil {
return nil, err
}
}
return &Driver{root}, nil
}
// Return a nil error if the kernel supports aufs
// We cannot modprobe because inside dind modprobe fails
// to run
func supportsAufs() error {
// We can try to modprobe aufs first before looking at
// proc/filesystems for when aufs is supported
exec.Command("modprobe", "aufs").Run()
f, err := os.Open("/proc/filesystems")
if err != nil {
return err
}
defer f.Close()
s := bufio.NewScanner(f)
for s.Scan() {
if strings.Contains(s.Text(), "aufs") {
return nil
}
}
return fmt.Errorf("AUFS was not found in /proc/filesystems")
}
func (a Driver) rootPath() string {
return a.root
}
func (Driver) String() string {
return "aufs"
}
func (Driver) Status() [][2]string {
return nil
}
// Exists returns true if the given id is registered with
// this driver
func (a Driver) Exists(id string) bool {
if _, err := os.Lstat(path.Join(a.rootPath(), "layers", id)); err != nil {
return false
}
return true
}
// Three folders are created for each id
// mnt, layers, and diff
func (a *Driver) Create(id, parent string) error {
if err := a.createDirsFor(id); err != nil {
return err
}
// Write the layers metadata
f, err := os.Create(path.Join(a.rootPath(), "layers", id))
if err != nil {
return err
}
defer f.Close()
if parent != "" {
ids, err := getParentIds(a.rootPath(), parent)
if err != nil {
return err
}
if _, err := fmt.Fprintln(f, parent); err != nil {
return err
}
for _, i := range ids {
if _, err := fmt.Fprintln(f, i); err != nil {
return err
}
}
}
return nil
}
func (a *Driver) createDirsFor(id string) error {
paths := []string{
"mnt",
"diff",
}
for _, p := range paths {
if err := os.MkdirAll(path.Join(a.rootPath(), p, id), 0755); err != nil {
return err
}
}
return nil
}
// Unmount and remove the dir information
func (a *Driver) Remove(id string) error {
// Make sure the dir is umounted first
if err := a.unmount(id); err != nil {
return err
}
tmpDirs := []string{
"mnt",
"diff",
}
// Remove the dirs atomically
for _, p := range tmpDirs {
// We need to use a temp dir in the same dir as the driver so Rename
// does not fall back to the slow copy if /tmp and the driver dir
// are on different devices
tmp := path.Join(a.rootPath(), "tmp", p, id)
if err := os.MkdirAll(tmp, 0755); err != nil {
return err
}
realPath := path.Join(a.rootPath(), p, id)
if err := os.Rename(realPath, tmp); err != nil && !os.IsNotExist(err) {
return err
}
defer os.RemoveAll(tmp)
}
// Remove the layers file for the id
if err := os.Remove(path.Join(a.rootPath(), "layers", id)); err != nil && !os.IsNotExist(err) {
return err
}
return nil
}
// Return the rootfs path for the id
// This will mount the dir at it's given path
func (a *Driver) Get(id string) (string, error) {
ids, err := getParentIds(a.rootPath(), id)
if err != nil {
if !os.IsNotExist(err) {
return "", err
}
ids = []string{}
}
// If a dir does not have a parent ( no layers )do not try to mount
// just return the diff path to the data
out := path.Join(a.rootPath(), "diff", id)
if len(ids) > 0 {
out = path.Join(a.rootPath(), "mnt", id)
if err := a.mount(id); err != nil {
return "", err
}
}
return out, nil
}
// Returns an archive of the contents for the id
func (a *Driver) Diff(id string) (archive.Archive, error) {
return archive.TarFilter(path.Join(a.rootPath(), "diff", id), &archive.TarOptions{
Recursive: true,
Compression: archive.Uncompressed,
})
}
func (a *Driver) ApplyDiff(id string, diff archive.Archive) error {
return archive.Untar(diff, path.Join(a.rootPath(), "diff", id), nil)
}
// Returns the size of the contents for the id
func (a *Driver) DiffSize(id string) (int64, error) {
return utils.TreeSize(path.Join(a.rootPath(), "diff", id))
}
func (a *Driver) Changes(id string) ([]archive.Change, error) {
layers, err := a.getParentLayerPaths(id)
if err != nil {
return nil, err
}
return archive.Changes(layers, path.Join(a.rootPath(), "diff", id))
}
func (a *Driver) getParentLayerPaths(id string) ([]string, error) {
parentIds, err := getParentIds(a.rootPath(), id)
if err != nil {
return nil, err
}
if len(parentIds) == 0 {
return nil, fmt.Errorf("Dir %s does not have any parent layers", id)
}
layers := make([]string, len(parentIds))
// Get the diff paths for all the parent ids
for i, p := range parentIds {
layers[i] = path.Join(a.rootPath(), "diff", p)
}
return layers, nil
}
func (a *Driver) mount(id string) error {
// If the id is mounted or we get an error return
if mounted, err := a.mounted(id); err != nil || mounted {
return err
}
var (
target = path.Join(a.rootPath(), "mnt", id)
rw = path.Join(a.rootPath(), "diff", id)
)
layers, err := a.getParentLayerPaths(id)
if err != nil {
return err
}
if err := a.aufsMount(layers, rw, target); err != nil {
return err
}
return nil
}
func (a *Driver) unmount(id string) error {
if mounted, err := a.mounted(id); err != nil || !mounted {
return err
}
target := path.Join(a.rootPath(), "mnt", id)
return Unmount(target)
}
func (a *Driver) mounted(id string) (bool, error) {
target := path.Join(a.rootPath(), "mnt", id)
return Mounted(target)
}
// During cleanup aufs needs to unmount all mountpoints
func (a *Driver) Cleanup() error {
ids, err := loadIds(path.Join(a.rootPath(), "layers"))
if err != nil {
return err
}
for _, id := range ids {
if err := a.unmount(id); err != nil {
utils.Errorf("Unmounting %s: %s", utils.TruncateID(id), err)
}
}
return nil
}
func (a *Driver) aufsMount(ro []string, rw, target string) error {
rwBranch := fmt.Sprintf("%v=rw", rw)
roBranches := ""
for _, layer := range ro {
roBranches += fmt.Sprintf("%v=ro+wh:", layer)
}
branches := fmt.Sprintf("br:%v:%v,xino=/dev/shm/aufs.xino", rwBranch, roBranches)
//if error, try to load aufs kernel module
if err := mount("none", target, "aufs", 0, branches); err != nil {
log.Printf("Kernel does not support AUFS, trying to load the AUFS module with modprobe...")
if err := exec.Command("modprobe", "aufs").Run(); err != nil {
return fmt.Errorf("Unable to load the AUFS module")
}
log.Printf("...module loaded.")
if err := mount("none", target, "aufs", 0, branches); err != nil {
return fmt.Errorf("Unable to mount using aufs %s", err)
}
}
return nil
}
-517
View File
@@ -1,517 +0,0 @@
package aufs
import (
"github.com/dotcloud/docker/archive"
"os"
"path"
"testing"
)
var (
tmp = path.Join(os.TempDir(), "aufs-tests", "aufs")
)
func newDriver(t *testing.T) *Driver {
if err := os.MkdirAll(tmp, 0755); err != nil {
t.Fatal(err)
}
d, err := Init(tmp)
if err != nil {
t.Fatal(err)
}
return d.(*Driver)
}
func TestNewDriver(t *testing.T) {
if err := os.MkdirAll(tmp, 0755); err != nil {
t.Fatal(err)
}
d, err := Init(tmp)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
if d == nil {
t.Fatalf("Driver should not be nil")
}
}
func TestAufsString(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
if d.String() != "aufs" {
t.Fatalf("Expected aufs got %s", d.String())
}
}
func TestCreateDirStructure(t *testing.T) {
newDriver(t)
defer os.RemoveAll(tmp)
paths := []string{
"mnt",
"layers",
"diff",
}
for _, p := range paths {
if _, err := os.Stat(path.Join(tmp, p)); err != nil {
t.Fatal(err)
}
}
}
// We should be able to create two drivers with the same dir structure
func TestNewDriverFromExistingDir(t *testing.T) {
if err := os.MkdirAll(tmp, 0755); err != nil {
t.Fatal(err)
}
if _, err := Init(tmp); err != nil {
t.Fatal(err)
}
if _, err := Init(tmp); err != nil {
t.Fatal(err)
}
os.RemoveAll(tmp)
}
func TestCreateNewDir(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
}
}
func TestCreateNewDirStructure(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
}
paths := []string{
"mnt",
"diff",
"layers",
}
for _, p := range paths {
if _, err := os.Stat(path.Join(tmp, p, "1")); err != nil {
t.Fatal(err)
}
}
}
func TestRemoveImage(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
}
if err := d.Remove("1"); err != nil {
t.Fatal(err)
}
paths := []string{
"mnt",
"diff",
"layers",
}
for _, p := range paths {
if _, err := os.Stat(path.Join(tmp, p, "1")); err == nil {
t.Fatalf("Error should not be nil because dirs with id 1 should be delted: %s", p)
}
}
}
func TestGetWithoutParent(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
}
diffPath, err := d.Get("1")
if err != nil {
t.Fatal(err)
}
expected := path.Join(tmp, "diff", "1")
if diffPath != expected {
t.Fatalf("Expected path %s got %s", expected, diffPath)
}
}
func TestCleanupWithNoDirs(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
if err := d.Cleanup(); err != nil {
t.Fatal(err)
}
}
func TestCleanupWithDir(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
}
if err := d.Cleanup(); err != nil {
t.Fatal(err)
}
}
func TestMountedFalseResponse(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
}
response, err := d.mounted("1")
if err != nil {
t.Fatal(err)
}
if response != false {
t.Fatalf("Response if dir id 1 is mounted should be false")
}
}
func TestMountedTrueReponse(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
defer d.Cleanup()
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
}
if err := d.Create("2", "1"); err != nil {
t.Fatal(err)
}
_, err := d.Get("2")
if err != nil {
t.Fatal(err)
}
response, err := d.mounted("2")
if err != nil {
t.Fatal(err)
}
if response != true {
t.Fatalf("Response if dir id 2 is mounted should be true")
}
}
func TestMountWithParent(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
}
if err := d.Create("2", "1"); err != nil {
t.Fatal(err)
}
defer func() {
if err := d.Cleanup(); err != nil {
t.Fatal(err)
}
}()
mntPath, err := d.Get("2")
if err != nil {
t.Fatal(err)
}
if mntPath == "" {
t.Fatal("mntPath should not be empty string")
}
expected := path.Join(tmp, "mnt", "2")
if mntPath != expected {
t.Fatalf("Expected %s got %s", expected, mntPath)
}
}
func TestRemoveMountedDir(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
}
if err := d.Create("2", "1"); err != nil {
t.Fatal(err)
}
defer func() {
if err := d.Cleanup(); err != nil {
t.Fatal(err)
}
}()
mntPath, err := d.Get("2")
if err != nil {
t.Fatal(err)
}
if mntPath == "" {
t.Fatal("mntPath should not be empty string")
}
mounted, err := d.mounted("2")
if err != nil {
t.Fatal(err)
}
if !mounted {
t.Fatalf("Dir id 2 should be mounted")
}
if err := d.Remove("2"); err != nil {
t.Fatal(err)
}
}
func TestCreateWithInvalidParent(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
if err := d.Create("1", "docker"); err == nil {
t.Fatalf("Error should not be nil with parent does not exist")
}
}
func TestGetDiff(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
}
diffPath, err := d.Get("1")
if err != nil {
t.Fatal(err)
}
// Add a file to the diff path with a fixed size
size := int64(1024)
f, err := os.Create(path.Join(diffPath, "test_file"))
if err != nil {
t.Fatal(err)
}
if err := f.Truncate(size); err != nil {
t.Fatal(err)
}
f.Close()
a, err := d.Diff("1")
if err != nil {
t.Fatal(err)
}
if a == nil {
t.Fatalf("Archive should not be nil")
}
}
func TestChanges(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
}
if err := d.Create("2", "1"); err != nil {
t.Fatal(err)
}
defer func() {
if err := d.Cleanup(); err != nil {
t.Fatal(err)
}
}()
mntPoint, err := d.Get("2")
if err != nil {
t.Fatal(err)
}
// Create a file to save in the mountpoint
f, err := os.Create(path.Join(mntPoint, "test.txt"))
if err != nil {
t.Fatal(err)
}
if _, err := f.WriteString("testline"); err != nil {
t.Fatal(err)
}
if err := f.Close(); err != nil {
t.Fatal(err)
}
changes, err := d.Changes("2")
if err != nil {
t.Fatal(err)
}
if len(changes) != 1 {
t.Fatalf("Dir 2 should have one change from parent got %d", len(changes))
}
change := changes[0]
expectedPath := "/test.txt"
if change.Path != expectedPath {
t.Fatalf("Expected path %s got %s", expectedPath, change.Path)
}
if change.Kind != archive.ChangeAdd {
t.Fatalf("Change kind should be ChangeAdd got %s", change.Kind)
}
if err := d.Create("3", "2"); err != nil {
t.Fatal(err)
}
mntPoint, err = d.Get("3")
if err != nil {
t.Fatal(err)
}
// Create a file to save in the mountpoint
f, err = os.Create(path.Join(mntPoint, "test2.txt"))
if err != nil {
t.Fatal(err)
}
if _, err := f.WriteString("testline"); err != nil {
t.Fatal(err)
}
if err := f.Close(); err != nil {
t.Fatal(err)
}
changes, err = d.Changes("3")
if err != nil {
t.Fatal(err)
}
if len(changes) != 1 {
t.Fatalf("Dir 2 should have one change from parent got %d", len(changes))
}
change = changes[0]
expectedPath = "/test2.txt"
if change.Path != expectedPath {
t.Fatalf("Expected path %s got %s", expectedPath, change.Path)
}
if change.Kind != archive.ChangeAdd {
t.Fatalf("Change kind should be ChangeAdd got %s", change.Kind)
}
}
func TestDiffSize(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
}
diffPath, err := d.Get("1")
if err != nil {
t.Fatal(err)
}
// Add a file to the diff path with a fixed size
size := int64(1024)
f, err := os.Create(path.Join(diffPath, "test_file"))
if err != nil {
t.Fatal(err)
}
f.Truncate(size)
s, err := f.Stat()
if err != nil {
t.Fatal(err)
}
size = s.Size()
if err := f.Close(); err != nil {
t.Fatal(err)
}
diffSize, err := d.DiffSize("1")
if err != nil {
t.Fatal(err)
}
if diffSize != size {
t.Fatalf("Expected size to be %d got %d", size, diffSize)
}
}
func TestApplyDiff(t *testing.T) {
d := newDriver(t)
defer os.RemoveAll(tmp)
defer d.Cleanup()
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
}
diffPath, err := d.Get("1")
if err != nil {
t.Fatal(err)
}
// Add a file to the diff path with a fixed size
size := int64(1024)
f, err := os.Create(path.Join(diffPath, "test_file"))
if err != nil {
t.Fatal(err)
}
f.Truncate(size)
f.Close()
diff, err := d.Diff("1")
if err != nil {
t.Fatal(err)
}
if err := d.Create("2", ""); err != nil {
t.Fatal(err)
}
if err := d.Create("3", "2"); err != nil {
t.Fatal(err)
}
if err := d.ApplyDiff("3", diff); err != nil {
t.Fatal(err)
}
// Ensure that the file is in the mount point for id 3
mountPoint, err := d.Get("3")
if err != nil {
t.Fatal(err)
}
if _, err := os.Stat(path.Join(mountPoint, "test_file")); err != nil {
t.Fatal(err)
}
}
-46
View File
@@ -1,46 +0,0 @@
package aufs
import (
"bufio"
"io/ioutil"
"os"
"path"
)
// Return all the directories
func loadIds(root string) ([]string, error) {
dirs, err := ioutil.ReadDir(root)
if err != nil {
return nil, err
}
out := []string{}
for _, d := range dirs {
if !d.IsDir() {
out = append(out, d.Name())
}
}
return out, nil
}
// Read the layers file for the current id and return all the
// layers represented by new lines in the file
//
// If there are no lines in the file then the id has no parent
// and an empty slice is returned.
func getParentIds(root, id string) ([]string, error) {
f, err := os.Open(path.Join(root, "layers", id))
if err != nil {
return nil, err
}
defer f.Close()
out := []string{}
s := bufio.NewScanner(f)
for s.Scan() {
if t := s.Text(); t != "" {
out = append(out, s.Text())
}
}
return out, s.Err()
}
-183
View File
@@ -1,183 +0,0 @@
package aufs
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path"
)
type metadata struct {
ID string `json:"id"`
ParentID string `json:"parent,omitempty"`
Image string `json:"Image,omitempty"`
parent *metadata
}
func pathExists(pth string) bool {
if _, err := os.Stat(pth); err != nil {
return false
}
return true
}
// Migrate existing images and containers from docker < 0.7.x
//
// The format pre 0.7 is for docker to store the metadata and filesystem
// content in the same directory. For the migration to work we need to move Image layer
// data from /var/lib/docker/graph/<id>/layers to the diff of the registered id.
//
// Next we need to migrate the container's rw layer to diff of the driver. After the
// contents are migrated we need to register the image and container ids with the
// driver.
//
// For the migration we try to move the folder containing the layer files, if that
// fails because the data is currently mounted we will fallback to creating a
// symlink.
func (a *Driver) Migrate(pth string, setupInit func(p string) error) error {
if pathExists(path.Join(pth, "graph")) {
if err := a.migrateImages(path.Join(pth, "graph")); err != nil {
return err
}
return a.migrateContainers(path.Join(pth, "containers"), setupInit)
}
return nil
}
func (a *Driver) migrateContainers(pth string, setupInit func(p string) error) error {
fis, err := ioutil.ReadDir(pth)
if err != nil {
return err
}
for _, fi := range fis {
if id := fi.Name(); fi.IsDir() && pathExists(path.Join(pth, id, "rw")) {
if err := tryRelocate(path.Join(pth, id, "rw"), path.Join(a.rootPath(), "diff", id)); err != nil {
return err
}
if !a.Exists(id) {
metadata, err := loadMetadata(path.Join(pth, id, "config.json"))
if err != nil {
return err
}
initID := fmt.Sprintf("%s-init", id)
if err := a.Create(initID, metadata.Image); err != nil {
return err
}
initPath, err := a.Get(initID)
if err != nil {
return err
}
// setup init layer
if err := setupInit(initPath); err != nil {
return err
}
if err := a.Create(id, initID); err != nil {
return err
}
}
}
}
return nil
}
func (a *Driver) migrateImages(pth string) error {
fis, err := ioutil.ReadDir(pth)
if err != nil {
return err
}
var (
m = make(map[string]*metadata)
current *metadata
exists bool
)
for _, fi := range fis {
if id := fi.Name(); fi.IsDir() && pathExists(path.Join(pth, id, "layer")) {
if current, exists = m[id]; !exists {
current, err = loadMetadata(path.Join(pth, id, "json"))
if err != nil {
return err
}
m[id] = current
}
}
}
for _, v := range m {
v.parent = m[v.ParentID]
}
migrated := make(map[string]bool)
for _, v := range m {
if err := a.migrateImage(v, pth, migrated); err != nil {
return err
}
}
return nil
}
func (a *Driver) migrateImage(m *metadata, pth string, migrated map[string]bool) error {
if !migrated[m.ID] {
if m.parent != nil {
a.migrateImage(m.parent, pth, migrated)
}
if err := tryRelocate(path.Join(pth, m.ID, "layer"), path.Join(a.rootPath(), "diff", m.ID)); err != nil {
return err
}
if !a.Exists(m.ID) {
if err := a.Create(m.ID, m.ParentID); err != nil {
return err
}
}
migrated[m.ID] = true
}
return nil
}
// tryRelocate will try to rename the old path to the new pack and if
// the operation fails, it will fallback to a symlink
func tryRelocate(oldPath, newPath string) error {
s, err := os.Lstat(newPath)
if err != nil && !os.IsNotExist(err) {
return err
}
// If the destination is a symlink then we already tried to relocate once before
// and it failed so we delete it and try to remove
if s != nil && s.Mode()&os.ModeSymlink == os.ModeSymlink {
if err := os.RemoveAll(newPath); err != nil {
return err
}
}
if err := os.Rename(oldPath, newPath); err != nil {
if sErr := os.Symlink(oldPath, newPath); sErr != nil {
return fmt.Errorf("Unable to relocate %s to %s: Rename err %s Symlink err %s", oldPath, newPath, err, sErr)
}
}
return nil
}
func loadMetadata(pth string) (*metadata, error) {
f, err := os.Open(pth)
if err != nil {
return nil, err
}
defer f.Close()
var (
out = &metadata{}
dec = json.NewDecoder(f)
)
if err := dec.Decode(out); err != nil {
return nil, err
}
return out, nil
}
-954
View File
@@ -1,954 +0,0 @@
package devmapper
import (
"encoding/json"
"fmt"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"sync"
"syscall"
"time"
)
var (
DefaultDataLoopbackSize int64 = 100 * 1024 * 1024 * 1024
DefaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024
DefaultBaseFsSize uint64 = 10 * 1024 * 1024 * 1024
)
type DevInfo struct {
Hash string `json:"-"`
DeviceId int `json:"device_id"`
Size uint64 `json:"size"`
TransactionId uint64 `json:"transaction_id"`
Initialized bool `json:"initialized"`
devices *DeviceSet `json:"-"`
}
type MetaData struct {
Devices map[string]*DevInfo `json:devices`
}
type DeviceSet struct {
MetaData
sync.Mutex
root string
devicePrefix string
TransactionId uint64
NewTransactionId uint64
nextFreeDevice int
activeMounts map[string]int
}
type DiskUsage struct {
Used uint64
Total uint64
}
type Status struct {
PoolName string
DataLoopback string
MetadataLoopback string
Data DiskUsage
Metadata DiskUsage
SectorSize uint64
}
type DevStatus struct {
DeviceId int
Size uint64
TransactionId uint64
SizeInSectors uint64
MappedSectors uint64
HighestMappedSector uint64
}
func getDevName(name string) string {
return "/dev/mapper/" + name
}
func (info *DevInfo) Name() string {
hash := info.Hash
if hash == "" {
hash = "base"
}
return fmt.Sprintf("%s-%s", info.devices.devicePrefix, hash)
}
func (info *DevInfo) DevName() string {
return getDevName(info.Name())
}
func (devices *DeviceSet) loopbackDir() string {
return path.Join(devices.root, "devicemapper")
}
func (devices *DeviceSet) jsonFile() string {
return path.Join(devices.loopbackDir(), "json")
}
func (devices *DeviceSet) getPoolName() string {
return devices.devicePrefix + "-pool"
}
func (devices *DeviceSet) getPoolDevName() string {
return getDevName(devices.getPoolName())
}
func (devices *DeviceSet) hasImage(name string) bool {
dirname := devices.loopbackDir()
filename := path.Join(dirname, name)
_, err := os.Stat(filename)
return err == nil
}
// ensureImage creates a sparse file of <size> bytes at the path
// <root>/devicemapper/<name>.
// If the file already exists, it does nothing.
// Either way it returns the full path.
func (devices *DeviceSet) ensureImage(name string, size int64) (string, error) {
dirname := devices.loopbackDir()
filename := path.Join(dirname, name)
if err := os.MkdirAll(dirname, 0700); err != nil && !os.IsExist(err) {
return "", err
}
if _, err := os.Stat(filename); err != nil {
if !os.IsNotExist(err) {
return "", err
}
utils.Debugf("Creating loopback file %s for device-manage use", filename)
file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0600)
if err != nil {
return "", err
}
defer file.Close()
if err = file.Truncate(size); err != nil {
return "", err
}
}
return filename, nil
}
func (devices *DeviceSet) allocateDeviceId() int {
// TODO: Add smarter reuse of deleted devices
id := devices.nextFreeDevice
devices.nextFreeDevice = devices.nextFreeDevice + 1
return id
}
func (devices *DeviceSet) allocateTransactionId() uint64 {
devices.NewTransactionId = devices.NewTransactionId + 1
return devices.NewTransactionId
}
func (devices *DeviceSet) saveMetadata() error {
jsonData, err := json.Marshal(devices.MetaData)
if err != nil {
return fmt.Errorf("Error encoding metaadata to json: %s", err)
}
tmpFile, err := ioutil.TempFile(filepath.Dir(devices.jsonFile()), ".json")
if err != nil {
return fmt.Errorf("Error creating metadata file: %s", err)
}
n, err := tmpFile.Write(jsonData)
if err != nil {
return fmt.Errorf("Error writing metadata to %s: %s", tmpFile.Name(), err)
}
if n < len(jsonData) {
return io.ErrShortWrite
}
if err := tmpFile.Sync(); err != nil {
return fmt.Errorf("Error syncing metadata file %s: %s", tmpFile.Name(), err)
}
if err := tmpFile.Close(); err != nil {
return fmt.Errorf("Error closing metadata file %s: %s", tmpFile.Name(), err)
}
if err := os.Rename(tmpFile.Name(), devices.jsonFile()); err != nil {
return fmt.Errorf("Error committing metadata file", err)
}
if devices.NewTransactionId != devices.TransactionId {
if err = setTransactionId(devices.getPoolDevName(), devices.TransactionId, devices.NewTransactionId); err != nil {
return fmt.Errorf("Error setting devmapper transition ID: %s", err)
}
devices.TransactionId = devices.NewTransactionId
}
return nil
}
func (devices *DeviceSet) registerDevice(id int, hash string, size uint64) (*DevInfo, error) {
utils.Debugf("registerDevice(%v, %v)", id, hash)
info := &DevInfo{
Hash: hash,
DeviceId: id,
Size: size,
TransactionId: devices.allocateTransactionId(),
Initialized: false,
devices: devices,
}
devices.Devices[hash] = info
if err := devices.saveMetadata(); err != nil {
// Try to remove unused device
delete(devices.Devices, hash)
return nil, err
}
return info, nil
}
func (devices *DeviceSet) activateDeviceIfNeeded(hash string) error {
utils.Debugf("activateDeviceIfNeeded(%v)", hash)
info := devices.Devices[hash]
if info == nil {
return fmt.Errorf("Unknown device %s", hash)
}
if devinfo, _ := getInfo(info.Name()); devinfo != nil && devinfo.Exists != 0 {
return nil
}
return activateDevice(devices.getPoolDevName(), info.Name(), info.DeviceId, info.Size)
}
func (devices *DeviceSet) createFilesystem(info *DevInfo) error {
devname := info.DevName()
err := exec.Command("mkfs.ext4", "-E", "discard,lazy_itable_init=0,lazy_journal_init=0", devname).Run()
if err != nil {
err = exec.Command("mkfs.ext4", "-E", "discard,lazy_itable_init=0", devname).Run()
}
if err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
return nil
}
func (devices *DeviceSet) loadMetaData() error {
utils.Debugf("loadMetadata()")
defer utils.Debugf("loadMetadata END")
_, _, _, params, err := getStatus(devices.getPoolName())
if err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
if _, err := fmt.Sscanf(params, "%d", &devices.TransactionId); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
devices.NewTransactionId = devices.TransactionId
jsonData, err := ioutil.ReadFile(devices.jsonFile())
if err != nil && !os.IsNotExist(err) {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
devices.MetaData.Devices = make(map[string]*DevInfo)
if jsonData != nil {
if err := json.Unmarshal(jsonData, &devices.MetaData); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
}
for hash, d := range devices.Devices {
d.Hash = hash
d.devices = devices
if d.DeviceId >= devices.nextFreeDevice {
devices.nextFreeDevice = d.DeviceId + 1
}
// If the transaction id is larger than the actual one we lost the device due to some crash
if d.TransactionId > devices.TransactionId {
utils.Debugf("Removing lost device %s with id %d", hash, d.TransactionId)
delete(devices.Devices, hash)
}
}
return nil
}
func (devices *DeviceSet) setupBaseImage() error {
oldInfo := devices.Devices[""]
if oldInfo != nil && oldInfo.Initialized {
return nil
}
if oldInfo != nil && !oldInfo.Initialized {
utils.Debugf("Removing uninitialized base image")
if err := devices.removeDevice(""); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
}
utils.Debugf("Initializing base device-manager snapshot")
id := devices.allocateDeviceId()
// Create initial device
if err := createDevice(devices.getPoolDevName(), id); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
utils.Debugf("Registering base device (id %v) with FS size %v", id, DefaultBaseFsSize)
info, err := devices.registerDevice(id, "", DefaultBaseFsSize)
if err != nil {
_ = deleteDevice(devices.getPoolDevName(), id)
utils.Debugf("\n--->Err: %s\n", err)
return err
}
utils.Debugf("Creating filesystem on base device-manager snapshot")
if err = devices.activateDeviceIfNeeded(""); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
if err := devices.createFilesystem(info); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
info.Initialized = true
if err = devices.saveMetadata(); err != nil {
info.Initialized = false
utils.Debugf("\n--->Err: %s\n", err)
return err
}
return nil
}
func setCloseOnExec(name string) {
fileInfos, _ := ioutil.ReadDir("/proc/self/fd")
if fileInfos != nil {
for _, i := range fileInfos {
link, _ := os.Readlink(filepath.Join("/proc/self/fd", i.Name()))
if link == name {
fd, err := strconv.Atoi(i.Name())
if err == nil {
syscall.CloseOnExec(fd)
}
}
}
}
}
func (devices *DeviceSet) log(level int, file string, line int, dmError int, message string) {
if level >= 7 {
return // Ignore _LOG_DEBUG
}
utils.Debugf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message)
}
func major(device uint64) uint64 {
return (device >> 8) & 0xfff
}
func minor(device uint64) uint64 {
return (device & 0xff) | ((device >> 12) & 0xfff00)
}
func (devices *DeviceSet) ResizePool(size int64) error {
dirname := devices.loopbackDir()
datafilename := path.Join(dirname, "data")
metadatafilename := path.Join(dirname, "metadata")
datafile, err := os.OpenFile(datafilename, os.O_RDWR, 0)
if datafile == nil {
return err
}
defer datafile.Close()
fi, err := datafile.Stat()
if fi == nil {
return err
}
if fi.Size() > size {
return fmt.Errorf("Can't shrink file")
}
dataloopback := FindLoopDeviceFor(datafile)
if dataloopback == nil {
return fmt.Errorf("Unable to find loopback mount for: %s", datafilename)
}
defer dataloopback.Close()
metadatafile, err := os.OpenFile(metadatafilename, os.O_RDWR, 0)
if metadatafile == nil {
return err
}
defer metadatafile.Close()
metadataloopback := FindLoopDeviceFor(metadatafile)
if metadataloopback == nil {
return fmt.Errorf("Unable to find loopback mount for: %s", metadatafilename)
}
defer metadataloopback.Close()
// Grow loopback file
if err := datafile.Truncate(size); err != nil {
return fmt.Errorf("Unable to grow loopback file: %s", err)
}
// Reload size for loopback device
if err := LoopbackSetCapacity(dataloopback); err != nil {
return fmt.Errorf("Unable to update loopback capacity: %s", err)
}
// Suspend the pool
if err := suspendDevice(devices.getPoolName()); err != nil {
return fmt.Errorf("Unable to suspend pool: %s", err)
}
// Reload with the new block sizes
if err := reloadPool(devices.getPoolName(), dataloopback, metadataloopback); err != nil {
return fmt.Errorf("Unable to reload pool: %s", err)
}
// Resume the pool
if err := resumeDevice(devices.getPoolName()); err != nil {
return fmt.Errorf("Unable to resume pool: %s", err)
}
return nil
}
func (devices *DeviceSet) initDevmapper(doInit bool) error {
logInit(devices)
// Make sure the sparse images exist in <root>/devicemapper/data and
// <root>/devicemapper/metadata
hasData := devices.hasImage("data")
hasMetadata := devices.hasImage("metadata")
if !doInit && !hasData {
return fmt.Errorf("Looback data file not found %s")
}
if !doInit && !hasMetadata {
return fmt.Errorf("Looback metadata file not found %s")
}
createdLoopback := !hasData || !hasMetadata
data, err := devices.ensureImage("data", DefaultDataLoopbackSize)
if err != nil {
utils.Debugf("Error device ensureImage (data): %s\n", err)
return err
}
metadata, err := devices.ensureImage("metadata", DefaultMetaDataLoopbackSize)
if err != nil {
utils.Debugf("Error device ensureImage (metadata): %s\n", err)
return err
}
// Set the device prefix from the device id and inode of the docker root dir
st, err := os.Stat(devices.root)
if err != nil {
return fmt.Errorf("Error looking up dir %s: %s", devices.root, err)
}
sysSt := st.Sys().(*syscall.Stat_t)
// "reg-" stands for "regular file".
// In the future we might use "dev-" for "device file", etc.
// docker-maj,min[-inode] stands for:
// - Managed by docker
// - The target of this device is at major <maj> and minor <min>
// - If <inode> is defined, use that file inside the device as a loopback image. Otherwise use the device itself.
devices.devicePrefix = fmt.Sprintf("docker-%d:%d-%d", major(sysSt.Dev), minor(sysSt.Dev), sysSt.Ino)
utils.Debugf("Generated prefix: %s", devices.devicePrefix)
// Check for the existence of the device <prefix>-pool
utils.Debugf("Checking for existence of the pool '%s'", devices.getPoolName())
info, err := getInfo(devices.getPoolName())
if info == nil {
utils.Debugf("Error device getInfo: %s", err)
return err
}
// It seems libdevmapper opens this without O_CLOEXEC, and go exec will not close files
// that are not Close-on-exec, and lxc-start will die if it inherits any unexpected files,
// so we add this badhack to make sure it closes itself
setCloseOnExec("/dev/mapper/control")
// If the pool doesn't exist, create it
if info.Exists == 0 {
utils.Debugf("Pool doesn't exist. Creating it.")
dataFile, err := AttachLoopDevice(data)
if err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
defer dataFile.Close()
metadataFile, err := AttachLoopDevice(metadata)
if err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
defer metadataFile.Close()
if err := createPool(devices.getPoolName(), dataFile, metadataFile); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
}
// If we didn't just create the data or metadata image, we need to
// load the metadata from the existing file.
if !createdLoopback {
if err = devices.loadMetaData(); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
}
// Setup the base image
if doInit {
if err := devices.setupBaseImage(); err != nil {
utils.Debugf("Error device setupBaseImage: %s\n", err)
return err
}
}
return nil
}
func (devices *DeviceSet) AddDevice(hash, baseHash string) error {
devices.Lock()
defer devices.Unlock()
if devices.Devices[hash] != nil {
return fmt.Errorf("hash %s already exists", hash)
}
baseInfo := devices.Devices[baseHash]
if baseInfo == nil {
return fmt.Errorf("Error adding device for '%s': can't find device for parent '%s'", hash, baseHash)
}
deviceId := devices.allocateDeviceId()
if err := devices.createSnapDevice(devices.getPoolDevName(), deviceId, baseInfo.Name(), baseInfo.DeviceId); err != nil {
utils.Debugf("Error creating snap device: %s\n", err)
return err
}
if _, err := devices.registerDevice(deviceId, hash, baseInfo.Size); err != nil {
deleteDevice(devices.getPoolDevName(), deviceId)
utils.Debugf("Error registering device: %s\n", err)
return err
}
return nil
}
func (devices *DeviceSet) removeDevice(hash string) error {
info := devices.Devices[hash]
if info == nil {
return fmt.Errorf("hash %s doesn't exists", hash)
}
devinfo, _ := getInfo(info.Name())
if devinfo != nil && devinfo.Exists != 0 {
if err := removeDevice(info.Name()); err != nil {
utils.Debugf("Error removing device: %s\n", err)
return err
}
}
if info.Initialized {
info.Initialized = false
if err := devices.saveMetadata(); err != nil {
utils.Debugf("Error saving meta data: %s\n", err)
return err
}
}
if err := deleteDevice(devices.getPoolDevName(), info.DeviceId); err != nil {
utils.Debugf("Error deleting device: %s\n", err)
return err
}
devices.allocateTransactionId()
delete(devices.Devices, info.Hash)
if err := devices.saveMetadata(); err != nil {
devices.Devices[info.Hash] = info
utils.Debugf("Error saving meta data: %s\n", err)
return err
}
return nil
}
func (devices *DeviceSet) RemoveDevice(hash string) error {
devices.Lock()
defer devices.Unlock()
return devices.removeDevice(hash)
}
func (devices *DeviceSet) deactivateDevice(hash string) error {
utils.Debugf("[devmapper] deactivateDevice(%s)", hash)
defer utils.Debugf("[devmapper] deactivateDevice END")
var devname string
// FIXME: shouldn't we just register the pool into devices?
devname, err := devices.byHash(hash)
if err != nil {
return err
}
devinfo, err := getInfo(devname)
if err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
if devinfo.Exists != 0 {
if err := removeDevice(devname); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
if err := devices.waitRemove(hash); err != nil {
return err
}
}
return nil
}
// waitRemove blocks until either:
// a) the device registered at <device_set_prefix>-<hash> is removed,
// or b) the 1 second timeout expires.
func (devices *DeviceSet) waitRemove(hash string) error {
utils.Debugf("[deviceset %s] waitRemove(%s)", devices.devicePrefix, hash)
defer utils.Debugf("[deviceset %s] waitRemove END", devices.devicePrefix, hash)
devname, err := devices.byHash(hash)
if err != nil {
return err
}
i := 0
for ; i < 1000; i += 1 {
devinfo, err := getInfo(devname)
if err != nil {
// If there is an error we assume the device doesn't exist.
// The error might actually be something else, but we can't differentiate.
return nil
}
utils.Debugf("Waiting for removal of %s: exists=%d", devname, devinfo.Exists)
if devinfo.Exists == 0 {
break
}
time.Sleep(1 * time.Millisecond)
}
if i == 1000 {
return fmt.Errorf("Timeout while waiting for device %s to be removed", devname)
}
return nil
}
// waitClose blocks until either:
// a) the device registered at <device_set_prefix>-<hash> is closed,
// or b) the 1 second timeout expires.
func (devices *DeviceSet) waitClose(hash string) error {
devname, err := devices.byHash(hash)
if err != nil {
return err
}
i := 0
for ; i < 1000; i += 1 {
devinfo, err := getInfo(devname)
if err != nil {
return err
}
utils.Debugf("Waiting for unmount of %s: opencount=%d", devname, devinfo.OpenCount)
if devinfo.OpenCount == 0 {
break
}
time.Sleep(1 * time.Millisecond)
}
if i == 1000 {
return fmt.Errorf("Timeout while waiting for device %s to close", devname)
}
return nil
}
// byHash is a hack to allow looking up the deviceset's pool by the hash "pool".
// FIXME: it seems probably cleaner to register the pool in devices.Devices,
// but I am afraid of arcane implications deep in the devicemapper code,
// so this will do.
func (devices *DeviceSet) byHash(hash string) (devname string, err error) {
if hash == "pool" {
return devices.getPoolDevName(), nil
}
info := devices.Devices[hash]
if info == nil {
return "", fmt.Errorf("hash %s doesn't exists", hash)
}
return info.Name(), nil
}
func (devices *DeviceSet) Shutdown() error {
utils.Debugf("[deviceset %s] shutdown()", devices.devicePrefix)
defer utils.Debugf("[deviceset %s] shutdown END", devices.devicePrefix)
devices.Lock()
utils.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root)
defer devices.Unlock()
for path, count := range devices.activeMounts {
for i := count; i > 0; i-- {
if err := syscall.Unmount(path, 0); err != nil {
utils.Debugf("Shutdown unmounting %s, error: %s\n", path, err)
}
}
delete(devices.activeMounts, path)
}
for _, d := range devices.Devices {
if err := devices.waitClose(d.Hash); err != nil {
utils.Errorf("Warning: error waiting for device %s to unmount: %s\n", d.Hash, err)
}
if err := devices.deactivateDevice(d.Hash); err != nil {
utils.Debugf("Shutdown deactivate %s , error: %s\n", d.Hash, err)
}
}
pool := devices.getPoolDevName()
if devinfo, err := getInfo(pool); err == nil && devinfo.Exists != 0 {
if err := devices.deactivateDevice("pool"); err != nil {
utils.Debugf("Shutdown deactivate %s , error: %s\n", pool, err)
}
}
return nil
}
func (devices *DeviceSet) MountDevice(hash, path string, readOnly bool) error {
devices.Lock()
defer devices.Unlock()
if err := devices.activateDeviceIfNeeded(hash); err != nil {
return fmt.Errorf("Error activating devmapper device for '%s': %s", hash, err)
}
info := devices.Devices[hash]
var flags uintptr = syscall.MS_MGC_VAL
if readOnly {
flags = flags | syscall.MS_RDONLY
}
err := syscall.Mount(info.DevName(), path, "ext4", flags, "discard")
if err != nil && err == syscall.EINVAL {
err = syscall.Mount(info.DevName(), path, "ext4", flags, "")
}
if err != nil {
return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), path, err)
}
count := devices.activeMounts[path]
devices.activeMounts[path] = count + 1
return devices.setInitialized(hash)
}
func (devices *DeviceSet) UnmountDevice(hash, path string, deactivate bool) error {
utils.Debugf("[devmapper] UnmountDevice(hash=%s path=%s)", hash, path)
defer utils.Debugf("[devmapper] UnmountDevice END")
devices.Lock()
defer devices.Unlock()
utils.Debugf("[devmapper] Unmount(%s)", path)
if err := syscall.Unmount(path, 0); err != nil {
utils.Debugf("\n--->Err: %s\n", err)
return err
}
utils.Debugf("[devmapper] Unmount done")
// Wait for the unmount to be effective,
// by watching the value of Info.OpenCount for the device
if err := devices.waitClose(hash); err != nil {
return err
}
if count := devices.activeMounts[path]; count > 1 {
devices.activeMounts[path] = count - 1
} else {
delete(devices.activeMounts, path)
}
if deactivate {
devices.deactivateDevice(hash)
}
return nil
}
func (devices *DeviceSet) HasDevice(hash string) bool {
devices.Lock()
defer devices.Unlock()
return devices.Devices[hash] != nil
}
func (devices *DeviceSet) HasInitializedDevice(hash string) bool {
devices.Lock()
defer devices.Unlock()
info := devices.Devices[hash]
return info != nil && info.Initialized
}
func (devices *DeviceSet) HasActivatedDevice(hash string) bool {
devices.Lock()
defer devices.Unlock()
info := devices.Devices[hash]
if info == nil {
return false
}
devinfo, _ := getInfo(info.Name())
return devinfo != nil && devinfo.Exists != 0
}
func (devices *DeviceSet) setInitialized(hash string) error {
info := devices.Devices[hash]
if info == nil {
return fmt.Errorf("Unknown device %s", hash)
}
info.Initialized = true
if err := devices.saveMetadata(); err != nil {
info.Initialized = false
utils.Debugf("\n--->Err: %s\n", err)
return err
}
return nil
}
func (devices *DeviceSet) List() []string {
devices.Lock()
defer devices.Unlock()
ids := make([]string, len(devices.Devices))
i := 0
for k := range devices.Devices {
ids[i] = k
i++
}
return ids
}
func (devices *DeviceSet) deviceStatus(devName string) (sizeInSectors, mappedSectors, highestMappedSector uint64, err error) {
var params string
_, sizeInSectors, _, params, err = getStatus(devName)
if err != nil {
return
}
if _, err = fmt.Sscanf(params, "%d %d", &mappedSectors, &highestMappedSector); err == nil {
return
}
return
}
func (devices *DeviceSet) GetDeviceStatus(hash string) (*DevStatus, error) {
devices.Lock()
defer devices.Unlock()
info := devices.Devices[hash]
if info == nil {
return nil, fmt.Errorf("No device %s", hash)
}
status := &DevStatus{
DeviceId: info.DeviceId,
Size: info.Size,
TransactionId: info.TransactionId,
}
if err := devices.activateDeviceIfNeeded(hash); err != nil {
return nil, fmt.Errorf("Error activating devmapper device for '%s': %s", hash, err)
}
if sizeInSectors, mappedSectors, highestMappedSector, err := devices.deviceStatus(info.DevName()); err != nil {
return nil, err
} else {
status.SizeInSectors = sizeInSectors
status.MappedSectors = mappedSectors
status.HighestMappedSector = highestMappedSector
}
return status, nil
}
func (devices *DeviceSet) poolStatus() (totalSizeInSectors, transactionId, dataUsed, dataTotal, metadataUsed, metadataTotal uint64, err error) {
var params string
if _, totalSizeInSectors, _, params, err = getStatus(devices.getPoolName()); err == nil {
_, err = fmt.Sscanf(params, "%d %d/%d %d/%d", &transactionId, &metadataUsed, &metadataTotal, &dataUsed, &dataTotal)
}
return
}
func (devices *DeviceSet) Status() *Status {
devices.Lock()
defer devices.Unlock()
status := &Status{}
status.PoolName = devices.getPoolName()
status.DataLoopback = path.Join(devices.loopbackDir(), "data")
status.MetadataLoopback = path.Join(devices.loopbackDir(), "metadata")
totalSizeInSectors, _, dataUsed, dataTotal, metadataUsed, metadataTotal, err := devices.poolStatus()
if err == nil {
// Convert from blocks to bytes
blockSizeInSectors := totalSizeInSectors / dataTotal
status.Data.Used = dataUsed * blockSizeInSectors * 512
status.Data.Total = dataTotal * blockSizeInSectors * 512
// metadata blocks are always 4k
status.Metadata.Used = metadataUsed * 4096
status.Metadata.Total = metadataTotal * 4096
status.SectorSize = blockSizeInSectors * 512
}
return status
}
func NewDeviceSet(root string, doInit bool) (*DeviceSet, error) {
SetDevDir("/dev")
devices := &DeviceSet{
root: root,
MetaData: MetaData{Devices: make(map[string]*DevInfo)},
activeMounts: make(map[string]int),
}
if err := devices.initDevmapper(doInit); err != nil {
return nil, err
}
return devices, nil
}
-580
View File
@@ -1,580 +0,0 @@
package devmapper
import (
"errors"
"fmt"
"github.com/dotcloud/docker/utils"
"os"
"runtime"
"syscall"
)
type DevmapperLogger interface {
log(level int, file string, line int, dmError int, message string)
}
const (
DeviceCreate TaskType = iota
DeviceReload
DeviceRemove
DeviceRemoveAll
DeviceSuspend
DeviceResume
DeviceInfo
DeviceDeps
DeviceRename
DeviceVersion
DeviceStatus
DeviceTable
DeviceWaitevent
DeviceList
DeviceClear
DeviceMknodes
DeviceListVersions
DeviceTargetMsg
DeviceSetGeometry
)
const (
AddNodeOnResume AddNodeType = iota
AddNodeOnCreate
)
var (
ErrTaskRun = errors.New("dm_task_run failed")
ErrTaskSetName = errors.New("dm_task_set_name failed")
ErrTaskSetMessage = errors.New("dm_task_set_message failed")
ErrTaskSetAddNode = errors.New("dm_task_set_add_node failed")
ErrTaskSetRo = errors.New("dm_task_set_ro failed")
ErrTaskAddTarget = errors.New("dm_task_add_target failed")
ErrTaskSetSector = errors.New("dm_task_set_sector failed")
ErrTaskGetInfo = errors.New("dm_task_get_info failed")
ErrTaskGetDriverVersion = errors.New("dm_task_get_driver_version failed")
ErrTaskSetCookie = errors.New("dm_task_set_cookie failed")
ErrNilCookie = errors.New("cookie ptr can't be nil")
ErrAttachLoopbackDevice = errors.New("loopback mounting failed")
ErrGetBlockSize = errors.New("Can't get block size")
ErrUdevWait = errors.New("wait on udev cookie failed")
ErrSetDevDir = errors.New("dm_set_dev_dir failed")
ErrGetLibraryVersion = errors.New("dm_get_library_version failed")
ErrCreateRemoveTask = errors.New("Can't create task of type DeviceRemove")
ErrRunRemoveDevice = errors.New("running removeDevice failed")
ErrInvalidAddNode = errors.New("Invalide AddNoce type")
ErrGetLoopbackBackingFile = errors.New("Unable to get loopback backing file")
ErrLoopbackSetCapacity = errors.New("Unable set loopback capacity")
)
type (
Task struct {
unmanaged *CDmTask
}
Info struct {
Exists int
Suspended int
LiveTable int
InactiveTable int
OpenCount int32
EventNr uint32
Major uint32
Minor uint32
ReadOnly int
TargetCount int32
}
TaskType int
AddNodeType int
)
func (t *Task) destroy() {
if t != nil {
DmTaskDestory(t.unmanaged)
runtime.SetFinalizer(t, nil)
}
}
func TaskCreate(tasktype TaskType) *Task {
Ctask := DmTaskCreate(int(tasktype))
if Ctask == nil {
return nil
}
task := &Task{unmanaged: Ctask}
runtime.SetFinalizer(task, (*Task).destroy)
return task
}
func (t *Task) Run() error {
if res := DmTaskRun(t.unmanaged); res != 1 {
return ErrTaskRun
}
return nil
}
func (t *Task) SetName(name string) error {
if res := DmTaskSetName(t.unmanaged, name); res != 1 {
return ErrTaskSetName
}
return nil
}
func (t *Task) SetMessage(message string) error {
if res := DmTaskSetMessage(t.unmanaged, message); res != 1 {
return ErrTaskSetMessage
}
return nil
}
func (t *Task) SetSector(sector uint64) error {
if res := DmTaskSetSector(t.unmanaged, sector); res != 1 {
return ErrTaskSetSector
}
return nil
}
func (t *Task) SetCookie(cookie *uint, flags uint16) error {
if cookie == nil {
return ErrNilCookie
}
if res := DmTaskSetCookie(t.unmanaged, cookie, flags); res != 1 {
return ErrTaskSetCookie
}
return nil
}
func (t *Task) SetAddNode(addNode AddNodeType) error {
if addNode != AddNodeOnResume && addNode != AddNodeOnCreate {
return ErrInvalidAddNode
}
if res := DmTaskSetAddNode(t.unmanaged, addNode); res != 1 {
return ErrTaskSetAddNode
}
return nil
}
func (t *Task) SetRo() error {
if res := DmTaskSetRo(t.unmanaged); res != 1 {
return ErrTaskSetRo
}
return nil
}
func (t *Task) AddTarget(start, size uint64, ttype, params string) error {
if res := DmTaskAddTarget(t.unmanaged, start, size,
ttype, params); res != 1 {
return ErrTaskAddTarget
}
return nil
}
func (t *Task) GetInfo() (*Info, error) {
info := &Info{}
if res := DmTaskGetInfo(t.unmanaged, info); res != 1 {
return nil, ErrTaskGetInfo
}
return info, nil
}
func (t *Task) GetNextTarget(next uintptr) (nextPtr uintptr, start uint64,
length uint64, targetType string, params string) {
return DmGetNextTarget(t.unmanaged, next, &start, &length,
&targetType, &params),
start, length, targetType, params
}
func AttachLoopDevice(filename string) (*os.File, error) {
var fd int
res := DmAttachLoopDevice(filename, &fd)
if res == "" {
return nil, ErrAttachLoopbackDevice
}
return os.NewFile(uintptr(fd), res), nil
}
func getLoopbackBackingFile(file *os.File) (uint64, uint64, error) {
dev, inode, err := dmGetLoopbackBackingFile(file.Fd())
if err != 0 {
return 0, 0, ErrGetLoopbackBackingFile
}
return dev, inode, nil
}
func LoopbackSetCapacity(file *os.File) error {
err := dmLoopbackSetCapacity(file.Fd())
if err != 0 {
return ErrLoopbackSetCapacity
}
return nil
}
func FindLoopDeviceFor(file *os.File) *os.File {
stat, err := file.Stat()
if err != nil {
return nil
}
targetInode := stat.Sys().(*syscall.Stat_t).Ino
targetDevice := stat.Sys().(*syscall.Stat_t).Dev
for i := 0; true; i++ {
path := fmt.Sprintf("/dev/loop%d", i)
file, err := os.OpenFile(path, os.O_RDWR, 0)
if err != nil {
if os.IsNotExist(err) {
return nil
}
// Ignore all errors until the first not-exist
// we want to continue looking for the file
continue
}
dev, inode, err := getLoopbackBackingFile(file)
if err == nil && dev == targetDevice && inode == targetInode {
return file
}
file.Close()
}
return nil
}
func UdevWait(cookie uint) error {
if res := DmUdevWait(cookie); res != 1 {
utils.Debugf("Failed to wait on udev cookie %d", cookie)
return ErrUdevWait
}
return nil
}
func LogInitVerbose(level int) {
DmLogInitVerbose(level)
}
var dmLogger DevmapperLogger = nil
func logInit(logger DevmapperLogger) {
dmLogger = logger
LogWithErrnoInit()
}
func SetDevDir(dir string) error {
if res := DmSetDevDir(dir); res != 1 {
utils.Debugf("Error dm_set_dev_dir")
return ErrSetDevDir
}
return nil
}
func GetLibraryVersion() (string, error) {
var version string
if res := DmGetLibraryVersion(&version); res != 1 {
return "", ErrGetLibraryVersion
}
return version, nil
}
// Useful helper for cleanup
func RemoveDevice(name string) error {
task := TaskCreate(DeviceRemove)
if task == nil {
return ErrCreateRemoveTask
}
if err := task.SetName(name); err != nil {
utils.Debugf("Can't set task name %s", name)
return err
}
if err := task.Run(); err != nil {
return ErrRunRemoveDevice
}
return nil
}
func GetBlockDeviceSize(file *os.File) (uint64, error) {
size, errno := DmGetBlockSize(file.Fd())
if size == -1 || errno != 0 {
return 0, ErrGetBlockSize
}
return uint64(size), nil
}
// This is the programmatic example of "dmsetup create"
func createPool(poolName string, dataFile *os.File, metadataFile *os.File) error {
task, err := createTask(DeviceCreate, poolName)
if task == nil {
return err
}
size, err := GetBlockDeviceSize(dataFile)
if err != nil {
return fmt.Errorf("Can't get data size")
}
params := metadataFile.Name() + " " + dataFile.Name() + " 128 32768"
if err := task.AddTarget(0, size/512, "thin-pool", params); err != nil {
return fmt.Errorf("Can't add target")
}
var cookie uint = 0
if err := task.SetCookie(&cookie, 0); err != nil {
return fmt.Errorf("Can't set cookie")
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running DeviceCreate (createPool)")
}
UdevWait(cookie)
return nil
}
func reloadPool(poolName string, dataFile *os.File, metadataFile *os.File) error {
task, err := createTask(DeviceReload, poolName)
if task == nil {
return err
}
size, err := GetBlockDeviceSize(dataFile)
if err != nil {
return fmt.Errorf("Can't get data size")
}
params := metadataFile.Name() + " " + dataFile.Name() + " 128 32768"
if err := task.AddTarget(0, size/512, "thin-pool", params); err != nil {
return fmt.Errorf("Can't add target")
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running DeviceCreate")
}
return nil
}
func createTask(t TaskType, name string) (*Task, error) {
task := TaskCreate(t)
if task == nil {
return nil, fmt.Errorf("Can't create task of type %d", int(t))
}
if err := task.SetName(name); err != nil {
return nil, fmt.Errorf("Can't set task name %s", name)
}
return task, nil
}
func getInfo(name string) (*Info, error) {
task, err := createTask(DeviceInfo, name)
if task == nil {
return nil, err
}
if err := task.Run(); err != nil {
return nil, err
}
return task.GetInfo()
}
func getStatus(name string) (uint64, uint64, string, string, error) {
task, err := createTask(DeviceStatus, name)
if task == nil {
utils.Debugf("getStatus: Error createTask: %s", err)
return 0, 0, "", "", err
}
if err := task.Run(); err != nil {
utils.Debugf("getStatus: Error Run: %s", err)
return 0, 0, "", "", err
}
devinfo, err := task.GetInfo()
if err != nil {
utils.Debugf("getStatus: Error GetInfo: %s", err)
return 0, 0, "", "", err
}
if devinfo.Exists == 0 {
utils.Debugf("getStatus: Non existing device %s", name)
return 0, 0, "", "", fmt.Errorf("Non existing device %s", name)
}
_, start, length, target_type, params := task.GetNextTarget(0)
return start, length, target_type, params, nil
}
func setTransactionId(poolName string, oldId uint64, newId uint64) error {
task, err := createTask(DeviceTargetMsg, poolName)
if task == nil {
return err
}
if err := task.SetSector(0); err != nil {
return fmt.Errorf("Can't set sector")
}
if err := task.SetMessage(fmt.Sprintf("set_transaction_id %d %d", oldId, newId)); err != nil {
return fmt.Errorf("Can't set message")
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running setTransactionId")
}
return nil
}
func suspendDevice(name string) error {
task, err := createTask(DeviceSuspend, name)
if task == nil {
return err
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running DeviceSuspend")
}
return nil
}
func resumeDevice(name string) error {
task, err := createTask(DeviceResume, name)
if task == nil {
return err
}
var cookie uint = 0
if err := task.SetCookie(&cookie, 0); err != nil {
return fmt.Errorf("Can't set cookie")
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running DeviceSuspend")
}
UdevWait(cookie)
return nil
}
func createDevice(poolName string, deviceId int) error {
utils.Debugf("[devmapper] createDevice(poolName=%v, deviceId=%v)", poolName, deviceId)
task, err := createTask(DeviceTargetMsg, poolName)
if task == nil {
return err
}
if err := task.SetSector(0); err != nil {
return fmt.Errorf("Can't set sector")
}
if err := task.SetMessage(fmt.Sprintf("create_thin %d", deviceId)); err != nil {
return fmt.Errorf("Can't set message")
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running createDevice")
}
return nil
}
func deleteDevice(poolName string, deviceId int) error {
task, err := createTask(DeviceTargetMsg, poolName)
if task == nil {
return err
}
if err := task.SetSector(0); err != nil {
return fmt.Errorf("Can't set sector")
}
if err := task.SetMessage(fmt.Sprintf("delete %d", deviceId)); err != nil {
return fmt.Errorf("Can't set message")
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running deleteDevice")
}
return nil
}
func removeDevice(name string) error {
utils.Debugf("[devmapper] removeDevice START")
defer utils.Debugf("[devmapper] removeDevice END")
task, err := createTask(DeviceRemove, name)
if task == nil {
return err
}
if err = task.Run(); err != nil {
return fmt.Errorf("Error running removeDevice")
}
return nil
}
func activateDevice(poolName string, name string, deviceId int, size uint64) error {
task, err := createTask(DeviceCreate, name)
if task == nil {
return err
}
params := fmt.Sprintf("%s %d", poolName, deviceId)
if err := task.AddTarget(0, size/512, "thin", params); err != nil {
return fmt.Errorf("Can't add target")
}
if err := task.SetAddNode(AddNodeOnCreate); err != nil {
return fmt.Errorf("Can't add node")
}
var cookie uint = 0
if err := task.SetCookie(&cookie, 0); err != nil {
return fmt.Errorf("Can't set cookie")
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running DeviceCreate (activateDevice)")
}
UdevWait(cookie)
return nil
}
func (devices *DeviceSet) createSnapDevice(poolName string, deviceId int, baseName string, baseDeviceId int) error {
devinfo, _ := getInfo(baseName)
doSuspend := devinfo != nil && devinfo.Exists != 0
if doSuspend {
if err := suspendDevice(baseName); err != nil {
return err
}
}
task, err := createTask(DeviceTargetMsg, poolName)
if task == nil {
if doSuspend {
resumeDevice(baseName)
}
return err
}
if err := task.SetSector(0); err != nil {
if doSuspend {
resumeDevice(baseName)
}
return fmt.Errorf("Can't set sector")
}
if err := task.SetMessage(fmt.Sprintf("create_snap %d %d", deviceId, baseDeviceId)); err != nil {
if doSuspend {
resumeDevice(baseName)
}
return fmt.Errorf("Can't set message")
}
if err := task.Run(); err != nil {
if doSuspend {
resumeDevice(baseName)
}
return fmt.Errorf("Error running DeviceCreate (createSnapDevice)")
}
if doSuspend {
if err := resumeDevice(baseName); err != nil {
return err
}
}
return nil
}
-13
View File
@@ -1,13 +0,0 @@
package devmapper
import "C"
// Due to the way cgo works this has to be in a separate file, as devmapper.go has
// definitions in the cgo block, which is incompatible with using "//export"
//export DevmapperLogCallback
func DevmapperLogCallback(level C.int, file *C.char, line C.int, dm_errno_or_class C.int, message *C.char) {
if dmLogger != nil {
dmLogger.log(int(level), C.GoString(file), int(line), int(dm_errno_or_class), C.GoString(message))
}
}
-285
View File
@@ -1,285 +0,0 @@
package devmapper
import (
"syscall"
"testing"
)
func TestTaskCreate(t *testing.T) {
// Test success
taskCreate(t, DeviceInfo)
// Test Failure
DmTaskCreate = dmTaskCreateFail
defer func() { DmTaskCreate = dmTaskCreateFct }()
if task := TaskCreate(-1); task != nil {
t.Fatalf("An error should have occured while creating an invalid task.")
}
}
func TestTaskRun(t *testing.T) {
task := taskCreate(t, DeviceInfo)
// Test success
// Perform the RUN
if err := task.Run(); err != nil {
t.Fatal(err)
}
// Make sure we don't have error with GetInfo
if _, err := task.GetInfo(); err != nil {
t.Fatal(err)
}
// Test failure
DmTaskRun = dmTaskRunFail
defer func() { DmTaskRun = dmTaskRunFct }()
task = taskCreate(t, DeviceInfo)
// Perform the RUN
if err := task.Run(); err != ErrTaskRun {
t.Fatalf("An error should have occured while running task.")
}
// Make sure GetInfo also fails
if _, err := task.GetInfo(); err != ErrTaskGetInfo {
t.Fatalf("GetInfo should fail if task.Run() failed.")
}
}
func TestTaskSetName(t *testing.T) {
task := taskCreate(t, DeviceInfo)
// Test success
if err := task.SetName("test"); err != nil {
t.Fatal(err)
}
// Test failure
DmTaskSetName = dmTaskSetNameFail
defer func() { DmTaskSetName = dmTaskSetNameFct }()
if err := task.SetName("test"); err != ErrTaskSetName {
t.Fatalf("An error should have occured while runnign SetName.")
}
}
func TestTaskSetMessage(t *testing.T) {
task := taskCreate(t, DeviceInfo)
// Test success
if err := task.SetMessage("test"); err != nil {
t.Fatal(err)
}
// Test failure
DmTaskSetMessage = dmTaskSetMessageFail
defer func() { DmTaskSetMessage = dmTaskSetMessageFct }()
if err := task.SetMessage("test"); err != ErrTaskSetMessage {
t.Fatalf("An error should have occured while runnign SetMessage.")
}
}
func TestTaskSetSector(t *testing.T) {
task := taskCreate(t, DeviceInfo)
// Test success
if err := task.SetSector(128); err != nil {
t.Fatal(err)
}
DmTaskSetSector = dmTaskSetSectorFail
defer func() { DmTaskSetSector = dmTaskSetSectorFct }()
// Test failure
if err := task.SetSector(0); err != ErrTaskSetSector {
t.Fatalf("An error should have occured while running SetSector.")
}
}
func TestTaskSetCookie(t *testing.T) {
var (
cookie uint = 0
task = taskCreate(t, DeviceInfo)
)
// Test success
if err := task.SetCookie(&cookie, 0); err != nil {
t.Fatal(err)
}
// Test failure
if err := task.SetCookie(nil, 0); err != ErrNilCookie {
t.Fatalf("An error should have occured while running SetCookie with nil cookie.")
}
DmTaskSetCookie = dmTaskSetCookieFail
defer func() { DmTaskSetCookie = dmTaskSetCookieFct }()
if err := task.SetCookie(&cookie, 0); err != ErrTaskSetCookie {
t.Fatalf("An error should have occured while running SetCookie.")
}
}
func TestTaskSetAddNode(t *testing.T) {
task := taskCreate(t, DeviceInfo)
// Test success
if err := task.SetAddNode(0); err != nil {
t.Fatal(err)
}
// Test failure
if err := task.SetAddNode(-1); err != ErrInvalidAddNode {
t.Fatalf("An error should have occured running SetAddNode with wrong node.")
}
DmTaskSetAddNode = dmTaskSetAddNodeFail
defer func() { DmTaskSetAddNode = dmTaskSetAddNodeFct }()
if err := task.SetAddNode(0); err != ErrTaskSetAddNode {
t.Fatalf("An error should have occured running SetAddNode.")
}
}
func TestTaskSetRo(t *testing.T) {
task := taskCreate(t, DeviceInfo)
// Test success
if err := task.SetRo(); err != nil {
t.Fatal(err)
}
// Test failure
DmTaskSetRo = dmTaskSetRoFail
defer func() { DmTaskSetRo = dmTaskSetRoFct }()
if err := task.SetRo(); err != ErrTaskSetRo {
t.Fatalf("An error should have occured running SetRo.")
}
}
func TestTaskAddTarget(t *testing.T) {
task := taskCreate(t, DeviceInfo)
// Test success
if err := task.AddTarget(0, 128, "thinp", ""); err != nil {
t.Fatal(err)
}
// Test failure
DmTaskAddTarget = dmTaskAddTargetFail
defer func() { DmTaskAddTarget = dmTaskAddTargetFct }()
if err := task.AddTarget(0, 128, "thinp", ""); err != ErrTaskAddTarget {
t.Fatalf("An error should have occured running AddTarget.")
}
}
// func TestTaskGetInfo(t *testing.T) {
// task := taskCreate(t, DeviceInfo)
// // Test success
// if _, err := task.GetInfo(); err != nil {
// t.Fatal(err)
// }
// // Test failure
// DmTaskGetInfo = dmTaskGetInfoFail
// defer func() { DmTaskGetInfo = dmTaskGetInfoFct }()
// if _, err := task.GetInfo(); err != ErrTaskGetInfo {
// t.Fatalf("An error should have occured running GetInfo.")
// }
// }
// func TestTaskGetNextTarget(t *testing.T) {
// task := taskCreate(t, DeviceInfo)
// if next, _, _, _, _ := task.GetNextTarget(0); next == 0 {
// t.Fatalf("The next target should not be 0.")
// }
// }
/// Utils
func taskCreate(t *testing.T, taskType TaskType) *Task {
task := TaskCreate(taskType)
if task == nil {
t.Fatalf("Error creating task")
}
return task
}
/// Failure function replacement
func dmTaskCreateFail(t int) *CDmTask {
return nil
}
func dmTaskRunFail(task *CDmTask) int {
return -1
}
func dmTaskSetNameFail(task *CDmTask, name string) int {
return -1
}
func dmTaskSetMessageFail(task *CDmTask, message string) int {
return -1
}
func dmTaskSetSectorFail(task *CDmTask, sector uint64) int {
return -1
}
func dmTaskSetCookieFail(task *CDmTask, cookie *uint, flags uint16) int {
return -1
}
func dmTaskSetAddNodeFail(task *CDmTask, addNode AddNodeType) int {
return -1
}
func dmTaskSetRoFail(task *CDmTask) int {
return -1
}
func dmTaskAddTargetFail(task *CDmTask,
start, size uint64, ttype, params string) int {
return -1
}
func dmTaskGetDriverVersionFail(task *CDmTask, version *string) int {
return -1
}
func dmTaskGetInfoFail(task *CDmTask, info *Info) int {
return -1
}
func dmGetNextTargetFail(task *CDmTask, next uintptr, start, length *uint64,
target, params *string) uintptr {
return 0
}
func dmAttachLoopDeviceFail(filename string, fd *int) string {
return ""
}
func sysGetBlockSizeFail(fd uintptr, size *uint64) syscall.Errno {
return 1
}
func dmGetBlockSizeFail(fd uintptr) int64 {
return -1
}
func dmUdevWaitFail(cookie uint) int {
return -1
}
func dmSetDevDirFail(dir string) int {
return -1
}
func dmGetLibraryVersionFail(version *string) int {
return -1
}
-342
View File
@@ -1,342 +0,0 @@
package devmapper
/*
#cgo LDFLAGS: -L. -ldevmapper
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <libdevmapper.h>
#include <linux/loop.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
#include <errno.h>
#ifndef LOOP_CTL_GET_FREE
#define LOOP_CTL_GET_FREE 0x4C82
#endif
// FIXME: this could easily be rewritten in go
char* attach_loop_device(const char *filename, int *loop_fd_out)
{
struct loop_info64 loopinfo = {0};
struct stat st;
char buf[64];
int i, loop_fd, fd, start_index;
char* loopname;
*loop_fd_out = -1;
start_index = 0;
fd = open("/dev/loop-control", O_RDONLY);
if (fd >= 0) {
start_index = ioctl(fd, LOOP_CTL_GET_FREE);
close(fd);
if (start_index < 0)
start_index = 0;
}
fd = open(filename, O_RDWR);
if (fd < 0) {
perror("open");
return NULL;
}
loop_fd = -1;
for (i = start_index ; loop_fd < 0 ; i++ ) {
if (sprintf(buf, "/dev/loop%d", i) < 0) {
close(fd);
return NULL;
}
if (stat(buf, &st)) {
if (!S_ISBLK(st.st_mode)) {
fprintf(stderr, "[error] Loopback device %s is not a block device.\n", buf);
} else if (errno == ENOENT) {
fprintf(stderr, "[error] There are no more loopback device available.\n");
} else {
fprintf(stderr, "[error] Unkown error trying to stat the loopback device %s (errno: %d).\n", buf, errno);
}
close(fd);
return NULL;
}
loop_fd = open(buf, O_RDWR);
if (loop_fd < 0 && errno == ENOENT) {
fprintf(stderr, "[error] The loopback device %s does not exists.\n", buf);
close(fd);
return NULL;
} else if (loop_fd < 0) {
fprintf(stderr, "[error] Unkown error openning the loopback device %s. (errno: %d)\n", buf, errno);
continue;
}
if (ioctl(loop_fd, LOOP_SET_FD, (void *)(size_t)fd) < 0) {
int errsv = errno;
close(loop_fd);
loop_fd = -1;
if (errsv != EBUSY) {
close(fd);
fprintf(stderr, "cannot set up loopback device %s: %s", buf, strerror(errsv));
return NULL;
}
continue;
}
close(fd);
strncpy((char*)loopinfo.lo_file_name, buf, LO_NAME_SIZE);
loopinfo.lo_offset = 0;
loopinfo.lo_flags = LO_FLAGS_AUTOCLEAR;
if (ioctl(loop_fd, LOOP_SET_STATUS64, &loopinfo) < 0) {
perror("ioctl LOOP_SET_STATUS64");
if (ioctl(loop_fd, LOOP_CLR_FD, 0) < 0) {
perror("ioctl LOOP_CLR_FD");
}
close(loop_fd);
fprintf (stderr, "cannot set up loopback device info");
return (NULL);
}
loopname = strdup(buf);
if (loopname == NULL) {
close(loop_fd);
return (NULL);
}
*loop_fd_out = loop_fd;
return (loopname);
}
return (NULL);
}
extern void DevmapperLogCallback(int level, char *file, int line, int dm_errno_or_class, char *str);
static void log_cb(int level, const char *file, int line,
int dm_errno_or_class, const char *f, ...)
{
char buffer[256];
va_list ap;
va_start(ap, f);
vsnprintf(buffer, 256, f, ap);
va_end(ap);
DevmapperLogCallback(level, (char *)file, line, dm_errno_or_class, buffer);
}
static void log_with_errno_init()
{
dm_log_with_errno_init(log_cb);
}
*/
import "C"
import (
"syscall"
"unsafe"
)
type (
CDmTask C.struct_dm_task
)
var (
DmTaskDestory = dmTaskDestroyFct
DmTaskCreate = dmTaskCreateFct
DmTaskRun = dmTaskRunFct
DmTaskSetName = dmTaskSetNameFct
DmTaskSetMessage = dmTaskSetMessageFct
DmTaskSetSector = dmTaskSetSectorFct
DmTaskSetCookie = dmTaskSetCookieFct
DmTaskSetAddNode = dmTaskSetAddNodeFct
DmTaskSetRo = dmTaskSetRoFct
DmTaskAddTarget = dmTaskAddTargetFct
DmTaskGetInfo = dmTaskGetInfoFct
DmGetNextTarget = dmGetNextTargetFct
DmGetBlockSize = dmGetBlockSizeFct
DmAttachLoopDevice = dmAttachLoopDeviceFct
DmUdevWait = dmUdevWaitFct
DmLogInitVerbose = dmLogInitVerboseFct
DmSetDevDir = dmSetDevDirFct
DmGetLibraryVersion = dmGetLibraryVersionFct
LogWithErrnoInit = logWithErrnoInitFct
GetBlockSize = getBlockSizeFct
)
func free(p *C.char) {
C.free(unsafe.Pointer(p))
}
func dmTaskDestroyFct(task *CDmTask) {
C.dm_task_destroy((*C.struct_dm_task)(task))
}
func dmTaskCreateFct(taskType int) *CDmTask {
return (*CDmTask)(C.dm_task_create(C.int(taskType)))
}
func dmTaskRunFct(task *CDmTask) int {
return int(C.dm_task_run((*C.struct_dm_task)(task)))
}
func dmTaskSetNameFct(task *CDmTask, name string) int {
Cname := C.CString(name)
defer free(Cname)
return int(C.dm_task_set_name((*C.struct_dm_task)(task),
Cname))
}
func dmTaskSetMessageFct(task *CDmTask, message string) int {
Cmessage := C.CString(message)
defer free(Cmessage)
return int(C.dm_task_set_message((*C.struct_dm_task)(task),
Cmessage))
}
func dmTaskSetSectorFct(task *CDmTask, sector uint64) int {
return int(C.dm_task_set_sector((*C.struct_dm_task)(task),
C.uint64_t(sector)))
}
func dmTaskSetCookieFct(task *CDmTask, cookie *uint, flags uint16) int {
cCookie := C.uint32_t(*cookie)
defer func() {
*cookie = uint(cCookie)
}()
return int(C.dm_task_set_cookie((*C.struct_dm_task)(task), &cCookie,
C.uint16_t(flags)))
}
func dmTaskSetAddNodeFct(task *CDmTask, addNode AddNodeType) int {
return int(C.dm_task_set_add_node((*C.struct_dm_task)(task),
C.dm_add_node_t(addNode)))
}
func dmTaskSetRoFct(task *CDmTask) int {
return int(C.dm_task_set_ro((*C.struct_dm_task)(task)))
}
func dmTaskAddTargetFct(task *CDmTask,
start, size uint64, ttype, params string) int {
Cttype := C.CString(ttype)
defer free(Cttype)
Cparams := C.CString(params)
defer free(Cparams)
return int(C.dm_task_add_target((*C.struct_dm_task)(task),
C.uint64_t(start), C.uint64_t(size), Cttype, Cparams))
}
func dmGetLoopbackBackingFile(fd uintptr) (uint64, uint64, syscall.Errno) {
var lo64 C.struct_loop_info64
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.LOOP_GET_STATUS64,
uintptr(unsafe.Pointer(&lo64)))
return uint64(lo64.lo_device), uint64(lo64.lo_inode), err
}
func dmLoopbackSetCapacity(fd uintptr) syscall.Errno {
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.LOOP_SET_CAPACITY, 0)
return err
}
func dmGetBlockSizeFct(fd uintptr) (int64, syscall.Errno) {
var size int64
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.BLKGETSIZE64,
uintptr(unsafe.Pointer(&size)))
return size, err
}
func dmTaskGetInfoFct(task *CDmTask, info *Info) int {
Cinfo := C.struct_dm_info{}
defer func() {
info.Exists = int(Cinfo.exists)
info.Suspended = int(Cinfo.suspended)
info.LiveTable = int(Cinfo.live_table)
info.InactiveTable = int(Cinfo.inactive_table)
info.OpenCount = int32(Cinfo.open_count)
info.EventNr = uint32(Cinfo.event_nr)
info.Major = uint32(Cinfo.major)
info.Minor = uint32(Cinfo.minor)
info.ReadOnly = int(Cinfo.read_only)
info.TargetCount = int32(Cinfo.target_count)
}()
return int(C.dm_task_get_info((*C.struct_dm_task)(task), &Cinfo))
}
func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64,
target, params *string) uintptr {
var (
Cstart, Clength C.uint64_t
CtargetType, Cparams *C.char
)
defer func() {
*start = uint64(Cstart)
*length = uint64(Clength)
*target = C.GoString(CtargetType)
*params = C.GoString(Cparams)
}()
nextp := C.dm_get_next_target((*C.struct_dm_task)(task),
unsafe.Pointer(next), &Cstart, &Clength, &CtargetType, &Cparams)
return uintptr(nextp)
}
func dmAttachLoopDeviceFct(filename string, fd *int) string {
cFilename := C.CString(filename)
defer free(cFilename)
var cFd C.int
defer func() {
*fd = int(cFd)
}()
ret := C.attach_loop_device(cFilename, &cFd)
defer free(ret)
return C.GoString(ret)
}
func getBlockSizeFct(fd uintptr, size *uint64) syscall.Errno {
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.BLKGETSIZE64,
uintptr(unsafe.Pointer(&size)))
return err
}
func dmUdevWaitFct(cookie uint) int {
return int(C.dm_udev_wait(C.uint32_t(cookie)))
}
func dmLogInitVerboseFct(level int) {
C.dm_log_init_verbose(C.int(level))
}
func logWithErrnoInitFct() {
C.log_with_errno_init()
}
func dmSetDevDirFct(dir string) int {
Cdir := C.CString(dir)
defer free(Cdir)
return int(C.dm_set_dev_dir(Cdir))
}
func dmGetLibraryVersionFct(version *string) int {
buffer := C.CString(string(make([]byte, 128)))
defer free(buffer)
defer func() {
*version = C.GoString(buffer)
}()
return int(C.dm_get_library_version(buffer, 128))
}
-123
View File
@@ -1,123 +0,0 @@
package devmapper
import (
"fmt"
"github.com/dotcloud/docker/graphdriver"
"io/ioutil"
"os"
"path"
)
func init() {
graphdriver.Register("devicemapper", Init)
}
// Placeholder interfaces, to be replaced
// at integration.
// End of placeholder interfaces.
type Driver struct {
*DeviceSet
home string
}
func Init(home string) (graphdriver.Driver, error) {
deviceSet, err := NewDeviceSet(home, true)
if err != nil {
return nil, err
}
d := &Driver{
DeviceSet: deviceSet,
home: home,
}
return d, nil
}
func (d *Driver) String() string {
return "devicemapper"
}
func (d *Driver) Status() [][2]string {
s := d.DeviceSet.Status()
status := [][2]string{
{"Pool Name", s.PoolName},
{"Data file", s.DataLoopback},
{"Metadata file", s.MetadataLoopback},
{"Data Space Used", fmt.Sprintf("%.1f Mb", float64(s.Data.Used)/(1024*1024))},
{"Data Space Total", fmt.Sprintf("%.1f Mb", float64(s.Data.Total)/(1024*1024))},
{"Metadata Space Used", fmt.Sprintf("%.1f Mb", float64(s.Metadata.Used)/(1024*1024))},
{"Metadata Space Total", fmt.Sprintf("%.1f Mb", float64(s.Metadata.Total)/(1024*1024))},
}
return status
}
func (d *Driver) Cleanup() error {
return d.DeviceSet.Shutdown()
}
func (d *Driver) Create(id string, parent string) error {
if err := d.DeviceSet.AddDevice(id, parent); err != nil {
return err
}
mp := path.Join(d.home, "mnt", id)
if err := d.mount(id, mp); err != nil {
return err
}
if err := os.MkdirAll(path.Join(mp, "rootfs"), 0755); err != nil && !os.IsExist(err) {
return err
}
// Create an "id" file with the container/image id in it to help reconscruct this in case
// of later problems
if err := ioutil.WriteFile(path.Join(mp, "id"), []byte(id), 0600); err != nil {
return err
}
return nil
}
func (d *Driver) Remove(id string) error {
mp := path.Join(d.home, "mnt", id)
if err := d.unmount(id, mp); err != nil {
return err
}
return d.DeviceSet.RemoveDevice(id)
}
func (d *Driver) Get(id string) (string, error) {
mp := path.Join(d.home, "mnt", id)
if err := d.mount(id, mp); err != nil {
return "", err
}
return path.Join(mp, "rootfs"), nil
}
func (d *Driver) mount(id, mountPoint string) error {
// Create the target directories if they don't exist
if err := os.MkdirAll(mountPoint, 0755); err != nil && !os.IsExist(err) {
return err
}
// If mountpoint is already mounted, do nothing
if mounted, err := Mounted(mountPoint); err != nil {
return fmt.Errorf("Error checking mountpoint: %s", err)
} else if mounted {
return nil
}
// Mount the device
return d.DeviceSet.MountDevice(id, mountPoint, false)
}
func (d *Driver) unmount(id, mountPoint string) error {
// If mountpoint is not mounted, do nothing
if mounted, err := Mounted(mountPoint); err != nil {
return fmt.Errorf("Error checking mountpoint: %s", err)
} else if !mounted {
return nil
}
// Unmount the device
return d.DeviceSet.UnmountDevice(id, mountPoint, true)
}
-303
View File
@@ -1,303 +0,0 @@
package devmapper
import (
"io/ioutil"
"os"
"path"
"testing"
)
func init() {
// Reduce the size the the base fs and loopback for the tests
DefaultDataLoopbackSize = 300 * 1024 * 1024
DefaultMetaDataLoopbackSize = 200 * 1024 * 1024
DefaultBaseFsSize = 300 * 1024 * 1024
}
func mkTestDirectory(t *testing.T) string {
dir, err := ioutil.TempDir("", "docker-test-devmapper-")
if err != nil {
t.Fatal(err)
}
return dir
}
func newDriver(t *testing.T) *Driver {
home := mkTestDirectory(t)
d, err := Init(home)
if err != nil {
t.Fatal(err)
}
return d.(*Driver)
}
func cleanup(d *Driver) {
d.Cleanup()
os.RemoveAll(d.home)
}
func TestInit(t *testing.T) {
home := mkTestDirectory(t)
defer os.RemoveAll(home)
driver, err := Init(home)
if err != nil {
t.Fatal(err)
}
defer func() {
if err := driver.Cleanup(); err != nil {
t.Fatal(err)
}
}()
id := "foo"
if err := driver.Create(id, ""); err != nil {
t.Fatal(err)
}
dir, err := driver.Get(id)
if err != nil {
t.Fatal(err)
}
if st, err := os.Stat(dir); err != nil {
t.Fatal(err)
} else if !st.IsDir() {
t.Fatalf("Get(%V) did not return a directory", id)
}
}
func TestDriverName(t *testing.T) {
d := newDriver(t)
defer cleanup(d)
if d.String() != "devicemapper" {
t.Fatalf("Expected driver name to be devicemapper got %s", d.String())
}
}
func TestDriverCreate(t *testing.T) {
d := newDriver(t)
defer cleanup(d)
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
}
}
func TestDriverRemove(t *testing.T) {
d := newDriver(t)
defer cleanup(d)
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
}
if err := d.Remove("1"); err != nil {
t.Fatal(err)
}
}
func TestCleanup(t *testing.T) {
t.Skip("Unimplemented")
d := newDriver(t)
defer os.RemoveAll(d.home)
mountPoints := make([]string, 2)
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
}
// Mount the id
p, err := d.Get("1")
if err != nil {
t.Fatal(err)
}
mountPoints[0] = p
if err := d.Create("2", "1"); err != nil {
t.Fatal(err)
}
p, err = d.Get("2")
if err != nil {
t.Fatal(err)
}
mountPoints[1] = p
// Ensure that all the mount points are currently mounted
for _, p := range mountPoints {
if mounted, err := Mounted(p); err != nil {
t.Fatal(err)
} else if !mounted {
t.Fatalf("Expected %s to be mounted", p)
}
}
// Ensure that devices are active
for _, p := range []string{"1", "2"} {
if !d.HasActivatedDevice(p) {
t.Fatalf("Expected %s to have an active device", p)
}
}
if err := d.Cleanup(); err != nil {
t.Fatal(err)
}
// Ensure that all the mount points are no longer mounted
for _, p := range mountPoints {
if mounted, err := Mounted(p); err != nil {
t.Fatal(err)
} else if mounted {
t.Fatalf("Expected %s to not be mounted", p)
}
}
// Ensure that devices are no longer activated
for _, p := range []string{"1", "2"} {
if d.HasActivatedDevice(p) {
t.Fatalf("Expected %s not be an active device", p)
}
}
}
func TestNotMounted(t *testing.T) {
t.Skip("Not implemented")
d := newDriver(t)
defer cleanup(d)
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
}
mounted, err := Mounted(path.Join(d.home, "mnt", "1"))
if err != nil {
t.Fatal(err)
}
if mounted {
t.Fatal("Id 1 should not be mounted")
}
}
func TestMounted(t *testing.T) {
d := newDriver(t)
defer cleanup(d)
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
}
if _, err := d.Get("1"); err != nil {
t.Fatal(err)
}
mounted, err := Mounted(path.Join(d.home, "mnt", "1"))
if err != nil {
t.Fatal(err)
}
if !mounted {
t.Fatal("Id 1 should be mounted")
}
}
func TestInitCleanedDriver(t *testing.T) {
d := newDriver(t)
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
}
if _, err := d.Get("1"); err != nil {
t.Fatal(err)
}
if err := d.Cleanup(); err != nil {
t.Fatal(err)
}
driver, err := Init(d.home)
if err != nil {
t.Fatal(err)
}
d = driver.(*Driver)
defer cleanup(d)
if _, err := d.Get("1"); err != nil {
t.Fatal(err)
}
}
func TestMountMountedDriver(t *testing.T) {
d := newDriver(t)
defer cleanup(d)
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
}
// Perform get on same id to ensure that it will
// not be mounted twice
if _, err := d.Get("1"); err != nil {
t.Fatal(err)
}
if _, err := d.Get("1"); err != nil {
t.Fatal(err)
}
}
func TestGetReturnsValidDevice(t *testing.T) {
d := newDriver(t)
defer cleanup(d)
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
}
if !d.HasDevice("1") {
t.Fatalf("Expected id 1 to be in device set")
}
if _, err := d.Get("1"); err != nil {
t.Fatal(err)
}
if !d.HasActivatedDevice("1") {
t.Fatalf("Expected id 1 to be activated")
}
if !d.HasInitializedDevice("1") {
t.Fatalf("Expected id 1 to be initialized")
}
}
func TestDriverGetSize(t *testing.T) {
t.Skipf("Size is currently not implemented")
d := newDriver(t)
defer cleanup(d)
if err := d.Create("1", ""); err != nil {
t.Fatal(err)
}
mountPoint, err := d.Get("1")
if err != nil {
t.Fatal(err)
}
size := int64(1024)
f, err := os.Create(path.Join(mountPoint, "test_file"))
if err != nil {
t.Fatal(err)
}
if err := f.Truncate(size); err != nil {
t.Fatal(err)
}
f.Close()
// diffSize, err := d.DiffSize("1")
// if err != nil {
// t.Fatal(err)
// }
// if diffSize != size {
// t.Fatalf("Expected size %d got %d", size, diffSize)
// }
}
-27
View File
@@ -1,27 +0,0 @@
package devmapper
import (
"os"
"path/filepath"
"syscall"
)
// FIXME: this is copy-pasted from the aufs driver.
// It should be moved into the core.
func Mounted(mountpoint string) (bool, error) {
mntpoint, err := os.Stat(mountpoint)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
parent, err := os.Stat(filepath.Join(mountpoint, ".."))
if err != nil {
return false, err
}
mntpointSt := mntpoint.Sys().(*syscall.Stat_t)
parentSt := parent.Sys().(*syscall.Stat_t)
return mntpointSt.Dev != parentSt.Dev, nil
}
-98
View File
@@ -1,98 +0,0 @@
package graphdriver
import (
"fmt"
"github.com/dotcloud/docker/archive"
"github.com/dotcloud/docker/utils"
"os"
"path"
)
var DefaultDriver string
type InitFunc func(root string) (Driver, error)
type Driver interface {
String() string
Create(id, parent string) error
Remove(id string) error
Get(id string) (dir string, err error)
Status() [][2]string
Cleanup() error
}
type Differ interface {
Diff(id string) (archive.Archive, error)
Changes(id string) ([]archive.Change, error)
ApplyDiff(id string, diff archive.Archive) error
DiffSize(id string) (bytes int64, err error)
}
var (
// All registred drivers
drivers map[string]InitFunc
// Slice of drivers that should be used in an order
priority = []string{
"aufs",
"devicemapper",
"dummy",
}
)
func init() {
drivers = make(map[string]InitFunc)
}
func Register(name string, initFunc InitFunc) error {
if _, exists := drivers[name]; exists {
return fmt.Errorf("Name already registered %s", name)
}
drivers[name] = initFunc
return nil
}
func GetDriver(name, home string) (Driver, error) {
if initFunc, exists := drivers[name]; exists {
return initFunc(path.Join(home, name))
}
return nil, fmt.Errorf("No such driver: %s", name)
}
func New(root string) (Driver, error) {
var driver Driver
var lastError error
for _, name := range []string{
os.Getenv("DOCKER_DRIVER"),
DefaultDriver,
} {
if name != "" {
return GetDriver(name, root)
}
}
// Check for priority drivers first
for _, name := range priority {
driver, lastError = GetDriver(name, root)
if lastError != nil {
utils.Debugf("Error loading driver %s: %s", name, lastError)
continue
}
return driver, nil
}
// Check all registered drivers if no priority driver is found
for _, initFunc := range drivers {
driver, lastError = initFunc(root)
if lastError != nil {
continue
}
return driver, nil
}
return nil, lastError
}
-86
View File
@@ -1,86 +0,0 @@
package dummy
import (
"fmt"
"github.com/dotcloud/docker/graphdriver"
"os"
"os/exec"
"path"
)
func init() {
graphdriver.Register("dummy", Init)
}
func Init(home string) (graphdriver.Driver, error) {
d := &Driver{
home: home,
}
return d, nil
}
type Driver struct {
home string
}
func (d *Driver) String() string {
return "dummy"
}
func (d *Driver) Status() [][2]string {
return nil
}
func (d *Driver) Cleanup() error {
return nil
}
func copyDir(src, dst string) error {
cmd := exec.Command("cp", "-aT", "--reflink=auto", src, dst)
if err := cmd.Run(); err != nil {
return err
}
return nil
}
func (d *Driver) Create(id string, parent string) error {
dir := d.dir(id)
if err := os.MkdirAll(path.Dir(dir), 0700); err != nil {
return err
}
if err := os.Mkdir(dir, 0700); err != nil {
return err
}
if parent == "" {
return nil
}
parentDir, err := d.Get(parent)
if err != nil {
return fmt.Errorf("%s: %s", parent, err)
}
if err := copyDir(parentDir, dir); err != nil {
return err
}
return nil
}
func (d *Driver) dir(id string) string {
return path.Join(d.home, "dir", path.Base(id))
}
func (d *Driver) Remove(id string) error {
if _, err := os.Stat(d.dir(id)); err != nil {
return err
}
return os.RemoveAll(d.dir(id))
}
func (d *Driver) Get(id string) (string, error) {
dir := d.dir(id)
if st, err := os.Stat(dir); err != nil {
return "", err
} else if !st.IsDir() {
return "", fmt.Errorf("%s: not a directory", dir)
}
return dir, nil
}
+163 -51
View File
@@ -6,14 +6,17 @@ import (
"encoding/json"
"fmt"
"github.com/dotcloud/docker/archive"
"github.com/dotcloud/docker/graphdriver"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
)
@@ -59,30 +62,39 @@ func LoadImage(root string) (*Image, error) {
img.Size = int64(size)
}
// Check that the filesystem layer exists
if stat, err := os.Stat(layerPath(root)); err != nil {
if os.IsNotExist(err) {
return nil, fmt.Errorf("Couldn't load image %s: no filesystem layer", img.ID)
}
return nil, err
} else if !stat.IsDir() {
return nil, fmt.Errorf("Couldn't load image %s: %s is not a directory", img.ID, layerPath(root))
}
return img, nil
}
func StoreImage(img *Image, jsonData []byte, layerData archive.Archive, root, rootfs string) error {
func StoreImage(img *Image, jsonData []byte, layerData archive.Archive, root string) error {
// Check that root doesn't already exist
if _, err := os.Stat(root); err == nil {
return fmt.Errorf("Image %s already exists", img.ID)
} else if !os.IsNotExist(err) {
return err
}
// Store the layer
layer := rootfs
layer := layerPath(root)
if err := os.MkdirAll(layer, 0755); err != nil {
return err
}
// If layerData is not nil, unpack it into the new layer
if layerData != nil {
if differ, ok := img.graph.driver.(graphdriver.Differ); ok {
if err := differ.ApplyDiff(img.ID, layerData); err != nil {
return err
}
} else {
start := time.Now()
utils.Debugf("Start untar layer")
if err := archive.ApplyLayer(layer, layerData); err != nil {
return err
}
utils.Debugf("Untar time: %vs", time.Now().Sub(start).Seconds())
start := time.Now()
utils.Debugf("Start untar layer")
if err := archive.Untar(layerData, layer); err != nil {
return err
}
utils.Debugf("Untar time: %vs", time.Now().Sub(start).Seconds())
}
// If raw json is provided, then use it
@@ -97,43 +109,112 @@ func StoreImage(img *Image, jsonData []byte, layerData archive.Archive, root, ro
if err := ioutil.WriteFile(jsonPath(root), jsonData, 0600); err != nil {
return err
}
// Compute and save the size of the rootfs
size, err := utils.TreeSize(rootfs)
if err != nil {
return fmt.Errorf("Error computing size of rootfs %s: %s", img.ID, err)
}
img.Size = size
if err := img.SaveSize(root); err != nil {
return err
return StoreSize(img, root)
}
func StoreSize(img *Image, root string) error {
layer := layerPath(root)
data := make(map[uint64]bool)
var totalSize int64
filepath.Walk(layer, func(path string, fileInfo os.FileInfo, err error) error {
size := fileInfo.Size()
if size == 0 {
return nil
}
inode := fileInfo.Sys().(*syscall.Stat_t).Ino
if _, entryExists := data[inode]; entryExists {
return nil
}
data[inode] = false
totalSize += size
return nil
})
img.Size = totalSize
if err := ioutil.WriteFile(path.Join(root, "layersize"), []byte(strconv.Itoa(int(totalSize))), 0600); err != nil {
return nil
}
return nil
}
// SaveSize stores the current `size` value of `img` in the directory `root`.
func (img *Image) SaveSize(root string) error {
if err := ioutil.WriteFile(path.Join(root, "layersize"), []byte(strconv.Itoa(int(img.Size))), 0600); err != nil {
return fmt.Errorf("Error storing image size in %s/layersize: %s", root, err)
}
return nil
func layerPath(root string) string {
return path.Join(root, "layer")
}
func jsonPath(root string) string {
return path.Join(root, "json")
}
func MountAUFS(ro []string, rw string, target string) error {
// FIXME: Now mount the layers
rwBranch := fmt.Sprintf("%v=rw", rw)
roBranches := ""
for _, layer := range ro {
roBranches += fmt.Sprintf("%v=ro+wh:", layer)
}
branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches)
branches += ",xino=/dev/shm/aufs.xino"
//if error, try to load aufs kernel module
if err := mount("none", target, "aufs", 0, branches); err != nil {
log.Printf("Kernel does not support AUFS, trying to load the AUFS module with modprobe...")
if err := exec.Command("modprobe", "aufs").Run(); err != nil {
return fmt.Errorf("Unable to load the AUFS module")
}
log.Printf("...module loaded.")
if err := mount("none", target, "aufs", 0, branches); err != nil {
return fmt.Errorf("Unable to mount using aufs")
}
}
return nil
}
// TarLayer returns a tar archive of the image's filesystem layer.
func (img *Image) TarLayer(compression archive.Compression) (archive.Archive, error) {
if img.graph == nil {
return nil, fmt.Errorf("Can't load storage driver for unregistered image %s", img.ID)
}
layerPath, err := img.graph.driver.Get(img.ID)
layerPath, err := img.layer()
if err != nil {
return nil, err
}
return archive.Tar(layerPath, compression)
}
func (img *Image) Mount(root, rw string) error {
if mounted, err := Mounted(root); err != nil {
return err
} else if mounted {
return fmt.Errorf("%s is already mounted", root)
}
layers, err := img.layers()
if err != nil {
return err
}
// Create the target directories if they don't exist
if err := os.Mkdir(root, 0755); err != nil && !os.IsExist(err) {
return err
}
if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) {
return err
}
if err := MountAUFS(layers, rw, root); err != nil {
return err
}
return nil
}
func (img *Image) Changes(rw string) ([]Change, error) {
layers, err := img.layers()
if err != nil {
return nil, err
}
return Changes(layers, rw)
}
func ValidateID(id string) error {
if id == "" {
return fmt.Errorf("Image id can't be empty")
@@ -169,6 +250,40 @@ func (img *Image) History() ([]*Image, error) {
return parents, nil
}
// layers returns all the filesystem layers needed to mount an image
// FIXME: @shykes refactor this function with the new error handling
// (I'll do it if I have time tonight, I focus on the rest)
func (img *Image) layers() ([]string, error) {
var (
list []string
e error
)
if err := img.WalkHistory(
func(img *Image) (err error) {
if layer, err := img.layer(); err != nil {
e = err
} else if layer != "" {
list = append(list, layer)
}
return err
},
); err != nil {
return nil, err
} else if e != nil { // Did an error occur inside the handler?
return nil, e
}
if len(list) == 0 {
return nil, fmt.Errorf("No layer found for image %s\n", img.ID)
}
// Inject the dockerinit layer (empty place-holder for mount-binding dockerinit)
dockerinitLayer, err := img.getDockerInitLayer()
if err != nil {
return nil, err
}
return append([]string{dockerinitLayer}, list...), nil
}
func (img *Image) WalkHistory(handler func(*Image) error) (err error) {
currentImg := img
for currentImg != nil {
@@ -195,6 +310,13 @@ func (img *Image) GetParent() (*Image, error) {
return img.graph.Get(img.Parent)
}
func (img *Image) getDockerInitLayer() (string, error) {
if img.graph == nil {
return "", fmt.Errorf("Can't lookup dockerinit layer of unregistered image")
}
return img.graph.getDockerInitLayer()
}
func (img *Image) root() (string, error) {
if img.graph == nil {
return "", fmt.Errorf("Can't lookup root of unregistered image")
@@ -202,6 +324,15 @@ func (img *Image) root() (string, error) {
return img.graph.imageRoot(img.ID), nil
}
// Return the path of an image's layer
func (img *Image) layer() (string, error) {
root, err := img.root()
if err != nil {
return "", err
}
return layerPath(root), nil
}
func (img *Image) getParentsSize(size int64) int64 {
parentImage, err := img.GetParent()
if err != nil || parentImage == nil {
@@ -211,25 +342,6 @@ func (img *Image) getParentsSize(size int64) int64 {
return parentImage.getParentsSize(size)
}
// Depth returns the number of parents for a
// current image
func (img *Image) Depth() (int, error) {
var (
count = 0
parent = img
err error
)
for parent != nil {
count++
parent, err = parent.GetParent()
if err != nil {
return -1, err
}
}
return count, nil
}
// Build an Image object from raw json data
func NewImgJSON(src []byte) (*Image, error) {
ret := &Image{}
+14 -13
View File
@@ -289,7 +289,7 @@ func TestRunDisconnect(t *testing.T) {
setTimeout(t, "Waiting for /bin/cat to exit timed out", 2*time.Second, func() {
container := globalRuntime.List()[0]
container.Wait()
if container.State.Running {
if container.State.IsRunning() {
t.Fatalf("/bin/cat is still running after closing stdin")
}
})
@@ -319,7 +319,7 @@ func TestRunDisconnectTty(t *testing.T) {
for {
// Client disconnect after run -i should keep stdin out in TTY mode
l := globalRuntime.List()
if len(l) == 1 && l[0].State.Running {
if len(l) == 1 && l[0].State.IsRunning() {
break
}
time.Sleep(10 * time.Millisecond)
@@ -329,7 +329,7 @@ func TestRunDisconnectTty(t *testing.T) {
// Client disconnect after run -i should keep stdin out in TTY mode
container := globalRuntime.List()[0]
setTimeout(t, "Read/Write assertion timed out", 2000*time.Second, func() {
setTimeout(t, "Read/Write assertion timed out", 2*time.Second, func() {
if err := assertPipe("hello\n", "hello", stdout, stdinPipe, 15); err != nil {
t.Fatal(err)
}
@@ -345,7 +345,7 @@ func TestRunDisconnectTty(t *testing.T) {
// Give some time to monitor to do his thing
container.WaitTimeout(500 * time.Millisecond)
if !container.State.Running {
if !container.State.IsRunning() {
t.Fatalf("/bin/cat should still be running after closing stdin (tty mode)")
}
}
@@ -454,7 +454,7 @@ func TestRunDetach(t *testing.T) {
})
time.Sleep(500 * time.Millisecond)
if !container.State.Running {
if !container.State.IsRunning() {
t.Fatal("The detached container should be still running")
}
@@ -534,7 +534,7 @@ func TestAttachDetach(t *testing.T) {
})
time.Sleep(500 * time.Millisecond)
if !container.State.Running {
if !container.State.IsRunning() {
t.Fatal("The detached container should be still running")
}
@@ -596,7 +596,7 @@ func TestAttachDetachTruncatedID(t *testing.T) {
})
time.Sleep(500 * time.Millisecond)
if !container.State.Running {
if !container.State.IsRunning() {
t.Fatal("The detached container should be still running")
}
@@ -629,7 +629,7 @@ func TestAttachDisconnect(t *testing.T) {
setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() {
for {
l := globalRuntime.List()
if len(l) == 1 && l[0].State.Running {
if len(l) == 1 && l[0].State.IsRunning() {
break
}
time.Sleep(10 * time.Millisecond)
@@ -665,7 +665,7 @@ func TestAttachDisconnect(t *testing.T) {
// We closed stdin, expect /bin/cat to still be running
// Wait a little bit to make sure container.monitor() did his thing
err := container.WaitTimeout(500 * time.Millisecond)
if err == nil || !container.State.Running {
if err == nil || !container.State.IsRunning() {
t.Fatalf("/bin/cat is not running after closing stdin")
}
@@ -840,12 +840,13 @@ func TestImagesTree(t *testing.T) {
t.Fatal(err)
}
cmdOutput := string(cmdOutputBytes)
regexpStrings := []string{
fmt.Sprintf("└─%s Size: (\\d+.\\d+ MB) \\(virtual \\d+.\\d+ MB\\) Tags: %s:latest", unitTestImageIDShort, unitTestImageName),
"(?m) └─[0-9a-f]+.*",
"(?m) └─[0-9a-f]+.*",
"(?m) └─[0-9a-f]+.*",
fmt.Sprintf("(?m)^ └─%s Size: \\d+.\\d+ MB \\(virtual \\d+.\\d+ MB\\) Tags: test:latest", utils.TruncateID(image.ID)),
"(?m)^ └─[0-9a-f]+",
"(?m)^ └─[0-9a-f]+",
"(?m)^ └─[0-9a-f]+",
fmt.Sprintf(" └─%s Size: \\d+ B \\(virtual \\d+.\\d+ MB\\) Tags: test:latest", utils.TruncateID(image.ID)),
}
compiledRegexps := []*regexp.Regexp{}
+46 -41
View File
@@ -172,7 +172,7 @@ func TestDiff(t *testing.T) {
// Commit the container
img, err := runtime.Commit(container1, "", "", "unit test commited image - diff", "", nil)
if err != nil {
t.Fatal(err)
t.Error(err)
}
// Create a new container from the commited image
@@ -224,13 +224,13 @@ func TestCommitAutoRun(t *testing.T) {
container1, _, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t)
defer runtime.Destroy(container1)
if container1.State.Running {
if container1.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
if err := container1.Run(); err != nil {
t.Fatal(err)
}
if container1.State.Running {
if container1.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
@@ -280,13 +280,13 @@ func TestCommitRun(t *testing.T) {
container1, _, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "echo hello > /world"}, t)
defer runtime.Destroy(container1)
if container1.State.Running {
if container1.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
if err := container1.Run(); err != nil {
t.Fatal(err)
}
if container1.State.Running {
if container1.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
@@ -330,6 +330,11 @@ func TestCommitRun(t *testing.T) {
}
func TestStart(t *testing.T) {
_, err1 := os.Stat("/sys/fs/cgroup/cpuacct,cpu")
_, err2 := os.Stat("/sys/fs/cgroup/cpu,cpuacct")
if err1 == nil || err2 == nil {
t.Skip("Fixme. Setting cpu cgroup shares doesn't work in dind on a Fedora host. The lxc utils are confused by the cpu,cpuacct mount.")
}
runtime := mkRuntime(t)
defer nuke(runtime)
container, _, _ := mkContainer(runtime, []string{"-m", "33554432", "-c", "1000", "-i", "_", "/bin/cat"}, t)
@@ -347,7 +352,7 @@ func TestStart(t *testing.T) {
// Give some time to the process to start
container.WaitTimeout(500 * time.Millisecond)
if !container.State.Running {
if !container.State.IsRunning() {
t.Errorf("Container should be running")
}
if err := container.Start(); err == nil {
@@ -365,13 +370,13 @@ func TestRun(t *testing.T) {
container, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t)
defer runtime.Destroy(container)
if container.State.Running {
if container.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
if err := container.Run(); err != nil {
t.Fatal(err)
}
if container.State.Running {
if container.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
}
@@ -395,7 +400,7 @@ func TestOutput(t *testing.T) {
t.Fatal(err)
}
if string(output) != "foobar" {
t.Error(string(output))
t.Fatalf("%s != %s", string(output), "foobar")
}
}
@@ -416,8 +421,8 @@ func TestContainerNetwork(t *testing.T) {
if err := container.Run(); err != nil {
t.Fatal(err)
}
if container.State.ExitCode != 0 {
t.Errorf("Unexpected ping 127.0.0.1 exit code %d (expected 0)", container.State.ExitCode)
if code := container.State.GetExitCode(); code != 0 {
t.Fatalf("Unexpected ping 127.0.0.1 exit code %d (expected 0)", code)
}
}
@@ -441,7 +446,7 @@ func TestKillDifferentUser(t *testing.T) {
// there is a side effect I'm not seeing.
// defer container.stdin.Close()
if container.State.Running {
if container.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
if err := container.Start(); err != nil {
@@ -449,7 +454,7 @@ func TestKillDifferentUser(t *testing.T) {
}
setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() {
for !container.State.Running {
for !container.State.IsRunning() {
time.Sleep(10 * time.Millisecond)
}
})
@@ -466,11 +471,11 @@ func TestKillDifferentUser(t *testing.T) {
t.Fatal(err)
}
if container.State.Running {
if container.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
container.Wait()
if container.State.Running {
if container.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
// Try stopping twice
@@ -528,7 +533,7 @@ func TestKill(t *testing.T) {
}
defer runtime.Destroy(container)
if container.State.Running {
if container.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
if err := container.Start(); err != nil {
@@ -538,17 +543,17 @@ func TestKill(t *testing.T) {
// Give some time to lxc to spawn the process
container.WaitTimeout(500 * time.Millisecond)
if !container.State.Running {
if !container.State.IsRunning() {
t.Errorf("Container should be running")
}
if err := container.Kill(); err != nil {
t.Fatal(err)
}
if container.State.Running {
if container.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
container.Wait()
if container.State.Running {
if container.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
// Try stopping twice
@@ -563,7 +568,7 @@ func TestExitCode(t *testing.T) {
trueContainer, _, err := runtime.Create(&docker.Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/true", ""},
Cmd: []string{"/bin/true"},
}, "")
if err != nil {
t.Fatal(err)
@@ -572,13 +577,13 @@ func TestExitCode(t *testing.T) {
if err := trueContainer.Run(); err != nil {
t.Fatal(err)
}
if trueContainer.State.ExitCode != 0 {
t.Errorf("Unexpected exit code %d (expected 0)", trueContainer.State.ExitCode)
if code := trueContainer.State.GetExitCode(); code != 0 {
t.Fatalf("Unexpected exit code %d (expected 0)", code)
}
falseContainer, _, err := runtime.Create(&docker.Config{
Image: GetTestImage(runtime).ID,
Cmd: []string{"/bin/false", ""},
Cmd: []string{"/bin/false"},
}, "")
if err != nil {
t.Fatal(err)
@@ -587,8 +592,8 @@ func TestExitCode(t *testing.T) {
if err := falseContainer.Run(); err != nil {
t.Fatal(err)
}
if falseContainer.State.ExitCode != 1 {
t.Errorf("Unexpected exit code %d (expected 1)", falseContainer.State.ExitCode)
if code := falseContainer.State.GetExitCode(); code != 1 {
t.Fatalf("Unexpected exit code %d (expected 1)", code)
}
}
@@ -736,7 +741,7 @@ func TestUser(t *testing.T) {
}
defer runtime.Destroy(container)
output, err = container.Output()
if err != nil || container.State.ExitCode != 0 {
if code := container.State.GetExitCode(); err != nil || code != 0 {
t.Fatal(err)
}
if !strings.Contains(string(output), "uid=0(root) gid=0(root)") {
@@ -752,12 +757,12 @@ func TestUser(t *testing.T) {
},
"",
)
if err != nil || container.State.ExitCode != 0 {
if code := container.State.GetExitCode(); err != nil || code != 0 {
t.Fatal(err)
}
defer runtime.Destroy(container)
output, err = container.Output()
if err != nil || container.State.ExitCode != 0 {
if code := container.State.GetExitCode(); err != nil || code != 0 {
t.Fatal(err)
}
if !strings.Contains(string(output), "uid=0(root) gid=0(root)") {
@@ -780,8 +785,8 @@ func TestUser(t *testing.T) {
output, err = container.Output()
if err != nil {
t.Fatal(err)
} else if container.State.ExitCode != 0 {
t.Fatalf("Container exit code is invalid: %d\nOutput:\n%s\n", container.State.ExitCode, output)
} else if code := container.State.GetExitCode(); code != 0 {
t.Fatalf("Container exit code is invalid: %d\nOutput:\n%s\n", code, output)
}
if !strings.Contains(string(output), "uid=1(daemon) gid=1(daemon)") {
t.Error(string(output))
@@ -801,7 +806,7 @@ func TestUser(t *testing.T) {
}
defer runtime.Destroy(container)
output, err = container.Output()
if err != nil || container.State.ExitCode != 0 {
if code := container.State.GetExitCode(); err != nil || code != 0 {
t.Fatal(err)
}
if !strings.Contains(string(output), "uid=1(daemon) gid=1(daemon)") {
@@ -822,7 +827,7 @@ func TestUser(t *testing.T) {
}
defer runtime.Destroy(container)
output, err = container.Output()
if container.State.ExitCode == 0 {
if container.State.GetExitCode() == 0 {
t.Fatal("Starting container with wrong uid should fail but it passed.")
}
}
@@ -866,10 +871,10 @@ func TestMultipleContainers(t *testing.T) {
container2.WaitTimeout(250 * time.Millisecond)
// If we are here, both containers should be running
if !container1.State.Running {
if !container1.State.IsRunning() {
t.Fatal("Container not running")
}
if !container2.State.Running {
if !container2.State.IsRunning() {
t.Fatal("Container not running")
}
@@ -1171,13 +1176,13 @@ func TestCopyVolumeUidGid(t *testing.T) {
container1, _, _ := mkContainer(r, []string{"_", "/bin/sh", "-c", "mkdir -p /hello && touch /hello/test.txt && chown daemon.daemon /hello"}, t)
defer r.Destroy(container1)
if container1.State.Running {
if container1.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
if err := container1.Run(); err != nil {
t.Fatal(err)
}
if container1.State.Running {
if container1.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
@@ -1205,13 +1210,13 @@ func TestCopyVolumeContent(t *testing.T) {
container1, _, _ := mkContainer(r, []string{"_", "/bin/sh", "-c", "mkdir -p /hello/local && echo hello > /hello/local/world"}, t)
defer r.Destroy(container1)
if container1.State.Running {
if container1.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
if err := container1.Run(); err != nil {
t.Fatal(err)
}
if container1.State.Running {
if container1.State.IsRunning() {
t.Errorf("Container shouldn't be running")
}
@@ -1661,17 +1666,17 @@ func TestRestartGhost(t *testing.T) {
},
"",
)
if err != nil {
t.Fatal(err)
}
if err := container.Kill(); err != nil {
t.Fatal(err)
}
container.State.Ghost = true
_, err = container.Output()
container.State.SetGhost(true)
_, err = container.Output()
if err != nil {
t.Fatal(err)
}
+11 -13
View File
@@ -2,7 +2,6 @@ package docker
import (
"github.com/dotcloud/docker"
"github.com/dotcloud/docker/graphdriver"
"io/ioutil"
"os"
"path"
@@ -10,10 +9,8 @@ import (
)
func TestMount(t *testing.T) {
graph, driver := tempGraph(t)
graph := tempGraph(t)
defer os.RemoveAll(graph.Root)
defer driver.Cleanup()
archive, err := fakeTar()
if err != nil {
t.Fatal(err)
@@ -35,25 +32,26 @@ func TestMount(t *testing.T) {
if err := os.MkdirAll(rw, 0700); err != nil {
t.Fatal(err)
}
if _, err := driver.Get(image.ID); err != nil {
if err := image.Mount(rootfs, rw); err != nil {
t.Fatal(err)
}
// FIXME: test for mount contents
defer func() {
if err := docker.Unmount(rootfs); err != nil {
t.Error(err)
}
}()
}
//FIXME: duplicate
func tempGraph(t *testing.T) (*docker.Graph, graphdriver.Driver) {
func tempGraph(t *testing.T) *docker.Graph {
tmp, err := ioutil.TempDir("", "docker-graph-")
if err != nil {
t.Fatal(err)
}
driver, err := graphdriver.New(tmp)
graph, err := docker.NewGraph(tmp)
if err != nil {
t.Fatal(err)
}
graph, err := docker.NewGraph(tmp, driver)
if err != nil {
t.Fatal(err)
}
return graph, driver
return graph
}
+9 -9
View File
@@ -419,7 +419,7 @@ func startEchoServerContainer(t *testing.T, proto string) (*docker.Runtime, *doc
}
setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() {
for !container.State.Running {
for !container.State.IsRunning() {
time.Sleep(10 * time.Millisecond)
}
})
@@ -533,7 +533,7 @@ func TestRestore(t *testing.T) {
t.Fatal(err)
}
if !container2.State.Running {
if !container2.State.IsRunning() {
t.Fatalf("Container %v should appear as running but isn't", container2.ID)
}
@@ -543,7 +543,7 @@ func TestRestore(t *testing.T) {
if err := container2.WaitTimeout(2 * time.Second); err != nil {
t.Fatal(err)
}
container2.State.Running = true
container2.State.SetRunning(42)
container2.ToDisk()
if len(runtime1.List()) != 2 {
@@ -553,7 +553,7 @@ func TestRestore(t *testing.T) {
t.Fatal(err)
}
if !container2.State.Running {
if !container2.State.IsRunning() {
t.Fatalf("Container %v should appear as running but isn't", container2.ID)
}
@@ -577,7 +577,7 @@ func TestRestore(t *testing.T) {
}
runningCount := 0
for _, c := range runtime2.List() {
if c.State.Running {
if c.State.IsRunning() {
t.Errorf("Running container found: %v (%v)", c.ID, c.Path)
runningCount++
}
@@ -592,7 +592,7 @@ func TestRestore(t *testing.T) {
if err := container3.Run(); err != nil {
t.Fatal(err)
}
container2.State.Running = false
container2.State.SetStopped(0)
}
func TestReloadContainerLinks(t *testing.T) {
@@ -638,11 +638,11 @@ func TestReloadContainerLinks(t *testing.T) {
t.Fatal(err)
}
if !container2.State.Running {
if !container2.State.IsRunning() {
t.Fatalf("Container %v should appear as running but isn't", container2.ID)
}
if !container1.State.Running {
if !container1.State.IsRunning() {
t.Fatalf("Container %s should appear as running but isn't", container1.ID)
}
@@ -669,7 +669,7 @@ func TestReloadContainerLinks(t *testing.T) {
}
runningCount := 0
for _, c := range runtime2.List() {
if c.State.Running {
if c.State.IsRunning() {
runningCount++
}
}
+11 -3
View File
@@ -109,7 +109,7 @@ func TestCreateRmVolumes(t *testing.T) {
srv := mkServerFromEngine(eng, t)
defer mkRuntimeFromEngine(eng, t).Nuke()
config, hostConfig, _, err := docker.ParseRun([]string{"-v", "/srv", unitTestImageID, "echo test"}, nil)
config, hostConfig, _, err := docker.ParseRun([]string{"-v", "/srv", unitTestImageID, "echo", "test"}, nil)
if err != nil {
t.Fatal(err)
}
@@ -164,7 +164,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) {
srv := mkServerFromEngine(eng, t)
defer mkRuntimeFromEngine(eng, t).Nuke()
config, hostConfig, _, err := docker.ParseRun([]string{unitTestImageID, "/bin/cat"}, nil)
config, hostConfig, _, err := docker.ParseRun([]string{"-i", unitTestImageID, "/bin/cat"}, nil)
if err != nil {
t.Fatal(err)
}
@@ -240,7 +240,7 @@ func TestRmi(t *testing.T) {
t.Fatal(err)
}
config, hostConfig, _, err := docker.ParseRun([]string{unitTestImageID, "echo test"}, nil)
config, hostConfig, _, err := docker.ParseRun([]string{unitTestImageID, "echo", "test"}, nil)
if err != nil {
t.Fatal(err)
}
@@ -256,6 +256,10 @@ func TestRmi(t *testing.T) {
t.Fatal(err)
}
if _, err := srv.ContainerWait(containerID); err != nil {
t.Fatal(err)
}
imageID, err := srv.ContainerCommit(containerID, "test", "", "", "", nil)
if err != nil {
t.Fatal(err)
@@ -277,6 +281,10 @@ func TestRmi(t *testing.T) {
t.Fatal(err)
}
if _, err := srv.ContainerWait(containerID); err != nil {
t.Fatal(err)
}
_, err = srv.ContainerCommit(containerID, "test", "", "", "", nil)
if err != nil {
t.Fatal(err)
+1 -1
View File
@@ -111,7 +111,7 @@ func containerKill(eng *engine.Engine, id string, t utils.Fataler) {
}
func containerRunning(eng *engine.Engine, id string, t utils.Fataler) bool {
return getContainer(eng, id, t).State.Running
return getContainer(eng, id, t).State.IsRunning()
}
func containerAssertExists(eng *engine.Engine, id string, t utils.Fataler) {
+1 -1
View File
@@ -21,7 +21,7 @@ func NewLink(parent, child *Container, name, bridgeInterface string) (*Link, err
if parent.ID == child.ID {
return nil, fmt.Errorf("Cannot link to self: %s == %s", parent.ID, child.ID)
}
if !child.State.Running {
if !child.State.IsRunning() {
return nil, fmt.Errorf("Cannot link to a non running container: %s AS %s", child.Name, name)
}
+19 -3
View File
@@ -1,11 +1,13 @@
package aufs
package docker
import (
"fmt"
"github.com/dotcloud/docker/utils"
"os"
"os/exec"
"path/filepath"
"syscall"
"time"
)
func Unmount(target string) error {
@@ -15,7 +17,22 @@ func Unmount(target string) error {
if err := syscall.Unmount(target, 0); err != nil {
return err
}
return nil
// Even though we just unmounted the filesystem, AUFS will prevent deleting the mntpoint
// for some time. We'll just keep retrying until it succeeds.
for retries := 0; retries < 1000; retries++ {
err := os.Remove(target)
if err == nil {
// rm mntpoint succeeded
return nil
}
if os.IsNotExist(err) {
// mntpoint doesn't exist anymore. Success.
return nil
}
// fmt.Printf("(%v) Remove %v returned: %v\n", retries, target, err)
time.Sleep(10 * time.Millisecond)
}
return fmt.Errorf("Umount: Failed to umount %v", target)
}
func Mounted(mountpoint string) (bool, error) {
@@ -32,6 +49,5 @@ func Mounted(mountpoint string) (bool, error) {
}
mntpointSt := mntpoint.Sys().(*syscall.Stat_t)
parentSt := parent.Sys().(*syscall.Stat_t)
return mntpointSt.Dev != parentSt.Dev, nil
}
@@ -1,4 +1,4 @@
package aufs
package docker
import "errors"
@@ -1,4 +1,4 @@
package aufs
package docker
import "syscall"
+25 -164
View File
@@ -5,12 +5,7 @@ import (
"container/list"
"database/sql"
"fmt"
"github.com/dotcloud/docker/archive"
"github.com/dotcloud/docker/graphdb"
"github.com/dotcloud/docker/graphdriver"
"github.com/dotcloud/docker/graphdriver/aufs"
_ "github.com/dotcloud/docker/graphdriver/devmapper"
_ "github.com/dotcloud/docker/graphdriver/dummy"
"github.com/dotcloud/docker/gograph"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
@@ -24,9 +19,6 @@ import (
"time"
)
// Set the max depth to the aufs restriction
const MaxImageDepth = 42
var defaultDns = []string{"8.8.8.8", "8.8.4.4"}
type Capabilities struct {
@@ -47,8 +39,7 @@ type Runtime struct {
volumes *Graph
srv *Server
config *DaemonConfig
containerGraph *graphdb.Database
driver graphdriver.Driver
containerGraph *gograph.Database
}
// List returns an array of all containers registered in the runtime.
@@ -109,8 +100,8 @@ func (runtime *Runtime) load(id string) (*Container, error) {
if container.ID != id {
return container, fmt.Errorf("Container %s is stored at %s", container.ID, id)
}
if container.State.Running {
container.State.Ghost = true
if container.State.IsRunning() {
container.State.SetGhost(true)
}
return container, nil
}
@@ -127,13 +118,6 @@ func (runtime *Runtime) Register(container *Container) error {
return err
}
// Get the root filesystem from the driver
rootfs, err := runtime.driver.Get(container.ID)
if err != nil {
return fmt.Errorf("Error getting container filesystem %s from driver %s: %s", container.ID, runtime.driver, err)
}
container.rootfs = rootfs
container.runtime = runtime
// Attach to stdout and stderr
@@ -152,7 +136,7 @@ func (runtime *Runtime) Register(container *Container) error {
// FIXME: if the container is supposed to be running but is not, auto restart it?
// if so, then we need to restart monitor and init a new lock
// If the container is supposed to be running, make sure of it
if container.State.Running {
if container.State.IsRunning() {
output, err := exec.Command("lxc-info", "-n", container.ID).CombinedOutput()
if err != nil {
return err
@@ -161,14 +145,14 @@ func (runtime *Runtime) Register(container *Container) error {
utils.Debugf("Container %s was supposed to be running be is not.", container.ID)
if runtime.config.AutoRestart {
utils.Debugf("Restarting")
container.State.Ghost = false
container.State.setStopped(0)
container.State.SetGhost(false)
container.State.SetStopped(0)
if err := container.Start(); err != nil {
return err
}
} else {
utils.Debugf("Marking as stopped")
container.State.setStopped(-127)
container.State.SetStopped(-127)
if err := container.ToDisk(); err != nil {
return err
}
@@ -232,8 +216,12 @@ func (runtime *Runtime) Destroy(container *Container) error {
return err
}
if err := runtime.driver.Remove(container.ID); err != nil {
return fmt.Errorf("Driver %s failed to remove root filesystem %s: %s", runtime.driver, container.ID, err)
if mounted, err := container.Mounted(); err != nil {
return err
} else if mounted {
if err := container.Unmount(); err != nil {
return fmt.Errorf("Unable to unmount container %v: %v", container.ID, err)
}
}
if _, err := runtime.containerGraph.Purge(container.ID); err != nil {
@@ -259,7 +247,6 @@ func (runtime *Runtime) restore() error {
return err
}
containers := make(map[string]*Container)
currentDriver := runtime.driver.String()
for i, v := range dir {
id := v.Name()
@@ -271,14 +258,8 @@ func (runtime *Runtime) restore() error {
utils.Errorf("Failed to load container %v: %v", id, err)
continue
}
// Ignore the container if it does not support the current driver being used by the graph
if container.Driver == "" && currentDriver == "aufs" || container.Driver == currentDriver {
utils.Debugf("Loaded container %v", container.ID)
containers[container.ID] = container
} else {
utils.Debugf("Cannot load container %s because it was created with another graph driver.", container.ID)
}
utils.Debugf("Loaded container %v", container.ID)
containers[container.ID] = container
}
register := func(container *Container) {
@@ -363,17 +344,6 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin
return nil, nil, err
}
// We add 2 layers to the depth because the container's rw and
// init layer add to the restriction
depth, err := img.Depth()
if err != nil {
return nil, nil, err
}
if depth+2 >= MaxImageDepth {
return nil, nil, fmt.Errorf("Cannot create container with more than %d parents", MaxImageDepth)
}
checkDeprecatedExpose := func(config *Config) bool {
if config != nil {
if config.PortSpecs != nil {
@@ -461,7 +431,6 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin
// FIXME: do we need to store this in the container?
SysInitPath: sysInitPath,
Name: name,
Driver: runtime.driver.String(),
}
container.root = runtime.containerRoot(container.ID)
// Step 1: create the container directory.
@@ -470,21 +439,6 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin
return nil, nil, err
}
initID := fmt.Sprintf("%s-init", container.ID)
if err := runtime.driver.Create(initID, img.ID); err != nil {
return nil, nil, err
}
initPath, err := runtime.driver.Get(initID)
if err != nil {
return nil, nil, err
}
if err := setupInitLayer(initPath); err != nil {
return nil, nil, err
}
if err := runtime.driver.Create(container.ID, initID); err != nil {
return nil, nil, err
}
resolvConf, err := utils.GetResolvConf()
if err != nil {
return nil, nil, err
@@ -595,7 +549,7 @@ func (runtime *Runtime) Children(name string) (map[string]*Container, error) {
}
children := make(map[string]*Container)
err = runtime.containerGraph.Walk(name, func(p string, e *graphdb.Entity) error {
err = runtime.containerGraph.Walk(name, func(p string, e *gograph.Entity) error {
c := runtime.Get(e.ID())
if c == nil {
return fmt.Errorf("Could not get container for name %s and id %s", e.ID(), p)
@@ -630,44 +584,20 @@ func NewRuntime(config *DaemonConfig) (*Runtime, error) {
}
func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
// Set the default driver
graphdriver.DefaultDriver = config.GraphDriver
// Load storage driver
driver, err := graphdriver.New(config.Root)
if err != nil {
return nil, err
}
utils.Debugf("Using graph driver %s", driver)
runtimeRepo := path.Join(config.Root, "containers")
if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) {
return nil, err
}
if ad, ok := driver.(*aufs.Driver); ok {
if err := ad.Migrate(config.Root, setupInitLayer); err != nil {
return nil, err
}
}
if err := linkLxcStart(config.Root); err != nil {
return nil, err
}
g, err := NewGraph(path.Join(config.Root, "graph"), driver)
g, err := NewGraph(path.Join(config.Root, "graph"))
if err != nil {
return nil, err
}
// We don't want to use a complex driver like aufs or devmapper
// for volumes, just a plain filesystem
volumesDriver, err := graphdriver.GetDriver("dummy", config.Root)
if err != nil {
return nil, err
}
volumes, err := NewGraph(path.Join(config.Root, "volumes"), volumesDriver)
volumes, err := NewGraph(path.Join(config.Root, "volumes"))
if err != nil {
return nil, err
}
@@ -683,20 +613,20 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
return nil, err
}
graphdbPath := path.Join(config.Root, "linkgraph.db")
gographPath := path.Join(config.Root, "linkgraph.db")
initDatabase := false
if _, err := os.Stat(graphdbPath); err != nil {
if _, err := os.Stat(gographPath); err != nil {
if os.IsNotExist(err) {
initDatabase = true
} else {
return nil, err
}
}
conn, err := sql.Open("sqlite3", graphdbPath)
conn, err := sql.Open("sqlite3", gographPath)
if err != nil {
return nil, err
}
graph, err := graphdb.NewDatabase(conn, initDatabase)
graph, err := gograph.NewDatabase(conn, initDatabase)
if err != nil {
return nil, err
}
@@ -712,7 +642,6 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
volumes: volumes,
config: config,
containerGraph: graph,
driver: driver,
}
if err := runtime.restore(); err != nil {
@@ -722,76 +651,8 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
}
func (runtime *Runtime) Close() error {
errorsStrings := []string{}
if err := runtime.networkManager.Close(); err != nil {
utils.Errorf("runtime.networkManager.Close(): %s", err.Error())
errorsStrings = append(errorsStrings, err.Error())
}
if err := runtime.driver.Cleanup(); err != nil {
utils.Errorf("runtime.driver.Cleanup(): %s", err.Error())
errorsStrings = append(errorsStrings, err.Error())
}
if err := runtime.containerGraph.Close(); err != nil {
utils.Errorf("runtime.containerGraph.Close(): %s", err.Error())
errorsStrings = append(errorsStrings, err.Error())
}
if len(errorsStrings) > 0 {
return fmt.Errorf("%s", strings.Join(errorsStrings, ", "))
}
return nil
}
func (runtime *Runtime) Mount(container *Container) error {
dir, err := runtime.driver.Get(container.ID)
if err != nil {
return fmt.Errorf("Error getting container %s from driver %s: %s", container.ID, runtime.driver, err)
}
if container.rootfs == "" {
container.rootfs = dir
} else if container.rootfs != dir {
return fmt.Errorf("Error: driver %s is returning inconsistent paths for container %s ('%s' then '%s')",
runtime.driver, container.ID, container.rootfs, dir)
}
return nil
}
func (runtime *Runtime) Unmount(container *Container) error {
// FIXME: Unmount is deprecated because drivers are responsible for mounting
// and unmounting when necessary. Use driver.Remove() instead.
return nil
}
func (runtime *Runtime) Changes(container *Container) ([]archive.Change, error) {
if differ, ok := runtime.driver.(graphdriver.Differ); ok {
return differ.Changes(container.ID)
}
cDir, err := runtime.driver.Get(container.ID)
if err != nil {
return nil, fmt.Errorf("Error getting container rootfs %s from driver %s: %s", container.ID, container.runtime.driver, err)
}
initDir, err := runtime.driver.Get(container.ID + "-init")
if err != nil {
return nil, fmt.Errorf("Error getting container init rootfs %s from driver %s: %s", container.ID, container.runtime.driver, err)
}
return archive.ChangesDirs(cDir, initDir)
}
func (runtime *Runtime) Diff(container *Container) (archive.Archive, error) {
if differ, ok := runtime.driver.(graphdriver.Differ); ok {
return differ.Diff(container.ID)
}
changes, err := runtime.Changes(container)
if err != nil {
return nil, err
}
cDir, err := runtime.driver.Get(container.ID)
if err != nil {
return nil, fmt.Errorf("Error getting container rootfs %s from driver %s: %s", container.ID, container.runtime.driver, err)
}
return archive.ExportChanges(cDir, changes)
runtime.networkManager.Close()
return runtime.containerGraph.Close()
}
// Nuke kills all containers then removes all content
+256 -33
View File
@@ -8,7 +8,7 @@ import (
"github.com/dotcloud/docker/archive"
"github.com/dotcloud/docker/auth"
"github.com/dotcloud/docker/engine"
"github.com/dotcloud/docker/graphdb"
"github.com/dotcloud/docker/gograph"
"github.com/dotcloud/docker/registry"
"github.com/dotcloud/docker/utils"
"io"
@@ -197,6 +197,222 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
return fmt.Errorf("No such container: %s", name)
}
// ImageExport exports all images with the given tag. All versions
// containing the same tag are exported. The resulting output is an
// uncompressed tar ball.
// name is the set of tags to export.
// out is the writer where the images are written to.
func (srv *Server) ImageExport(name string, out io.Writer) error {
// get image json
tempdir, err := ioutil.TempDir("", "docker-export-")
if err != nil {
return err
}
defer os.RemoveAll(tempdir)
utils.Debugf("Serializing %s", name)
rootRepo, err := srv.runtime.repositories.Get(name)
if err != nil {
return err
}
if rootRepo != nil {
for _, id := range rootRepo {
image, err := srv.ImageInspect(id)
if err != nil {
return err
}
if err := srv.exportImage(image, tempdir); err != nil {
return err
}
}
// write repositories
rootRepoMap := map[string]Repository{}
rootRepoMap[name] = rootRepo
rootRepoJson, _ := json.Marshal(rootRepoMap)
if err := ioutil.WriteFile(path.Join(tempdir, "repositories"), rootRepoJson, os.ModeAppend); err != nil {
return err
}
} else {
image, err := srv.ImageInspect(name)
if err != nil {
return err
}
if err := srv.exportImage(image, tempdir); err != nil {
return err
}
}
fs, err := archive.Tar(tempdir, archive.Uncompressed)
if err != nil {
return err
}
if _, err := io.Copy(out, fs); err != nil {
return err
}
return nil
}
func (srv *Server) exportImage(image *Image, tempdir string) error {
for i := image; i != nil; {
// temporary directory
tmpImageDir := path.Join(tempdir, i.ID)
if err := os.Mkdir(tmpImageDir, os.ModeDir); err != nil {
return err
}
var version = "1.0"
var versionBuf = []byte(version)
if err := ioutil.WriteFile(path.Join(tmpImageDir, "VERSION"), versionBuf, os.ModeAppend); err != nil {
return err
}
// serialize json
b, err := json.Marshal(i)
if err != nil {
return err
}
if err := ioutil.WriteFile(path.Join(tmpImageDir, "json"), b, os.ModeAppend); err != nil {
return err
}
// serialize filesystem
fs, err := archive.Tar(path.Join(srv.runtime.graph.Root, i.ID, "layer"), archive.Uncompressed)
if err != nil {
return err
}
fsTar, err := os.Create(path.Join(tmpImageDir, "layer.tar"))
if err != nil {
return err
}
if _, err = io.Copy(fsTar, fs); err != nil {
return err
}
fsTar.Close()
// find parent
if i.Parent != "" {
i, err = srv.ImageInspect(i.Parent)
if err != nil {
return err
}
} else {
i = nil
}
}
return nil
}
// Loads a set of images into the repository. This is the complementary of ImageExport.
// The input stream is an uncompressed tar ball containing images and metadata.
func (srv *Server) ImageLoad(in io.Reader) error {
tmpImageDir, err := ioutil.TempDir("", "docker-import-")
if err != nil {
return err
}
defer os.RemoveAll(tmpImageDir)
var (
repoTarFile = path.Join(tmpImageDir, "repo.tar")
repoDir = path.Join(tmpImageDir, "repo")
)
tarFile, err := os.Create(repoTarFile)
if err != nil {
return err
}
if _, err := io.Copy(tarFile, in); err != nil {
return err
}
tarFile.Close()
repoFile, err := os.Open(repoTarFile)
if err != nil {
return err
}
if err := os.Mkdir(repoDir, os.ModeDir); err != nil {
return err
}
if err := archive.Untar(repoFile, repoDir); err != nil {
return err
}
dirs, err := ioutil.ReadDir(repoDir)
if err != nil {
return err
}
for _, d := range dirs {
if d.IsDir() {
if err := srv.recursiveLoad(d.Name(), tmpImageDir); err != nil {
return err
}
}
}
repositoriesJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", "repositories"))
if err == nil {
repositories := map[string]Repository{}
if err := json.Unmarshal(repositoriesJson, &repositories); err != nil {
return err
}
for imageName, tagMap := range repositories {
for tag, address := range tagMap {
if err := srv.runtime.repositories.Set(imageName, tag, address, true); err != nil {
return err
}
}
}
} else if !os.IsNotExist(err) {
return err
}
return nil
}
func (srv *Server) recursiveLoad(address, tmpImageDir string) error {
if _, err := srv.ImageInspect(address); err != nil {
utils.Debugf("Loading %s", address)
imageJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", address, "json"))
if err != nil {
return err
utils.Debugf("Error reading json", err)
}
layer, err := os.Open(path.Join(tmpImageDir, "repo", address, "layer.tar"))
if err != nil {
utils.Debugf("Error reading embedded tar", err)
return err
}
img, err := NewImgJSON(imageJson)
if err != nil {
utils.Debugf("Error unmarshalling json", err)
return err
}
if img.Parent != "" {
if !srv.runtime.graph.Exists(img.Parent) {
if err := srv.recursiveLoad(img.Parent, tmpImageDir); err != nil {
return err
}
}
}
if err := srv.runtime.graph.Register(imageJson, layer, img); err != nil {
return err
}
}
utils.Debugf("Completed processing %s", address)
return nil
}
func (srv *Server) ImagesSearch(term string) ([]registry.SearchResult, error) {
r, err := registry.NewRegistry(srv.runtime.config.Root, nil, srv.HTTPRequestFactory(nil))
if err != nil {
@@ -377,8 +593,6 @@ func (srv *Server) DockerInfo() *APIInfo {
return &APIInfo{
Containers: len(srv.runtime.List()),
Images: imgcount,
Driver: srv.runtime.driver.String(),
DriverStatus: srv.runtime.driver.Status(),
MemoryLimit: srv.runtime.capabilities.MemoryLimit,
SwapLimit: srv.runtime.capabilities.SwapLimit,
IPv4Forwarding: !srv.runtime.capabilities.IPv4ForwardingDisabled,
@@ -461,7 +675,7 @@ func (srv *Server) ContainerTop(name, psArgs string) (*APITop, error) {
return nil, fmt.Errorf("No such container: %s", name)
}
func (srv *Server) ContainerChanges(name string) ([]archive.Change, error) {
func (srv *Server) ContainerChanges(name string) ([]Change, error) {
if container := srv.runtime.Get(name); container != nil {
return container.Changes()
}
@@ -473,18 +687,21 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API
var displayed int
out := []APIContainers{}
names := map[string][]string{}
srv.runtime.containerGraph.Walk("/", func(p string, e *gograph.Entity) error {
names[e.ID()] = append(names[e.ID()], p)
return nil
}, -1)
for _, container := range srv.runtime.List() {
if !container.State.Running && !all && n == -1 && since == "" && before == "" {
if !container.State.IsRunning() && !all && n == -1 && since == "" && before == "" {
continue
}
if before != "" {
if before != "" && !foundBefore {
if container.ID == before || utils.TruncateID(container.ID) == before {
foundBefore = true
continue
}
if !foundBefore {
continue
}
continue
}
if displayed == n {
break
@@ -493,25 +710,17 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API
break
}
displayed++
c := createAPIContainer(container, size, srv.runtime)
c := createAPIContainer(names[container.ID], container, size, srv.runtime)
out = append(out, c)
}
return out
}
func createAPIContainer(container *Container, size bool, runtime *Runtime) APIContainers {
func createAPIContainer(names []string, container *Container, size bool, runtime *Runtime) APIContainers {
c := APIContainers{
ID: container.ID,
}
names := []string{}
runtime.containerGraph.Walk("/", func(p string, e *graphdb.Entity) error {
if e.ID() == container.ID {
names = append(names, p)
}
return nil
}, -1)
c.Names = names
c.Image = runtime.repositories.ImageName(container.Image)
c.Command = fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " "))
c.Created = container.Created.Unix()
@@ -547,12 +756,11 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin
if err != nil {
return err
}
out.Write(sf.FormatProgress(utils.TruncateID(imgID), "Pulling", "dependend layers"))
out.Write(sf.FormatProgress(utils.TruncateID(imgID), "Pulling", "dependent layers"))
// FIXME: Try to stream the images?
// FIXME: Launch the getRemoteImage() in goroutines
for i := len(history) - 1; i >= 0; i-- {
id := history[i]
for _, id := range history {
// ensure no two downloads of the same layer happen at the same time
if err := srv.poolAdd("pull", "layer:"+id); err != nil {
@@ -565,13 +773,13 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin
out.Write(sf.FormatProgress(utils.TruncateID(id), "Pulling", "metadata"))
imgJSON, imgSize, err := r.GetRemoteImageJSON(id, endpoint, token)
if err != nil {
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "pulling dependend layers"))
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "pulling dependent layers"))
// FIXME: Keep going in case of error?
return err
}
img, err := NewImgJSON(imgJSON)
if err != nil {
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "pulling dependend layers"))
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "pulling dependent layers"))
return fmt.Errorf("Failed to parse json: %s", err)
}
@@ -579,12 +787,12 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin
out.Write(sf.FormatProgress(utils.TruncateID(id), "Pulling", "fs layer"))
layer, err := r.GetRemoteImageLayer(img.ID, endpoint, token)
if err != nil {
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "pulling dependend layers"))
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "pulling dependent layers"))
return err
}
defer layer.Close()
if err := srv.runtime.graph.Register(imgJSON, utils.ProgressReader(layer, imgSize, out, sf.FormatProgress(utils.TruncateID(id), "Downloading", "%8v/%v (%v)"), sf, false), img); err != nil {
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "downloading dependend layers"))
out.Write(sf.FormatProgress(utils.TruncateID(id), "Error", "downloading dependent layers"))
return err
}
}
@@ -1131,13 +1339,28 @@ func (srv *Server) ContainerDestroy(name string, removeVolume, removeLink bool)
}
if container != nil {
if container.State.Running {
if container.State.IsRunning() {
return fmt.Errorf("Impossible to remove a running container, please stop it first")
}
volumes := make(map[string]struct{})
binds := make(map[string]struct{})
for _, bind := range container.hostConfig.Binds {
splitBind := strings.Split(bind, ":")
source := splitBind[0]
binds[source] = struct{}{}
}
// Store all the deleted containers volumes
for _, volumeId := range container.Volumes {
volumeId = strings.TrimRight(volumeId, "/layer")
// Skip the volumes mounted from external
if _, exists := binds[volumeId]; exists {
continue
}
volumeId = strings.TrimSuffix(volumeId, "/layer")
volumeId = filepath.Base(volumeId)
volumes[volumeId] = struct{}{}
}
@@ -1210,8 +1433,8 @@ func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi) error {
if err != nil {
return err
}
*imgs = append(*imgs, APIRmi{Deleted: utils.TruncateID(id)})
srv.LogEvent("delete", utils.TruncateID(id), "")
*imgs = append(*imgs, APIRmi{Deleted: id})
srv.LogEvent("delete", id, "")
return nil
}
return nil
@@ -1293,7 +1516,7 @@ func (srv *Server) ImageDelete(name string, autoPrune bool) ([]APIRmi, error) {
// Prevent deletion if image is used by a running container
for _, container := range srv.runtime.List() {
if container.State.Running {
if container.State.IsRunning() {
parent, err := srv.runtime.repositories.LookupImage(container.Image)
if err != nil {
return nil, err
@@ -1515,7 +1738,7 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
//stream
if stream {
if container.State.Ghost {
if container.State.IsGhost() {
return fmt.Errorf("Impossible to attach to a ghost container")
}
+40 -3
View File
@@ -8,7 +8,7 @@ import (
)
type State struct {
sync.Mutex
sync.RWMutex
Running bool
Pid int
ExitCode int
@@ -19,6 +19,9 @@ type State struct {
// String returns a human-readable description of the state
func (s *State) String() string {
s.RLock()
defer s.RUnlock()
if s.Running {
if s.Ghost {
return fmt.Sprintf("Ghost")
@@ -28,7 +31,38 @@ func (s *State) String() string {
return fmt.Sprintf("Exit %d", s.ExitCode)
}
func (s *State) setRunning(pid int) {
func (s *State) IsRunning() bool {
s.RLock()
defer s.RUnlock()
return s.Running
}
func (s *State) IsGhost() bool {
s.RLock()
defer s.RUnlock()
return s.Ghost
}
func (s *State) GetExitCode() int {
s.RLock()
defer s.RUnlock()
return s.ExitCode
}
func (s *State) SetGhost(val bool) {
s.Lock()
defer s.Unlock()
s.Ghost = val
}
func (s *State) SetRunning(pid int) {
s.Lock()
defer s.Unlock()
s.Running = true
s.Ghost = false
s.ExitCode = 0
@@ -36,7 +70,10 @@ func (s *State) setRunning(pid int) {
s.StartedAt = time.Now()
}
func (s *State) setStopped(exitCode int) {
func (s *State) SetStopped(exitCode int) {
s.Lock()
defer s.Unlock()
s.Running = false
s.Pid = 0
s.FinishedAt = time.Now()
+3 -8
View File
@@ -1,7 +1,6 @@
package docker
import (
"github.com/dotcloud/docker/graphdriver"
"github.com/dotcloud/docker/utils"
"os"
"path"
@@ -9,16 +8,12 @@ import (
)
const (
testImageName = "myapp"
testImageID = "foo"
testImageName string = "myapp"
testImageID string = "foo"
)
func mkTestTagStore(root string, t *testing.T) *TagStore {
driver, err := graphdriver.New(root)
if err != nil {
t.Fatal(err)
}
graph, err := NewGraph(root, driver)
graph, err := NewGraph(root)
if err != nil {
t.Fatal(err)
}
-48
View File
@@ -1,43 +1,14 @@
package docker
/*
#include <sys/ioctl.h>
#include <linux/fs.h>
#include <errno.h>
// See linux.git/fs/btrfs/ioctl.h
#define BTRFS_IOCTL_MAGIC 0x94
#define BTRFS_IOC_CLONE _IOW(BTRFS_IOCTL_MAGIC, 9, int)
int
btrfs_reflink(int fd_out, int fd_in)
{
int res;
res = ioctl(fd_out, BTRFS_IOC_CLONE, fd_in);
if (res < 0)
return errno;
return 0;
}
*/
import "C"
import (
"fmt"
"github.com/dotcloud/docker/archive"
"github.com/dotcloud/docker/namesgenerator"
"github.com/dotcloud/docker/utils"
"io"
"io/ioutil"
"os"
"strconv"
"strings"
"syscall"
)
type Change struct {
archive.Change
}
// Compare two Config struct. Do not compare the "Image" nor "Hostname" fields
// If OpenStdin is set, then it differs
func CompareConfig(a, b *Config) bool {
@@ -334,14 +305,6 @@ func migratePortMappings(config *Config, hostConfig *HostConfig) error {
return nil
}
func BtrfsReflink(fd_out, fd_in uintptr) error {
res := C.btrfs_reflink(C.int(fd_out), C.int(fd_in))
if res != 0 {
return syscall.Errno(res)
}
return nil
}
// Links come in the format of
// name:alias
func parseLink(rawLink string) (map[string]string, error) {
@@ -374,14 +337,3 @@ func (c *checker) Exists(name string) bool {
func generateRandomName(runtime *Runtime) (string, error) {
return namesgenerator.GenerateRandomName(&checker{runtime})
}
func CopyFile(dstFile, srcFile *os.File) error {
err := BtrfsReflink(dstFile.Fd(), srcFile.Fd())
if err == nil {
return nil
}
// Fall back to normal copy
_, err = io.Copy(dstFile, srcFile)
return err
}
-19
View File
@@ -1,19 +0,0 @@
package utils
import (
"os"
"path/filepath"
)
// TreeSize walks a directory tree and returns its total size in bytes.
func TreeSize(dir string) (size int64, err error) {
err = filepath.Walk(dir, func(d string, fileInfo os.FileInfo, e error) error {
// Ignore directory sizes
if fileInfo.IsDir() {
return nil
}
size += fileInfo.Size()
return nil
})
return
}