Move aufs to a storage driver, add devicemapper and dummy drivers
| ... | ... |
@@ -23,7 +23,7 @@ |
| 23 | 23 |
# the case. Therefore, you don't have to disable it anymore. |
| 24 | 24 |
# |
| 25 | 25 |
|
| 26 |
-docker-version 0.6.1 |
|
| 26 |
+docker-version 0.6.1 |
|
| 27 | 27 |
from ubuntu:12.04 |
| 28 | 28 |
maintainer Solomon Hykes <solomon@dotcloud.com> |
| 29 | 29 |
|
| ... | ... |
@@ -33,13 +33,13 @@ run apt-get update |
| 33 | 33 |
run apt-get install -y -q curl |
| 34 | 34 |
run apt-get install -y -q git |
| 35 | 35 |
run apt-get install -y -q mercurial |
| 36 |
-run apt-get install -y -q build-essential libsqlite3-dev |
|
| 36 |
+run apt-get install -y -q build-essential libsqlite3-dev |
|
| 37 | 37 |
|
| 38 | 38 |
# Install Go |
| 39 | 39 |
run curl -s https://go.googlecode.com/files/go1.2rc5.src.tar.gz | tar -v -C /usr/local -xz |
| 40 | 40 |
env PATH /usr/local/go/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin |
| 41 | 41 |
env GOPATH /go:/go/src/github.com/dotcloud/docker/vendor |
| 42 |
-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 |
|
| 42 |
+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 |
|
| 43 | 43 |
|
| 44 | 44 |
# Ubuntu stuff |
| 45 | 45 |
run apt-get install -y -q ruby1.9.3 rubygems libffi-dev |
| ... | ... |
@@ -56,11 +56,20 @@ run apt-get install -y -q iptables |
| 56 | 56 |
run apt-get install -y -q lxc |
| 57 | 57 |
run apt-get install -y -q aufs-tools |
| 58 | 58 |
|
| 59 |
+# Get lvm2 source for compiling statically |
|
| 60 |
+run git clone https://git.fedorahosted.org/git/lvm2.git /usr/local/lvm2 && cd /usr/local/lvm2 && git checkout v2_02_103 |
|
| 61 |
+# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags |
|
| 62 |
+# 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 |
|
| 63 |
+ |
|
| 64 |
+# Compile and install lvm2 |
|
| 65 |
+run cd /usr/local/lvm2 && ./configure --enable-static_link && make device-mapper && make install_device-mapper |
|
| 66 |
+# see https://git.fedorahosted.org/cgit/lvm2.git/tree/INSTALL |
|
| 67 |
+ |
|
| 59 | 68 |
volume /var/lib/docker |
| 60 | 69 |
workdir /go/src/github.com/dotcloud/docker |
| 61 | 70 |
|
| 62 | 71 |
# Wrap all commands in the "docker-in-docker" script to allow nested containers |
| 63 |
-entrypoint ["hack/dind"] |
|
| 72 |
+entrypoint ["hack/dind"] |
|
| 64 | 73 |
|
| 65 | 74 |
# Upload docker source |
| 66 |
-add . /go/src/github.com/dotcloud/docker |
|
| 75 |
+add . /go/src/github.com/dotcloud/docker |
| ... | ... |
@@ -33,15 +33,17 @@ type ( |
| 33 | 33 |
Debug bool |
| 34 | 34 |
Containers int |
| 35 | 35 |
Images int |
| 36 |
- NFd int `json:",omitempty"` |
|
| 37 |
- NGoroutines int `json:",omitempty"` |
|
| 38 |
- MemoryLimit bool `json:",omitempty"` |
|
| 39 |
- SwapLimit bool `json:",omitempty"` |
|
| 40 |
- IPv4Forwarding bool `json:",omitempty"` |
|
| 41 |
- LXCVersion string `json:",omitempty"` |
|
| 42 |
- NEventsListener int `json:",omitempty"` |
|
| 43 |
- KernelVersion string `json:",omitempty"` |
|
| 44 |
- IndexServerAddress string `json:",omitempty"` |
|
| 36 |
+ Driver string `json:",omitempty"` |
|
| 37 |
+ DriverStatus [][2]string `json:",omitempty"` |
|
| 38 |
+ NFd int `json:",omitempty"` |
|
| 39 |
+ NGoroutines int `json:",omitempty"` |
|
| 40 |
+ MemoryLimit bool `json:",omitempty"` |
|
| 41 |
+ SwapLimit bool `json:",omitempty"` |
|
| 42 |
+ IPv4Forwarding bool `json:",omitempty"` |
|
| 43 |
+ LXCVersion string `json:",omitempty"` |
|
| 44 |
+ NEventsListener int `json:",omitempty"` |
|
| 45 |
+ KernelVersion string `json:",omitempty"` |
|
| 46 |
+ IndexServerAddress string `json:",omitempty"` |
|
| 45 | 47 |
} |
| 46 | 48 |
|
| 47 | 49 |
APITop struct {
|
| ... | ... |
@@ -15,7 +15,15 @@ import ( |
| 15 | 15 |
|
| 16 | 16 |
type Archive io.Reader |
| 17 | 17 |
|
| 18 |
-type Compression uint32 |
|
| 18 |
+type Compression int |
|
| 19 |
+ |
|
| 20 |
+type TarOptions struct {
|
|
| 21 |
+ Includes []string |
|
| 22 |
+ Excludes []string |
|
| 23 |
+ Recursive bool |
|
| 24 |
+ Compression Compression |
|
| 25 |
+ CreateFiles []string |
|
| 26 |
+} |
|
| 19 | 27 |
|
| 20 | 28 |
const ( |
| 21 | 29 |
Uncompressed Compression = iota |
| ... | ... |
@@ -80,20 +88,78 @@ func (compression *Compression) Extension() string {
|
| 80 | 80 |
// Tar creates an archive from the directory at `path`, and returns it as a |
| 81 | 81 |
// stream of bytes. |
| 82 | 82 |
func Tar(path string, compression Compression) (io.Reader, error) {
|
| 83 |
- return TarFilter(path, compression, nil) |
|
| 83 |
+ return TarFilter(path, &TarOptions{Recursive: true, Compression: compression})
|
|
| 84 |
+} |
|
| 85 |
+ |
|
| 86 |
+func escapeName(name string) string {
|
|
| 87 |
+ escaped := make([]byte, 0) |
|
| 88 |
+ for i, c := range []byte(name) {
|
|
| 89 |
+ if i == 0 && c == '/' {
|
|
| 90 |
+ continue |
|
| 91 |
+ } |
|
| 92 |
+ // all printable chars except "-" which is 0x2d |
|
| 93 |
+ if (0x20 <= c && c <= 0x7E) && c != 0x2d {
|
|
| 94 |
+ escaped = append(escaped, c) |
|
| 95 |
+ } else {
|
|
| 96 |
+ escaped = append(escaped, fmt.Sprintf("\\%03o", c)...)
|
|
| 97 |
+ } |
|
| 98 |
+ } |
|
| 99 |
+ return string(escaped) |
|
| 84 | 100 |
} |
| 85 | 101 |
|
| 86 | 102 |
// Tar creates an archive from the directory at `path`, only including files whose relative |
| 87 | 103 |
// paths are included in `filter`. If `filter` is nil, then all files are included. |
| 88 |
-func TarFilter(path string, compression Compression, filter []string) (io.Reader, error) {
|
|
| 89 |
- args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path}
|
|
| 90 |
- if filter == nil {
|
|
| 91 |
- filter = []string{"."}
|
|
| 104 |
+func TarFilter(path string, options *TarOptions) (io.Reader, error) {
|
|
| 105 |
+ args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path, "-T", "-"}
|
|
| 106 |
+ if options.Includes == nil {
|
|
| 107 |
+ options.Includes = []string{"."}
|
|
| 92 | 108 |
} |
| 93 |
- for _, f := range filter {
|
|
| 94 |
- args = append(args, "-c"+compression.Flag(), f) |
|
| 109 |
+ args = append(args, "-c"+options.Compression.Flag()) |
|
| 110 |
+ |
|
| 111 |
+ for _, exclude := range options.Excludes {
|
|
| 112 |
+ args = append(args, fmt.Sprintf("--exclude=%s", exclude))
|
|
| 113 |
+ } |
|
| 114 |
+ |
|
| 115 |
+ if !options.Recursive {
|
|
| 116 |
+ args = append(args, "--no-recursion") |
|
| 117 |
+ } |
|
| 118 |
+ |
|
| 119 |
+ files := "" |
|
| 120 |
+ for _, f := range options.Includes {
|
|
| 121 |
+ files = files + escapeName(f) + "\n" |
|
| 122 |
+ } |
|
| 123 |
+ |
|
| 124 |
+ tmpDir := "" |
|
| 125 |
+ |
|
| 126 |
+ if options.CreateFiles != nil {
|
|
| 127 |
+ var err error // Can't use := here or we override the outer tmpDir |
|
| 128 |
+ tmpDir, err = ioutil.TempDir("", "docker-tar")
|
|
| 129 |
+ if err != nil {
|
|
| 130 |
+ return nil, err |
|
| 131 |
+ } |
|
| 132 |
+ |
|
| 133 |
+ files = files + "-C" + tmpDir + "\n" |
|
| 134 |
+ for _, f := range options.CreateFiles {
|
|
| 135 |
+ path := filepath.Join(tmpDir, f) |
|
| 136 |
+ err := os.MkdirAll(filepath.Dir(path), 0600) |
|
| 137 |
+ if err != nil {
|
|
| 138 |
+ return nil, err |
|
| 139 |
+ } |
|
| 140 |
+ |
|
| 141 |
+ if file, err := os.OpenFile(path, os.O_CREATE, 0600); err != nil {
|
|
| 142 |
+ return nil, err |
|
| 143 |
+ } else {
|
|
| 144 |
+ file.Close() |
|
| 145 |
+ } |
|
| 146 |
+ files = files + escapeName(f) + "\n" |
|
| 147 |
+ } |
|
| 95 | 148 |
} |
| 96 |
- return CmdStream(exec.Command(args[0], args[1:]...)) |
|
| 149 |
+ |
|
| 150 |
+ return CmdStream(exec.Command(args[0], args[1:]...), &files, func() {
|
|
| 151 |
+ if tmpDir != "" {
|
|
| 152 |
+ _ = os.RemoveAll(tmpDir) |
|
| 153 |
+ } |
|
| 154 |
+ }) |
|
| 97 | 155 |
} |
| 98 | 156 |
|
| 99 | 157 |
// Untar reads a stream of bytes from `archive`, parses it as a tar archive, |
| ... | ... |
@@ -101,7 +167,7 @@ func TarFilter(path string, compression Compression, filter []string) (io.Reader |
| 101 | 101 |
// The archive may be compressed with one of the following algorithms: |
| 102 | 102 |
// identity (uncompressed), gzip, bzip2, xz. |
| 103 | 103 |
// FIXME: specify behavior when target path exists vs. doesn't exist. |
| 104 |
-func Untar(archive io.Reader, path string) error {
|
|
| 104 |
+func Untar(archive io.Reader, path string, options *TarOptions) error {
|
|
| 105 | 105 |
if archive == nil {
|
| 106 | 106 |
return fmt.Errorf("Empty archive")
|
| 107 | 107 |
} |
| ... | ... |
@@ -123,8 +189,15 @@ func Untar(archive io.Reader, path string) error {
|
| 123 | 123 |
compression := DetectCompression(buf) |
| 124 | 124 |
|
| 125 | 125 |
utils.Debugf("Archive compression detected: %s", compression.Extension())
|
| 126 |
+ args := []string{"--numeric-owner", "-f", "-", "-C", path, "-x" + compression.Flag()}
|
|
| 127 |
+ |
|
| 128 |
+ if options != nil {
|
|
| 129 |
+ for _, exclude := range options.Excludes {
|
|
| 130 |
+ args = append(args, fmt.Sprintf("--exclude=%s", exclude))
|
|
| 131 |
+ } |
|
| 132 |
+ } |
|
| 126 | 133 |
|
| 127 |
- cmd := exec.Command("tar", "--numeric-owner", "-f", "-", "-C", path, "-x"+compression.Flag())
|
|
| 134 |
+ cmd := exec.Command("tar", args...)
|
|
| 128 | 135 |
cmd.Stdin = io.MultiReader(bytes.NewReader(buf), archive) |
| 129 | 136 |
// Hardcode locale environment for predictable outcome regardless of host configuration. |
| 130 | 137 |
// (see https://github.com/dotcloud/docker/issues/355) |
| ... | ... |
@@ -141,11 +214,11 @@ func Untar(archive io.Reader, path string) error {
|
| 141 | 141 |
// TarUntar aborts and returns the error. |
| 142 | 142 |
func TarUntar(src string, filter []string, dst string) error {
|
| 143 | 143 |
utils.Debugf("TarUntar(%s %s %s)", src, filter, dst)
|
| 144 |
- archive, err := TarFilter(src, Uncompressed, filter) |
|
| 144 |
+ archive, err := TarFilter(src, &TarOptions{Compression: Uncompressed, Includes: filter, Recursive: true})
|
|
| 145 | 145 |
if err != nil {
|
| 146 | 146 |
return err |
| 147 | 147 |
} |
| 148 |
- return Untar(archive, dst) |
|
| 148 |
+ return Untar(archive, dst, nil) |
|
| 149 | 149 |
} |
| 150 | 150 |
|
| 151 | 151 |
// UntarPath is a convenience function which looks for an archive |
| ... | ... |
@@ -153,7 +226,7 @@ func TarUntar(src string, filter []string, dst string) error {
|
| 153 | 153 |
func UntarPath(src, dst string) error {
|
| 154 | 154 |
if archive, err := os.Open(src); err != nil {
|
| 155 | 155 |
return err |
| 156 |
- } else if err := Untar(archive, dst); err != nil {
|
|
| 156 |
+ } else if err := Untar(archive, dst, nil); err != nil {
|
|
| 157 | 157 |
return err |
| 158 | 158 |
} |
| 159 | 159 |
return nil |
| ... | ... |
@@ -222,19 +295,39 @@ func CopyFileWithTar(src, dst string) error {
|
| 222 | 222 |
return err |
| 223 | 223 |
} |
| 224 | 224 |
tw.Close() |
| 225 |
- return Untar(buf, filepath.Dir(dst)) |
|
| 225 |
+ return Untar(buf, filepath.Dir(dst), nil) |
|
| 226 | 226 |
} |
| 227 | 227 |
|
| 228 | 228 |
// CmdStream executes a command, and returns its stdout as a stream. |
| 229 | 229 |
// If the command fails to run or doesn't complete successfully, an error |
| 230 | 230 |
// will be returned, including anything written on stderr. |
| 231 |
-func CmdStream(cmd *exec.Cmd) (io.Reader, error) {
|
|
| 231 |
+func CmdStream(cmd *exec.Cmd, input *string, atEnd func()) (io.Reader, error) {
|
|
| 232 |
+ if input != nil {
|
|
| 233 |
+ stdin, err := cmd.StdinPipe() |
|
| 234 |
+ if err != nil {
|
|
| 235 |
+ if atEnd != nil {
|
|
| 236 |
+ atEnd() |
|
| 237 |
+ } |
|
| 238 |
+ return nil, err |
|
| 239 |
+ } |
|
| 240 |
+ // Write stdin if any |
|
| 241 |
+ go func() {
|
|
| 242 |
+ _, _ = stdin.Write([]byte(*input)) |
|
| 243 |
+ stdin.Close() |
|
| 244 |
+ }() |
|
| 245 |
+ } |
|
| 232 | 246 |
stdout, err := cmd.StdoutPipe() |
| 233 | 247 |
if err != nil {
|
| 248 |
+ if atEnd != nil {
|
|
| 249 |
+ atEnd() |
|
| 250 |
+ } |
|
| 234 | 251 |
return nil, err |
| 235 | 252 |
} |
| 236 | 253 |
stderr, err := cmd.StderrPipe() |
| 237 | 254 |
if err != nil {
|
| 255 |
+ if atEnd != nil {
|
|
| 256 |
+ atEnd() |
|
| 257 |
+ } |
|
| 238 | 258 |
return nil, err |
| 239 | 259 |
} |
| 240 | 260 |
pipeR, pipeW := io.Pipe() |
| ... | ... |
@@ -259,6 +352,9 @@ func CmdStream(cmd *exec.Cmd) (io.Reader, error) {
|
| 259 | 259 |
} else {
|
| 260 | 260 |
pipeW.Close() |
| 261 | 261 |
} |
| 262 |
+ if atEnd != nil {
|
|
| 263 |
+ atEnd() |
|
| 264 |
+ } |
|
| 262 | 265 |
}() |
| 263 | 266 |
// Run the command and return the pipe |
| 264 | 267 |
if err := cmd.Start(); err != nil {
|
| ... | ... |
@@ -14,7 +14,7 @@ import ( |
| 14 | 14 |
|
| 15 | 15 |
func TestCmdStreamLargeStderr(t *testing.T) {
|
| 16 | 16 |
cmd := exec.Command("/bin/sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello")
|
| 17 |
- out, err := CmdStream(cmd) |
|
| 17 |
+ out, err := CmdStream(cmd, nil, nil) |
|
| 18 | 18 |
if err != nil {
|
| 19 | 19 |
t.Fatalf("Failed to start command: %s", err)
|
| 20 | 20 |
} |
| ... | ... |
@@ -35,7 +35,7 @@ func TestCmdStreamLargeStderr(t *testing.T) {
|
| 35 | 35 |
|
| 36 | 36 |
func TestCmdStreamBad(t *testing.T) {
|
| 37 | 37 |
badCmd := exec.Command("/bin/sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1")
|
| 38 |
- out, err := CmdStream(badCmd) |
|
| 38 |
+ out, err := CmdStream(badCmd, nil, nil) |
|
| 39 | 39 |
if err != nil {
|
| 40 | 40 |
t.Fatalf("Failed to start command: %s", err)
|
| 41 | 41 |
} |
| ... | ... |
@@ -50,7 +50,7 @@ func TestCmdStreamBad(t *testing.T) {
|
| 50 | 50 |
|
| 51 | 51 |
func TestCmdStreamGood(t *testing.T) {
|
| 52 | 52 |
cmd := exec.Command("/bin/sh", "-c", "echo hello; exit 0")
|
| 53 |
- out, err := CmdStream(cmd) |
|
| 53 |
+ out, err := CmdStream(cmd, nil, nil) |
|
| 54 | 54 |
if err != nil {
|
| 55 | 55 |
t.Fatal(err) |
| 56 | 56 |
} |
| ... | ... |
@@ -83,7 +83,7 @@ func tarUntar(t *testing.T, origin string, compression Compression) error {
|
| 83 | 83 |
return err |
| 84 | 84 |
} |
| 85 | 85 |
defer os.RemoveAll(tmp) |
| 86 |
- if err := Untar(archive, tmp); err != nil {
|
|
| 86 |
+ if err := Untar(archive, tmp, nil); err != nil {
|
|
| 87 | 87 |
return err |
| 88 | 88 |
} |
| 89 | 89 |
if _, err := os.Stat(tmp); err != nil {
|
| 90 | 90 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,317 @@ |
| 0 |
+package archive |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "os" |
|
| 5 |
+ "path/filepath" |
|
| 6 |
+ "strings" |
|
| 7 |
+ "syscall" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+type ChangeType int |
|
| 11 |
+ |
|
| 12 |
+const ( |
|
| 13 |
+ ChangeModify = iota |
|
| 14 |
+ ChangeAdd |
|
| 15 |
+ ChangeDelete |
|
| 16 |
+) |
|
| 17 |
+ |
|
| 18 |
+type Change struct {
|
|
| 19 |
+ Path string |
|
| 20 |
+ Kind ChangeType |
|
| 21 |
+} |
|
| 22 |
+ |
|
| 23 |
+func (change *Change) String() string {
|
|
| 24 |
+ var kind string |
|
| 25 |
+ switch change.Kind {
|
|
| 26 |
+ case ChangeModify: |
|
| 27 |
+ kind = "C" |
|
| 28 |
+ case ChangeAdd: |
|
| 29 |
+ kind = "A" |
|
| 30 |
+ case ChangeDelete: |
|
| 31 |
+ kind = "D" |
|
| 32 |
+ } |
|
| 33 |
+ return fmt.Sprintf("%s %s", kind, change.Path)
|
|
| 34 |
+} |
|
| 35 |
+ |
|
| 36 |
+func Changes(layers []string, rw string) ([]Change, error) {
|
|
| 37 |
+ var changes []Change |
|
| 38 |
+ err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error {
|
|
| 39 |
+ if err != nil {
|
|
| 40 |
+ return err |
|
| 41 |
+ } |
|
| 42 |
+ |
|
| 43 |
+ // Rebase path |
|
| 44 |
+ path, err = filepath.Rel(rw, path) |
|
| 45 |
+ if err != nil {
|
|
| 46 |
+ return err |
|
| 47 |
+ } |
|
| 48 |
+ path = filepath.Join("/", path)
|
|
| 49 |
+ |
|
| 50 |
+ // Skip root |
|
| 51 |
+ if path == "/" {
|
|
| 52 |
+ return nil |
|
| 53 |
+ } |
|
| 54 |
+ |
|
| 55 |
+ // Skip AUFS metadata |
|
| 56 |
+ if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched {
|
|
| 57 |
+ return err |
|
| 58 |
+ } |
|
| 59 |
+ |
|
| 60 |
+ change := Change{
|
|
| 61 |
+ Path: path, |
|
| 62 |
+ } |
|
| 63 |
+ |
|
| 64 |
+ // Find out what kind of modification happened |
|
| 65 |
+ file := filepath.Base(path) |
|
| 66 |
+ // If there is a whiteout, then the file was removed |
|
| 67 |
+ if strings.HasPrefix(file, ".wh.") {
|
|
| 68 |
+ originalFile := file[len(".wh."):]
|
|
| 69 |
+ change.Path = filepath.Join(filepath.Dir(path), originalFile) |
|
| 70 |
+ change.Kind = ChangeDelete |
|
| 71 |
+ } else {
|
|
| 72 |
+ // Otherwise, the file was added |
|
| 73 |
+ change.Kind = ChangeAdd |
|
| 74 |
+ |
|
| 75 |
+ // ...Unless it already existed in a top layer, in which case, it's a modification |
|
| 76 |
+ for _, layer := range layers {
|
|
| 77 |
+ stat, err := os.Stat(filepath.Join(layer, path)) |
|
| 78 |
+ if err != nil && !os.IsNotExist(err) {
|
|
| 79 |
+ return err |
|
| 80 |
+ } |
|
| 81 |
+ if err == nil {
|
|
| 82 |
+ // The file existed in the top layer, so that's a modification |
|
| 83 |
+ |
|
| 84 |
+ // However, if it's a directory, maybe it wasn't actually modified. |
|
| 85 |
+ // If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar |
|
| 86 |
+ if stat.IsDir() && f.IsDir() {
|
|
| 87 |
+ if f.Size() == stat.Size() && f.Mode() == stat.Mode() && f.ModTime() == stat.ModTime() {
|
|
| 88 |
+ // Both directories are the same, don't record the change |
|
| 89 |
+ return nil |
|
| 90 |
+ } |
|
| 91 |
+ } |
|
| 92 |
+ change.Kind = ChangeModify |
|
| 93 |
+ break |
|
| 94 |
+ } |
|
| 95 |
+ } |
|
| 96 |
+ } |
|
| 97 |
+ |
|
| 98 |
+ // Record change |
|
| 99 |
+ changes = append(changes, change) |
|
| 100 |
+ return nil |
|
| 101 |
+ }) |
|
| 102 |
+ if err != nil && !os.IsNotExist(err) {
|
|
| 103 |
+ return nil, err |
|
| 104 |
+ } |
|
| 105 |
+ return changes, nil |
|
| 106 |
+} |
|
| 107 |
+ |
|
| 108 |
+type FileInfo struct {
|
|
| 109 |
+ parent *FileInfo |
|
| 110 |
+ name string |
|
| 111 |
+ stat syscall.Stat_t |
|
| 112 |
+ children map[string]*FileInfo |
|
| 113 |
+} |
|
| 114 |
+ |
|
| 115 |
+func (root *FileInfo) LookUp(path string) *FileInfo {
|
|
| 116 |
+ parent := root |
|
| 117 |
+ if path == "/" {
|
|
| 118 |
+ return root |
|
| 119 |
+ } |
|
| 120 |
+ |
|
| 121 |
+ pathElements := strings.Split(path, "/") |
|
| 122 |
+ for _, elem := range pathElements {
|
|
| 123 |
+ if elem != "" {
|
|
| 124 |
+ child := parent.children[elem] |
|
| 125 |
+ if child == nil {
|
|
| 126 |
+ return nil |
|
| 127 |
+ } |
|
| 128 |
+ parent = child |
|
| 129 |
+ } |
|
| 130 |
+ } |
|
| 131 |
+ return parent |
|
| 132 |
+} |
|
| 133 |
+ |
|
| 134 |
+func (info *FileInfo) path() string {
|
|
| 135 |
+ if info.parent == nil {
|
|
| 136 |
+ return "/" |
|
| 137 |
+ } |
|
| 138 |
+ return filepath.Join(info.parent.path(), info.name) |
|
| 139 |
+} |
|
| 140 |
+ |
|
| 141 |
+func (info *FileInfo) isDir() bool {
|
|
| 142 |
+ return info.parent == nil || info.stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR |
|
| 143 |
+} |
|
| 144 |
+ |
|
| 145 |
+func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
|
|
| 146 |
+ if oldInfo == nil {
|
|
| 147 |
+ // add |
|
| 148 |
+ change := Change{
|
|
| 149 |
+ Path: info.path(), |
|
| 150 |
+ Kind: ChangeAdd, |
|
| 151 |
+ } |
|
| 152 |
+ *changes = append(*changes, change) |
|
| 153 |
+ } |
|
| 154 |
+ |
|
| 155 |
+ // We make a copy so we can modify it to detect additions |
|
| 156 |
+ // also, we only recurse on the old dir if the new info is a directory |
|
| 157 |
+ // otherwise any previous delete/change is considered recursive |
|
| 158 |
+ oldChildren := make(map[string]*FileInfo) |
|
| 159 |
+ if oldInfo != nil && info.isDir() {
|
|
| 160 |
+ for k, v := range oldInfo.children {
|
|
| 161 |
+ oldChildren[k] = v |
|
| 162 |
+ } |
|
| 163 |
+ } |
|
| 164 |
+ |
|
| 165 |
+ for name, newChild := range info.children {
|
|
| 166 |
+ oldChild, _ := oldChildren[name] |
|
| 167 |
+ if oldChild != nil {
|
|
| 168 |
+ // change? |
|
| 169 |
+ oldStat := &oldChild.stat |
|
| 170 |
+ newStat := &newChild.stat |
|
| 171 |
+ // Note: We can't compare inode or ctime or blocksize here, because these change |
|
| 172 |
+ // when copying a file into a container. However, that is not generally a problem |
|
| 173 |
+ // because any content change will change mtime, and any status change should |
|
| 174 |
+ // be visible when actually comparing the stat fields. The only time this |
|
| 175 |
+ // breaks down is if some code intentionally hides a change by setting |
|
| 176 |
+ // back mtime |
|
| 177 |
+ if oldStat.Mode != newStat.Mode || |
|
| 178 |
+ oldStat.Uid != newStat.Uid || |
|
| 179 |
+ oldStat.Gid != newStat.Gid || |
|
| 180 |
+ oldStat.Rdev != newStat.Rdev || |
|
| 181 |
+ // Don't look at size for dirs, its not a good measure of change |
|
| 182 |
+ (oldStat.Size != newStat.Size && oldStat.Mode&syscall.S_IFDIR != syscall.S_IFDIR) || |
|
| 183 |
+ oldStat.Mtim != newStat.Mtim {
|
|
| 184 |
+ change := Change{
|
|
| 185 |
+ Path: newChild.path(), |
|
| 186 |
+ Kind: ChangeModify, |
|
| 187 |
+ } |
|
| 188 |
+ *changes = append(*changes, change) |
|
| 189 |
+ } |
|
| 190 |
+ |
|
| 191 |
+ // Remove from copy so we can detect deletions |
|
| 192 |
+ delete(oldChildren, name) |
|
| 193 |
+ } |
|
| 194 |
+ |
|
| 195 |
+ newChild.addChanges(oldChild, changes) |
|
| 196 |
+ } |
|
| 197 |
+ for _, oldChild := range oldChildren {
|
|
| 198 |
+ // delete |
|
| 199 |
+ change := Change{
|
|
| 200 |
+ Path: oldChild.path(), |
|
| 201 |
+ Kind: ChangeDelete, |
|
| 202 |
+ } |
|
| 203 |
+ *changes = append(*changes, change) |
|
| 204 |
+ } |
|
| 205 |
+ |
|
| 206 |
+} |
|
| 207 |
+ |
|
| 208 |
+func (info *FileInfo) Changes(oldInfo *FileInfo) []Change {
|
|
| 209 |
+ var changes []Change |
|
| 210 |
+ |
|
| 211 |
+ info.addChanges(oldInfo, &changes) |
|
| 212 |
+ |
|
| 213 |
+ return changes |
|
| 214 |
+} |
|
| 215 |
+ |
|
| 216 |
+func newRootFileInfo() *FileInfo {
|
|
| 217 |
+ root := &FileInfo{
|
|
| 218 |
+ name: "/", |
|
| 219 |
+ children: make(map[string]*FileInfo), |
|
| 220 |
+ } |
|
| 221 |
+ return root |
|
| 222 |
+} |
|
| 223 |
+ |
|
| 224 |
+func collectFileInfo(sourceDir string) (*FileInfo, error) {
|
|
| 225 |
+ root := newRootFileInfo() |
|
| 226 |
+ |
|
| 227 |
+ err := filepath.Walk(sourceDir, func(path string, f os.FileInfo, err error) error {
|
|
| 228 |
+ if err != nil {
|
|
| 229 |
+ return err |
|
| 230 |
+ } |
|
| 231 |
+ |
|
| 232 |
+ // Rebase path |
|
| 233 |
+ relPath, err := filepath.Rel(sourceDir, path) |
|
| 234 |
+ if err != nil {
|
|
| 235 |
+ return err |
|
| 236 |
+ } |
|
| 237 |
+ relPath = filepath.Join("/", relPath)
|
|
| 238 |
+ |
|
| 239 |
+ if relPath == "/" {
|
|
| 240 |
+ return nil |
|
| 241 |
+ } |
|
| 242 |
+ |
|
| 243 |
+ parent := root.LookUp(filepath.Dir(relPath)) |
|
| 244 |
+ if parent == nil {
|
|
| 245 |
+ return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath)
|
|
| 246 |
+ } |
|
| 247 |
+ |
|
| 248 |
+ info := &FileInfo{
|
|
| 249 |
+ name: filepath.Base(relPath), |
|
| 250 |
+ children: make(map[string]*FileInfo), |
|
| 251 |
+ parent: parent, |
|
| 252 |
+ } |
|
| 253 |
+ |
|
| 254 |
+ if err := syscall.Lstat(path, &info.stat); err != nil {
|
|
| 255 |
+ return err |
|
| 256 |
+ } |
|
| 257 |
+ |
|
| 258 |
+ parent.children[info.name] = info |
|
| 259 |
+ |
|
| 260 |
+ return nil |
|
| 261 |
+ }) |
|
| 262 |
+ if err != nil {
|
|
| 263 |
+ return nil, err |
|
| 264 |
+ } |
|
| 265 |
+ return root, nil |
|
| 266 |
+} |
|
| 267 |
+ |
|
| 268 |
+// Compare two directories and generate an array of Change objects describing the changes |
|
| 269 |
+func ChangesDirs(newDir, oldDir string) ([]Change, error) {
|
|
| 270 |
+ oldRoot, err := collectFileInfo(oldDir) |
|
| 271 |
+ if err != nil {
|
|
| 272 |
+ return nil, err |
|
| 273 |
+ } |
|
| 274 |
+ newRoot, err := collectFileInfo(newDir) |
|
| 275 |
+ if err != nil {
|
|
| 276 |
+ return nil, err |
|
| 277 |
+ } |
|
| 278 |
+ |
|
| 279 |
+ return newRoot.Changes(oldRoot), nil |
|
| 280 |
+} |
|
| 281 |
+ |
|
| 282 |
+func ChangesSize(newDir string, changes []Change) int64 {
|
|
| 283 |
+ var size int64 |
|
| 284 |
+ for _, change := range changes {
|
|
| 285 |
+ if change.Kind == ChangeModify || change.Kind == ChangeAdd {
|
|
| 286 |
+ file := filepath.Join(newDir, change.Path) |
|
| 287 |
+ fileInfo, _ := os.Lstat(file) |
|
| 288 |
+ if fileInfo != nil && !fileInfo.IsDir() {
|
|
| 289 |
+ size += fileInfo.Size() |
|
| 290 |
+ } |
|
| 291 |
+ } |
|
| 292 |
+ } |
|
| 293 |
+ return size |
|
| 294 |
+} |
|
| 295 |
+ |
|
| 296 |
+func ExportChanges(dir string, changes []Change) (Archive, error) {
|
|
| 297 |
+ files := make([]string, 0) |
|
| 298 |
+ deletions := make([]string, 0) |
|
| 299 |
+ for _, change := range changes {
|
|
| 300 |
+ if change.Kind == ChangeModify || change.Kind == ChangeAdd {
|
|
| 301 |
+ files = append(files, change.Path) |
|
| 302 |
+ } |
|
| 303 |
+ if change.Kind == ChangeDelete {
|
|
| 304 |
+ base := filepath.Base(change.Path) |
|
| 305 |
+ dir := filepath.Dir(change.Path) |
|
| 306 |
+ deletions = append(deletions, filepath.Join(dir, ".wh."+base)) |
|
| 307 |
+ } |
|
| 308 |
+ } |
|
| 309 |
+ // FIXME: Why do we create whiteout files inside Tar code ? |
|
| 310 |
+ return TarFilter(dir, &TarOptions{
|
|
| 311 |
+ Compression: Uncompressed, |
|
| 312 |
+ Includes: files, |
|
| 313 |
+ Recursive: false, |
|
| 314 |
+ CreateFiles: deletions, |
|
| 315 |
+ }) |
|
| 316 |
+} |
| 0 | 317 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,298 @@ |
| 0 |
+package archive |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "io/ioutil" |
|
| 4 |
+ "os" |
|
| 5 |
+ "os/exec" |
|
| 6 |
+ "path" |
|
| 7 |
+ "sort" |
|
| 8 |
+ "testing" |
|
| 9 |
+ "time" |
|
| 10 |
+) |
|
| 11 |
+ |
|
| 12 |
+func max(x, y int) int {
|
|
| 13 |
+ if x >= y {
|
|
| 14 |
+ return x |
|
| 15 |
+ } |
|
| 16 |
+ return y |
|
| 17 |
+} |
|
| 18 |
+ |
|
| 19 |
+func copyDir(src, dst string) error {
|
|
| 20 |
+ cmd := exec.Command("cp", "-a", src, dst)
|
|
| 21 |
+ if err := cmd.Run(); err != nil {
|
|
| 22 |
+ return err |
|
| 23 |
+ } |
|
| 24 |
+ return nil |
|
| 25 |
+} |
|
| 26 |
+ |
|
| 27 |
+// Helper to sort []Change by path |
|
| 28 |
+type byPath struct{ changes []Change }
|
|
| 29 |
+ |
|
| 30 |
+func (b byPath) Less(i, j int) bool { return b.changes[i].Path < b.changes[j].Path }
|
|
| 31 |
+func (b byPath) Len() int { return len(b.changes) }
|
|
| 32 |
+func (b byPath) Swap(i, j int) { b.changes[i], b.changes[j] = b.changes[j], b.changes[i] }
|
|
| 33 |
+ |
|
| 34 |
+type FileType uint32 |
|
| 35 |
+ |
|
| 36 |
+const ( |
|
| 37 |
+ Regular FileType = iota |
|
| 38 |
+ Dir |
|
| 39 |
+ Symlink |
|
| 40 |
+) |
|
| 41 |
+ |
|
| 42 |
+type FileData struct {
|
|
| 43 |
+ filetype FileType |
|
| 44 |
+ path string |
|
| 45 |
+ contents string |
|
| 46 |
+ permissions os.FileMode |
|
| 47 |
+} |
|
| 48 |
+ |
|
| 49 |
+func createSampleDir(t *testing.T, root string) {
|
|
| 50 |
+ files := []FileData{
|
|
| 51 |
+ {Regular, "file1", "file1\n", 0600},
|
|
| 52 |
+ {Regular, "file2", "file2\n", 0666},
|
|
| 53 |
+ {Regular, "file3", "file3\n", 0404},
|
|
| 54 |
+ {Regular, "file4", "file4\n", 0600},
|
|
| 55 |
+ {Regular, "file5", "file5\n", 0600},
|
|
| 56 |
+ {Regular, "file6", "file6\n", 0600},
|
|
| 57 |
+ {Regular, "file7", "file7\n", 0600},
|
|
| 58 |
+ {Dir, "dir1", "", 0740},
|
|
| 59 |
+ {Regular, "dir1/file1-1", "file1-1\n", 01444},
|
|
| 60 |
+ {Regular, "dir1/file1-2", "file1-2\n", 0666},
|
|
| 61 |
+ {Dir, "dir2", "", 0700},
|
|
| 62 |
+ {Regular, "dir2/file2-1", "file2-1\n", 0666},
|
|
| 63 |
+ {Regular, "dir2/file2-2", "file2-2\n", 0666},
|
|
| 64 |
+ {Dir, "dir3", "", 0700},
|
|
| 65 |
+ {Regular, "dir3/file3-1", "file3-1\n", 0666},
|
|
| 66 |
+ {Regular, "dir3/file3-2", "file3-2\n", 0666},
|
|
| 67 |
+ {Dir, "dir4", "", 0700},
|
|
| 68 |
+ {Regular, "dir4/file3-1", "file4-1\n", 0666},
|
|
| 69 |
+ {Regular, "dir4/file3-2", "file4-2\n", 0666},
|
|
| 70 |
+ {Symlink, "symlink1", "target1", 0666},
|
|
| 71 |
+ {Symlink, "symlink2", "target2", 0666},
|
|
| 72 |
+ } |
|
| 73 |
+ for _, info := range files {
|
|
| 74 |
+ if info.filetype == Dir {
|
|
| 75 |
+ if err := os.MkdirAll(path.Join(root, info.path), info.permissions); err != nil {
|
|
| 76 |
+ t.Fatal(err) |
|
| 77 |
+ } |
|
| 78 |
+ } else if info.filetype == Regular {
|
|
| 79 |
+ if err := ioutil.WriteFile(path.Join(root, info.path), []byte(info.contents), info.permissions); err != nil {
|
|
| 80 |
+ t.Fatal(err) |
|
| 81 |
+ } |
|
| 82 |
+ } else if info.filetype == Symlink {
|
|
| 83 |
+ if err := os.Symlink(info.contents, path.Join(root, info.path)); err != nil {
|
|
| 84 |
+ t.Fatal(err) |
|
| 85 |
+ } |
|
| 86 |
+ } |
|
| 87 |
+ } |
|
| 88 |
+} |
|
| 89 |
+ |
|
| 90 |
+// Create an directory, copy it, make sure we report no changes between the two |
|
| 91 |
+func TestChangesDirsEmpty(t *testing.T) {
|
|
| 92 |
+ src, err := ioutil.TempDir("", "docker-changes-test")
|
|
| 93 |
+ if err != nil {
|
|
| 94 |
+ t.Fatal(err) |
|
| 95 |
+ } |
|
| 96 |
+ createSampleDir(t, src) |
|
| 97 |
+ dst := src + "-copy" |
|
| 98 |
+ if err := copyDir(src, dst); err != nil {
|
|
| 99 |
+ t.Fatal(err) |
|
| 100 |
+ } |
|
| 101 |
+ changes, err := ChangesDirs(dst, src) |
|
| 102 |
+ if err != nil {
|
|
| 103 |
+ t.Fatal(err) |
|
| 104 |
+ } |
|
| 105 |
+ |
|
| 106 |
+ if len(changes) != 0 {
|
|
| 107 |
+ t.Fatalf("Reported changes for identical dirs: %v", changes)
|
|
| 108 |
+ } |
|
| 109 |
+ os.RemoveAll(src) |
|
| 110 |
+ os.RemoveAll(dst) |
|
| 111 |
+} |
|
| 112 |
+ |
|
| 113 |
+func mutateSampleDir(t *testing.T, root string) {
|
|
| 114 |
+ // Remove a regular file |
|
| 115 |
+ if err := os.RemoveAll(path.Join(root, "file1")); err != nil {
|
|
| 116 |
+ t.Fatal(err) |
|
| 117 |
+ } |
|
| 118 |
+ |
|
| 119 |
+ // Remove a directory |
|
| 120 |
+ if err := os.RemoveAll(path.Join(root, "dir1")); err != nil {
|
|
| 121 |
+ t.Fatal(err) |
|
| 122 |
+ } |
|
| 123 |
+ |
|
| 124 |
+ // Remove a symlink |
|
| 125 |
+ if err := os.RemoveAll(path.Join(root, "symlink1")); err != nil {
|
|
| 126 |
+ t.Fatal(err) |
|
| 127 |
+ } |
|
| 128 |
+ |
|
| 129 |
+ // Rewrite a file |
|
| 130 |
+ if err := ioutil.WriteFile(path.Join(root, "file2"), []byte("fileN\n"), 0777); err != nil {
|
|
| 131 |
+ t.Fatal(err) |
|
| 132 |
+ } |
|
| 133 |
+ |
|
| 134 |
+ // Replace a file |
|
| 135 |
+ if err := os.RemoveAll(path.Join(root, "file3")); err != nil {
|
|
| 136 |
+ t.Fatal(err) |
|
| 137 |
+ } |
|
| 138 |
+ if err := ioutil.WriteFile(path.Join(root, "file3"), []byte("fileM\n"), 0404); err != nil {
|
|
| 139 |
+ t.Fatal(err) |
|
| 140 |
+ } |
|
| 141 |
+ |
|
| 142 |
+ // Touch file |
|
| 143 |
+ if err := os.Chtimes(path.Join(root, "file4"), time.Now(), time.Now()); err != nil {
|
|
| 144 |
+ t.Fatal(err) |
|
| 145 |
+ } |
|
| 146 |
+ |
|
| 147 |
+ // Replace file with dir |
|
| 148 |
+ if err := os.RemoveAll(path.Join(root, "file5")); err != nil {
|
|
| 149 |
+ t.Fatal(err) |
|
| 150 |
+ } |
|
| 151 |
+ if err := os.MkdirAll(path.Join(root, "file5"), 0666); err != nil {
|
|
| 152 |
+ t.Fatal(err) |
|
| 153 |
+ } |
|
| 154 |
+ |
|
| 155 |
+ // Create new file |
|
| 156 |
+ if err := ioutil.WriteFile(path.Join(root, "filenew"), []byte("filenew\n"), 0777); err != nil {
|
|
| 157 |
+ t.Fatal(err) |
|
| 158 |
+ } |
|
| 159 |
+ |
|
| 160 |
+ // Create new dir |
|
| 161 |
+ if err := os.MkdirAll(path.Join(root, "dirnew"), 0766); err != nil {
|
|
| 162 |
+ t.Fatal(err) |
|
| 163 |
+ } |
|
| 164 |
+ |
|
| 165 |
+ // Create a new symlink |
|
| 166 |
+ if err := os.Symlink("targetnew", path.Join(root, "symlinknew")); err != nil {
|
|
| 167 |
+ t.Fatal(err) |
|
| 168 |
+ } |
|
| 169 |
+ |
|
| 170 |
+ // Change a symlink |
|
| 171 |
+ if err := os.RemoveAll(path.Join(root, "symlink2")); err != nil {
|
|
| 172 |
+ t.Fatal(err) |
|
| 173 |
+ } |
|
| 174 |
+ if err := os.Symlink("target2change", path.Join(root, "symlink2")); err != nil {
|
|
| 175 |
+ t.Fatal(err) |
|
| 176 |
+ } |
|
| 177 |
+ |
|
| 178 |
+ // Replace dir with file |
|
| 179 |
+ if err := os.RemoveAll(path.Join(root, "dir2")); err != nil {
|
|
| 180 |
+ t.Fatal(err) |
|
| 181 |
+ } |
|
| 182 |
+ if err := ioutil.WriteFile(path.Join(root, "dir2"), []byte("dir2\n"), 0777); err != nil {
|
|
| 183 |
+ t.Fatal(err) |
|
| 184 |
+ } |
|
| 185 |
+ |
|
| 186 |
+ // Touch dir |
|
| 187 |
+ if err := os.Chtimes(path.Join(root, "dir3"), time.Now(), time.Now()); err != nil {
|
|
| 188 |
+ t.Fatal(err) |
|
| 189 |
+ } |
|
| 190 |
+} |
|
| 191 |
+ |
|
| 192 |
+func TestChangesDirsMutated(t *testing.T) {
|
|
| 193 |
+ src, err := ioutil.TempDir("", "docker-changes-test")
|
|
| 194 |
+ if err != nil {
|
|
| 195 |
+ t.Fatal(err) |
|
| 196 |
+ } |
|
| 197 |
+ createSampleDir(t, src) |
|
| 198 |
+ dst := src + "-copy" |
|
| 199 |
+ if err := copyDir(src, dst); err != nil {
|
|
| 200 |
+ t.Fatal(err) |
|
| 201 |
+ } |
|
| 202 |
+ mutateSampleDir(t, dst) |
|
| 203 |
+ |
|
| 204 |
+ changes, err := ChangesDirs(dst, src) |
|
| 205 |
+ if err != nil {
|
|
| 206 |
+ t.Fatal(err) |
|
| 207 |
+ } |
|
| 208 |
+ |
|
| 209 |
+ sort.Sort(byPath{changes})
|
|
| 210 |
+ |
|
| 211 |
+ expectedChanges := []Change{
|
|
| 212 |
+ {"/dir1", ChangeDelete},
|
|
| 213 |
+ {"/dir2", ChangeModify},
|
|
| 214 |
+ {"/dir3", ChangeModify},
|
|
| 215 |
+ {"/dirnew", ChangeAdd},
|
|
| 216 |
+ {"/file1", ChangeDelete},
|
|
| 217 |
+ {"/file2", ChangeModify},
|
|
| 218 |
+ {"/file3", ChangeModify},
|
|
| 219 |
+ {"/file4", ChangeModify},
|
|
| 220 |
+ {"/file5", ChangeModify},
|
|
| 221 |
+ {"/filenew", ChangeAdd},
|
|
| 222 |
+ {"/symlink1", ChangeDelete},
|
|
| 223 |
+ {"/symlink2", ChangeModify},
|
|
| 224 |
+ {"/symlinknew", ChangeAdd},
|
|
| 225 |
+ } |
|
| 226 |
+ |
|
| 227 |
+ i := 0 |
|
| 228 |
+ for ; i < max(len(changes), len(expectedChanges)); i++ {
|
|
| 229 |
+ if i >= len(expectedChanges) {
|
|
| 230 |
+ t.Fatalf("unexpected change %s\n", changes[i].String())
|
|
| 231 |
+ } |
|
| 232 |
+ if i >= len(changes) {
|
|
| 233 |
+ t.Fatalf("no change for expected change %s\n", expectedChanges[i].String())
|
|
| 234 |
+ } |
|
| 235 |
+ if changes[i].Path == expectedChanges[i].Path {
|
|
| 236 |
+ if changes[i] != expectedChanges[i] {
|
|
| 237 |
+ t.Fatalf("Wrong change for %s, expected %s, got %d\n", changes[i].Path, changes[i].String(), expectedChanges[i].String())
|
|
| 238 |
+ } |
|
| 239 |
+ } else if changes[i].Path < expectedChanges[i].Path {
|
|
| 240 |
+ t.Fatalf("unexpected change %s\n", changes[i].String())
|
|
| 241 |
+ } else {
|
|
| 242 |
+ t.Fatalf("no change for expected change %s\n", expectedChanges[i].String())
|
|
| 243 |
+ } |
|
| 244 |
+ } |
|
| 245 |
+ for ; i < len(expectedChanges); i++ {
|
|
| 246 |
+ } |
|
| 247 |
+ |
|
| 248 |
+ os.RemoveAll(src) |
|
| 249 |
+ os.RemoveAll(dst) |
|
| 250 |
+} |
|
| 251 |
+ |
|
| 252 |
+func TestApplyLayer(t *testing.T) {
|
|
| 253 |
+ t.Skip("Skipping TestApplyLayer due to known failures") // Disable this for now as it is broken
|
|
| 254 |
+ return |
|
| 255 |
+ |
|
| 256 |
+ src, err := ioutil.TempDir("", "docker-changes-test")
|
|
| 257 |
+ if err != nil {
|
|
| 258 |
+ t.Fatal(err) |
|
| 259 |
+ } |
|
| 260 |
+ createSampleDir(t, src) |
|
| 261 |
+ dst := src + "-copy" |
|
| 262 |
+ if err := copyDir(src, dst); err != nil {
|
|
| 263 |
+ t.Fatal(err) |
|
| 264 |
+ } |
|
| 265 |
+ mutateSampleDir(t, dst) |
|
| 266 |
+ |
|
| 267 |
+ changes, err := ChangesDirs(dst, src) |
|
| 268 |
+ if err != nil {
|
|
| 269 |
+ t.Fatal(err) |
|
| 270 |
+ } |
|
| 271 |
+ |
|
| 272 |
+ layer, err := ExportChanges(dst, changes) |
|
| 273 |
+ if err != nil {
|
|
| 274 |
+ t.Fatal(err) |
|
| 275 |
+ } |
|
| 276 |
+ |
|
| 277 |
+ layerCopy, err := NewTempArchive(layer, "") |
|
| 278 |
+ if err != nil {
|
|
| 279 |
+ t.Fatal(err) |
|
| 280 |
+ } |
|
| 281 |
+ |
|
| 282 |
+ if err := ApplyLayer(src, layerCopy); err != nil {
|
|
| 283 |
+ t.Fatal(err) |
|
| 284 |
+ } |
|
| 285 |
+ |
|
| 286 |
+ changes2, err := ChangesDirs(src, dst) |
|
| 287 |
+ if err != nil {
|
|
| 288 |
+ t.Fatal(err) |
|
| 289 |
+ } |
|
| 290 |
+ |
|
| 291 |
+ if len(changes2) != 0 {
|
|
| 292 |
+ t.Fatalf("Unexpected differences after re applying mutation: %v", changes)
|
|
| 293 |
+ } |
|
| 294 |
+ |
|
| 295 |
+ os.RemoveAll(src) |
|
| 296 |
+ os.RemoveAll(dst) |
|
| 297 |
+} |
| 0 | 298 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,95 @@ |
| 0 |
+package archive |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "os" |
|
| 4 |
+ "path/filepath" |
|
| 5 |
+ "strings" |
|
| 6 |
+ "syscall" |
|
| 7 |
+ "time" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+// ApplyLayer parses a diff in the standard layer format from `layer`, and |
|
| 11 |
+// applies it to the directory `dest`. |
|
| 12 |
+func ApplyLayer(dest string, layer Archive) error {
|
|
| 13 |
+ // Poor man's diff applyer in 2 steps: |
|
| 14 |
+ |
|
| 15 |
+ // Step 1: untar everything in place |
|
| 16 |
+ if err := Untar(layer, dest, nil); err != nil {
|
|
| 17 |
+ return err |
|
| 18 |
+ } |
|
| 19 |
+ |
|
| 20 |
+ modifiedDirs := make(map[string]*syscall.Stat_t) |
|
| 21 |
+ addDir := func(file string) {
|
|
| 22 |
+ d := filepath.Dir(file) |
|
| 23 |
+ if _, exists := modifiedDirs[d]; !exists {
|
|
| 24 |
+ if s, err := os.Lstat(d); err == nil {
|
|
| 25 |
+ if sys := s.Sys(); sys != nil {
|
|
| 26 |
+ if stat, ok := sys.(*syscall.Stat_t); ok {
|
|
| 27 |
+ modifiedDirs[d] = stat |
|
| 28 |
+ } |
|
| 29 |
+ } |
|
| 30 |
+ } |
|
| 31 |
+ } |
|
| 32 |
+ } |
|
| 33 |
+ |
|
| 34 |
+ // Step 2: walk for whiteouts and apply them, removing them in the process |
|
| 35 |
+ err := filepath.Walk(dest, func(fullPath string, f os.FileInfo, err error) error {
|
|
| 36 |
+ if err != nil {
|
|
| 37 |
+ if os.IsNotExist(err) {
|
|
| 38 |
+ // This happens in the case of whiteouts in parent dir removing a directory |
|
| 39 |
+ // We just ignore it |
|
| 40 |
+ return filepath.SkipDir |
|
| 41 |
+ } |
|
| 42 |
+ return err |
|
| 43 |
+ } |
|
| 44 |
+ |
|
| 45 |
+ // Rebase path |
|
| 46 |
+ path, err := filepath.Rel(dest, fullPath) |
|
| 47 |
+ if err != nil {
|
|
| 48 |
+ return err |
|
| 49 |
+ } |
|
| 50 |
+ path = filepath.Join("/", path)
|
|
| 51 |
+ |
|
| 52 |
+ // Skip AUFS metadata |
|
| 53 |
+ if matched, err := filepath.Match("/.wh..wh.*", path); err != nil {
|
|
| 54 |
+ return err |
|
| 55 |
+ } else if matched {
|
|
| 56 |
+ addDir(fullPath) |
|
| 57 |
+ if err := os.RemoveAll(fullPath); err != nil {
|
|
| 58 |
+ return err |
|
| 59 |
+ } |
|
| 60 |
+ } |
|
| 61 |
+ |
|
| 62 |
+ filename := filepath.Base(path) |
|
| 63 |
+ if strings.HasPrefix(filename, ".wh.") {
|
|
| 64 |
+ rmTargetName := filename[len(".wh."):]
|
|
| 65 |
+ rmTargetPath := filepath.Join(filepath.Dir(fullPath), rmTargetName) |
|
| 66 |
+ |
|
| 67 |
+ // Remove the file targeted by the whiteout |
|
| 68 |
+ addDir(rmTargetPath) |
|
| 69 |
+ if err := os.RemoveAll(rmTargetPath); err != nil {
|
|
| 70 |
+ return err |
|
| 71 |
+ } |
|
| 72 |
+ // Remove the whiteout itself |
|
| 73 |
+ addDir(fullPath) |
|
| 74 |
+ if err := os.RemoveAll(fullPath); err != nil {
|
|
| 75 |
+ return err |
|
| 76 |
+ } |
|
| 77 |
+ } |
|
| 78 |
+ return nil |
|
| 79 |
+ }) |
|
| 80 |
+ if err != nil {
|
|
| 81 |
+ return err |
|
| 82 |
+ } |
|
| 83 |
+ |
|
| 84 |
+ for k, v := range modifiedDirs {
|
|
| 85 |
+ aTime := time.Unix(v.Atim.Unix()) |
|
| 86 |
+ mTime := time.Unix(v.Mtim.Unix()) |
|
| 87 |
+ |
|
| 88 |
+ if err := os.Chtimes(k, aTime, mTime); err != nil {
|
|
| 89 |
+ return err |
|
| 90 |
+ } |
|
| 91 |
+ } |
|
| 92 |
+ |
|
| 93 |
+ return nil |
|
| 94 |
+} |
| ... | ... |
@@ -476,7 +476,7 @@ func (b *buildFile) Build(context io.Reader) (string, error) {
|
| 476 | 476 |
if err != nil {
|
| 477 | 477 |
return "", err |
| 478 | 478 |
} |
| 479 |
- if err := archive.Untar(context, name); err != nil {
|
|
| 479 |
+ if err := archive.Untar(context, name, nil); err != nil {
|
|
| 480 | 480 |
return "", err |
| 481 | 481 |
} |
| 482 | 482 |
defer os.RemoveAll(name) |
| 483 | 483 |
deleted file mode 100644 |
| ... | ... |
@@ -1,106 +0,0 @@ |
| 1 |
-package docker |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- "os" |
|
| 6 |
- "path/filepath" |
|
| 7 |
- "strings" |
|
| 8 |
-) |
|
| 9 |
- |
|
| 10 |
-type ChangeType int |
|
| 11 |
- |
|
| 12 |
-const ( |
|
| 13 |
- ChangeModify = iota |
|
| 14 |
- ChangeAdd |
|
| 15 |
- ChangeDelete |
|
| 16 |
-) |
|
| 17 |
- |
|
| 18 |
-type Change struct {
|
|
| 19 |
- Path string |
|
| 20 |
- Kind ChangeType |
|
| 21 |
-} |
|
| 22 |
- |
|
| 23 |
-func (change *Change) String() string {
|
|
| 24 |
- var kind string |
|
| 25 |
- switch change.Kind {
|
|
| 26 |
- case ChangeModify: |
|
| 27 |
- kind = "C" |
|
| 28 |
- case ChangeAdd: |
|
| 29 |
- kind = "A" |
|
| 30 |
- case ChangeDelete: |
|
| 31 |
- kind = "D" |
|
| 32 |
- } |
|
| 33 |
- return fmt.Sprintf("%s %s", kind, change.Path)
|
|
| 34 |
-} |
|
| 35 |
- |
|
| 36 |
-func Changes(layers []string, rw string) ([]Change, error) {
|
|
| 37 |
- var changes []Change |
|
| 38 |
- err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error {
|
|
| 39 |
- if err != nil {
|
|
| 40 |
- return err |
|
| 41 |
- } |
|
| 42 |
- |
|
| 43 |
- // Rebase path |
|
| 44 |
- path, err = filepath.Rel(rw, path) |
|
| 45 |
- if err != nil {
|
|
| 46 |
- return err |
|
| 47 |
- } |
|
| 48 |
- path = filepath.Join("/", path)
|
|
| 49 |
- |
|
| 50 |
- // Skip root |
|
| 51 |
- if path == "/" {
|
|
| 52 |
- return nil |
|
| 53 |
- } |
|
| 54 |
- |
|
| 55 |
- // Skip AUFS metadata |
|
| 56 |
- if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched {
|
|
| 57 |
- return err |
|
| 58 |
- } |
|
| 59 |
- |
|
| 60 |
- change := Change{
|
|
| 61 |
- Path: path, |
|
| 62 |
- } |
|
| 63 |
- |
|
| 64 |
- // Find out what kind of modification happened |
|
| 65 |
- file := filepath.Base(path) |
|
| 66 |
- // If there is a whiteout, then the file was removed |
|
| 67 |
- if strings.HasPrefix(file, ".wh.") {
|
|
| 68 |
- originalFile := file[len(".wh."):]
|
|
| 69 |
- change.Path = filepath.Join(filepath.Dir(path), originalFile) |
|
| 70 |
- change.Kind = ChangeDelete |
|
| 71 |
- } else {
|
|
| 72 |
- // Otherwise, the file was added |
|
| 73 |
- change.Kind = ChangeAdd |
|
| 74 |
- |
|
| 75 |
- // ...Unless it already existed in a top layer, in which case, it's a modification |
|
| 76 |
- for _, layer := range layers {
|
|
| 77 |
- stat, err := os.Stat(filepath.Join(layer, path)) |
|
| 78 |
- if err != nil && !os.IsNotExist(err) {
|
|
| 79 |
- return err |
|
| 80 |
- } |
|
| 81 |
- if err == nil {
|
|
| 82 |
- // The file existed in the top layer, so that's a modification |
|
| 83 |
- |
|
| 84 |
- // However, if it's a directory, maybe it wasn't actually modified. |
|
| 85 |
- // If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar |
|
| 86 |
- if stat.IsDir() && f.IsDir() {
|
|
| 87 |
- if f.Size() == stat.Size() && f.Mode() == stat.Mode() && f.ModTime() == stat.ModTime() {
|
|
| 88 |
- // Both directories are the same, don't record the change |
|
| 89 |
- return nil |
|
| 90 |
- } |
|
| 91 |
- } |
|
| 92 |
- change.Kind = ChangeModify |
|
| 93 |
- break |
|
| 94 |
- } |
|
| 95 |
- } |
|
| 96 |
- } |
|
| 97 |
- |
|
| 98 |
- // Record change |
|
| 99 |
- changes = append(changes, change) |
|
| 100 |
- return nil |
|
| 101 |
- }) |
|
| 102 |
- if err != nil && !os.IsNotExist(err) {
|
|
| 103 |
- return nil, err |
|
| 104 |
- } |
|
| 105 |
- return changes, nil |
|
| 106 |
-} |
| ... | ... |
@@ -463,6 +463,10 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
| 463 | 463 |
|
| 464 | 464 |
fmt.Fprintf(cli.out, "Containers: %d\n", out.Containers) |
| 465 | 465 |
fmt.Fprintf(cli.out, "Images: %d\n", out.Images) |
| 466 |
+ fmt.Fprintf(cli.out, "Driver: %s\n", out.Driver) |
|
| 467 |
+ for _, pair := range out.DriverStatus {
|
|
| 468 |
+ fmt.Fprintf(cli.out, " %s: %s\n", pair[0], pair[1]) |
|
| 469 |
+ } |
|
| 466 | 470 |
if out.Debug || os.Getenv("DEBUG") != "" {
|
| 467 | 471 |
fmt.Fprintf(cli.out, "Debug mode (server): %v\n", out.Debug) |
| 468 | 472 |
fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "")
|
| ... | ... |
@@ -1128,16 +1132,18 @@ func (cli *DockerCli) CmdImages(args ...string) error {
|
| 1128 | 1128 |
} |
| 1129 | 1129 |
|
| 1130 | 1130 |
var outs []APIImages |
| 1131 |
- err = json.Unmarshal(body, &outs) |
|
| 1132 |
- if err != nil {
|
|
| 1131 |
+ if err := json.Unmarshal(body, &outs); err != nil {
|
|
| 1133 | 1132 |
return err |
| 1134 | 1133 |
} |
| 1135 | 1134 |
|
| 1136 |
- var startImageArg = cmd.Arg(0) |
|
| 1137 |
- var startImage APIImages |
|
| 1135 |
+ var ( |
|
| 1136 |
+ startImageArg = cmd.Arg(0) |
|
| 1137 |
+ startImage APIImages |
|
| 1138 |
+ |
|
| 1139 |
+ roots []APIImages |
|
| 1140 |
+ byParent = make(map[string][]APIImages) |
|
| 1141 |
+ ) |
|
| 1138 | 1142 |
|
| 1139 |
- var roots []APIImages |
|
| 1140 |
- var byParent = make(map[string][]APIImages) |
|
| 1141 | 1143 |
for _, image := range outs {
|
| 1142 | 1144 |
if image.ParentId == "" {
|
| 1143 | 1145 |
roots = append(roots, image) |
| ... | ... |
@@ -2181,7 +2187,7 @@ func (cli *DockerCli) CmdCp(args ...string) error {
|
| 2181 | 2181 |
|
| 2182 | 2182 |
if statusCode == 200 {
|
| 2183 | 2183 |
r := bytes.NewReader(data) |
| 2184 |
- if err := archive.Untar(r, copyData.HostPath); err != nil {
|
|
| 2184 |
+ if err := archive.Untar(r, copyData.HostPath, nil); err != nil {
|
|
| 2185 | 2185 |
return err |
| 2186 | 2186 |
} |
| 2187 | 2187 |
} |
| ... | ... |
@@ -16,6 +16,7 @@ type DaemonConfig struct {
|
| 16 | 16 |
BridgeIface string |
| 17 | 17 |
DefaultIp net.IP |
| 18 | 18 |
InterContainerCommunication bool |
| 19 |
+ GraphDriver string |
|
| 19 | 20 |
} |
| 20 | 21 |
|
| 21 | 22 |
// ConfigFromJob creates and returns a new DaemonConfig object |
| ... | ... |
@@ -37,5 +38,6 @@ func ConfigFromJob(job *engine.Job) *DaemonConfig {
|
| 37 | 37 |
} |
| 38 | 38 |
config.DefaultIp = net.ParseIP(job.Getenv("DefaultIp"))
|
| 39 | 39 |
config.InterContainerCommunication = job.GetenvBool("InterContainerCommunication")
|
| 40 |
+ config.GraphDriver = job.Getenv("GraphDriver")
|
|
| 40 | 41 |
return &config |
| 41 | 42 |
} |
| ... | ... |
@@ -6,6 +6,7 @@ import ( |
| 6 | 6 |
"errors" |
| 7 | 7 |
"fmt" |
| 8 | 8 |
"github.com/dotcloud/docker/archive" |
| 9 |
+ "github.com/dotcloud/docker/graphdriver" |
|
| 9 | 10 |
"github.com/dotcloud/docker/term" |
| 10 | 11 |
"github.com/dotcloud/docker/utils" |
| 11 | 12 |
"github.com/kr/pty" |
| ... | ... |
@@ -16,7 +17,6 @@ import ( |
| 16 | 16 |
"os" |
| 17 | 17 |
"os/exec" |
| 18 | 18 |
"path" |
| 19 |
- "path/filepath" |
|
| 20 | 19 |
"strconv" |
| 21 | 20 |
"strings" |
| 22 | 21 |
"sync" |
| ... | ... |
@@ -26,8 +26,8 @@ import ( |
| 26 | 26 |
|
| 27 | 27 |
type Container struct {
|
| 28 | 28 |
sync.Mutex |
| 29 |
- |
|
| 30 |
- root string |
|
| 29 |
+ root string // Path to the "home" of the container, including metadata. |
|
| 30 |
+ rootfs string // Path to the root filesystem of the container. |
|
| 31 | 31 |
|
| 32 | 32 |
ID string |
| 33 | 33 |
|
| ... | ... |
@@ -48,6 +48,7 @@ type Container struct {
|
| 48 | 48 |
HostnamePath string |
| 49 | 49 |
HostsPath string |
| 50 | 50 |
Name string |
| 51 |
+ Driver string |
|
| 51 | 52 |
|
| 52 | 53 |
cmd *exec.Cmd |
| 53 | 54 |
stdout *utils.WriteBroadcaster |
| ... | ... |
@@ -196,8 +197,13 @@ func (settings *NetworkSettings) PortMappingAPI() []APIPort {
|
| 196 | 196 |
|
| 197 | 197 |
// Inject the io.Reader at the given path. Note: do not close the reader |
| 198 | 198 |
func (container *Container) Inject(file io.Reader, pth string) error {
|
| 199 |
+ if err := container.EnsureMounted(); err != nil {
|
|
| 200 |
+ return fmt.Errorf("inject: error mounting container %s: %s", container.ID, err)
|
|
| 201 |
+ } |
|
| 202 |
+ |
|
| 199 | 203 |
// Return error if path exists |
| 200 |
- if _, err := os.Stat(path.Join(container.rwPath(), pth)); err == nil {
|
|
| 204 |
+ destPath := path.Join(container.RootfsPath(), pth) |
|
| 205 |
+ if _, err := os.Stat(destPath); err == nil {
|
|
| 201 | 206 |
// Since err is nil, the path could be stat'd and it exists |
| 202 | 207 |
return fmt.Errorf("%s exists", pth)
|
| 203 | 208 |
} else if !os.IsNotExist(err) {
|
| ... | ... |
@@ -208,14 +214,16 @@ func (container *Container) Inject(file io.Reader, pth string) error {
|
| 208 | 208 |
} |
| 209 | 209 |
|
| 210 | 210 |
// Make sure the directory exists |
| 211 |
- if err := os.MkdirAll(path.Join(container.rwPath(), path.Dir(pth)), 0755); err != nil {
|
|
| 211 |
+ if err := os.MkdirAll(path.Join(container.RootfsPath(), path.Dir(pth)), 0755); err != nil {
|
|
| 212 | 212 |
return err |
| 213 | 213 |
} |
| 214 | 214 |
|
| 215 |
- dest, err := os.Create(path.Join(container.rwPath(), pth)) |
|
| 215 |
+ dest, err := os.Create(destPath) |
|
| 216 | 216 |
if err != nil {
|
| 217 | 217 |
return err |
| 218 | 218 |
} |
| 219 |
+ defer dest.Close() |
|
| 220 |
+ |
|
| 219 | 221 |
if _, err := io.Copy(dest, file); err != nil {
|
| 220 | 222 |
return err |
| 221 | 223 |
} |
| ... | ... |
@@ -607,6 +615,7 @@ func (container *Container) Start() (err error) {
|
| 607 | 607 |
} |
| 608 | 608 |
} |
| 609 | 609 |
|
| 610 |
+ volumesDriver := container.runtime.volumes.driver |
|
| 610 | 611 |
// Create the requested volumes if they don't exist |
| 611 | 612 |
for volPath := range container.Config.Volumes {
|
| 612 | 613 |
volPath = path.Clean(volPath) |
| ... | ... |
@@ -626,13 +635,17 @@ func (container *Container) Start() (err error) {
|
| 626 | 626 |
} |
| 627 | 627 |
// Otherwise create an directory in $ROOT/volumes/ and use that |
| 628 | 628 |
} else {
|
| 629 |
- c, err := container.runtime.volumes.Create(nil, container, "", "", nil) |
|
| 629 |
+ |
|
| 630 |
+ // Do not pass a container as the parameter for the volume creation. |
|
| 631 |
+ // The graph driver using the container's information ( Image ) to |
|
| 632 |
+ // create the parent. |
|
| 633 |
+ c, err := container.runtime.volumes.Create(nil, nil, "", "", nil) |
|
| 630 | 634 |
if err != nil {
|
| 631 | 635 |
return err |
| 632 | 636 |
} |
| 633 |
- srcPath, err = c.layer() |
|
| 637 |
+ srcPath, err = volumesDriver.Get(c.ID) |
|
| 634 | 638 |
if err != nil {
|
| 635 |
- return err |
|
| 639 |
+ return fmt.Errorf("Driver %s failed to get volume rootfs %s: %s", volumesDriver, c.ID, err)
|
|
| 636 | 640 |
} |
| 637 | 641 |
srcRW = true // RW by default |
| 638 | 642 |
} |
| ... | ... |
@@ -1231,15 +1244,14 @@ func (container *Container) Resize(h, w int) error {
|
| 1231 | 1231 |
} |
| 1232 | 1232 |
|
| 1233 | 1233 |
func (container *Container) ExportRw() (archive.Archive, error) {
|
| 1234 |
- return archive.Tar(container.rwPath(), archive.Uncompressed) |
|
| 1235 |
-} |
|
| 1236 |
- |
|
| 1237 |
-func (container *Container) RwChecksum() (string, error) {
|
|
| 1238 |
- rwData, err := archive.Tar(container.rwPath(), archive.Xz) |
|
| 1239 |
- if err != nil {
|
|
| 1240 |
- return "", err |
|
| 1234 |
+ if err := container.EnsureMounted(); err != nil {
|
|
| 1235 |
+ return nil, err |
|
| 1236 |
+ } |
|
| 1237 |
+ if container.runtime == nil {
|
|
| 1238 |
+ return nil, fmt.Errorf("Can't load storage driver for unregistered container %s", container.ID)
|
|
| 1241 | 1239 |
} |
| 1242 |
- return utils.HashData(rwData) |
|
| 1240 |
+ |
|
| 1241 |
+ return container.runtime.Diff(container) |
|
| 1243 | 1242 |
} |
| 1244 | 1243 |
|
| 1245 | 1244 |
func (container *Container) Export() (archive.Archive, error) {
|
| ... | ... |
@@ -1265,28 +1277,17 @@ func (container *Container) WaitTimeout(timeout time.Duration) error {
|
| 1265 | 1265 |
} |
| 1266 | 1266 |
|
| 1267 | 1267 |
func (container *Container) EnsureMounted() error {
|
| 1268 |
- if mounted, err := container.Mounted(); err != nil {
|
|
| 1269 |
- return err |
|
| 1270 |
- } else if mounted {
|
|
| 1271 |
- return nil |
|
| 1272 |
- } |
|
| 1268 |
+ // FIXME: EnsureMounted is deprecated because drivers are now responsible |
|
| 1269 |
+ // for re-entrant mounting in their Get() method. |
|
| 1273 | 1270 |
return container.Mount() |
| 1274 | 1271 |
} |
| 1275 | 1272 |
|
| 1276 | 1273 |
func (container *Container) Mount() error {
|
| 1277 |
- image, err := container.GetImage() |
|
| 1278 |
- if err != nil {
|
|
| 1279 |
- return err |
|
| 1280 |
- } |
|
| 1281 |
- return image.Mount(container.RootfsPath(), container.rwPath()) |
|
| 1274 |
+ return container.runtime.Mount(container) |
|
| 1282 | 1275 |
} |
| 1283 | 1276 |
|
| 1284 |
-func (container *Container) Changes() ([]Change, error) {
|
|
| 1285 |
- image, err := container.GetImage() |
|
| 1286 |
- if err != nil {
|
|
| 1287 |
- return nil, err |
|
| 1288 |
- } |
|
| 1289 |
- return image.Changes(container.rwPath()) |
|
| 1277 |
+func (container *Container) Changes() ([]archive.Change, error) {
|
|
| 1278 |
+ return container.runtime.Changes(container) |
|
| 1290 | 1279 |
} |
| 1291 | 1280 |
|
| 1292 | 1281 |
func (container *Container) GetImage() (*Image, error) {
|
| ... | ... |
@@ -1296,18 +1297,8 @@ func (container *Container) GetImage() (*Image, error) {
|
| 1296 | 1296 |
return container.runtime.graph.Get(container.Image) |
| 1297 | 1297 |
} |
| 1298 | 1298 |
|
| 1299 |
-func (container *Container) Mounted() (bool, error) {
|
|
| 1300 |
- return Mounted(container.RootfsPath()) |
|
| 1301 |
-} |
|
| 1302 |
- |
|
| 1303 | 1299 |
func (container *Container) Unmount() error {
|
| 1304 |
- if _, err := os.Stat(container.RootfsPath()); err != nil {
|
|
| 1305 |
- if os.IsNotExist(err) {
|
|
| 1306 |
- return nil |
|
| 1307 |
- } |
|
| 1308 |
- return err |
|
| 1309 |
- } |
|
| 1310 |
- return Unmount(container.RootfsPath()) |
|
| 1300 |
+ return container.runtime.Unmount(container) |
|
| 1311 | 1301 |
} |
| 1312 | 1302 |
|
| 1313 | 1303 |
func (container *Container) logPath(name string) string {
|
| ... | ... |
@@ -1336,11 +1327,7 @@ func (container *Container) lxcConfigPath() string {
|
| 1336 | 1336 |
|
| 1337 | 1337 |
// This method must be exported to be used from the lxc template |
| 1338 | 1338 |
func (container *Container) RootfsPath() string {
|
| 1339 |
- return path.Join(container.root, "rootfs") |
|
| 1340 |
-} |
|
| 1341 |
- |
|
| 1342 |
-func (container *Container) rwPath() string {
|
|
| 1343 |
- return path.Join(container.root, "rw") |
|
| 1339 |
+ return container.rootfs |
|
| 1344 | 1340 |
} |
| 1345 | 1341 |
|
| 1346 | 1342 |
func validateID(id string) error {
|
| ... | ... |
@@ -1352,49 +1339,38 @@ func validateID(id string) error {
|
| 1352 | 1352 |
|
| 1353 | 1353 |
// GetSize, return real size, virtual size |
| 1354 | 1354 |
func (container *Container) GetSize() (int64, int64) {
|
| 1355 |
- var sizeRw, sizeRootfs int64 |
|
| 1356 |
- data := make(map[uint64]bool) |
|
| 1355 |
+ var ( |
|
| 1356 |
+ sizeRw, sizeRootfs int64 |
|
| 1357 |
+ err error |
|
| 1358 |
+ driver = container.runtime.driver |
|
| 1359 |
+ ) |
|
| 1357 | 1360 |
|
| 1358 |
- filepath.Walk(container.rwPath(), func(path string, fileInfo os.FileInfo, err error) error {
|
|
| 1359 |
- if fileInfo == nil {
|
|
| 1360 |
- return nil |
|
| 1361 |
+ if err := container.EnsureMounted(); err != nil {
|
|
| 1362 |
+ utils.Errorf("Warning: failed to compute size of container rootfs %s: %s", container.ID, err)
|
|
| 1363 |
+ return sizeRw, sizeRootfs |
|
| 1364 |
+ } |
|
| 1365 |
+ |
|
| 1366 |
+ if differ, ok := container.runtime.driver.(graphdriver.Differ); ok {
|
|
| 1367 |
+ sizeRw, err = differ.DiffSize(container.ID) |
|
| 1368 |
+ if err != nil {
|
|
| 1369 |
+ utils.Errorf("Warning: driver %s couldn't return diff size of container %s: %s", driver, container.ID, err)
|
|
| 1370 |
+ // FIXME: GetSize should return an error. Not changing it now in case |
|
| 1371 |
+ // there is a side-effect. |
|
| 1372 |
+ sizeRw = -1 |
|
| 1361 | 1373 |
} |
| 1362 |
- size := fileInfo.Size() |
|
| 1363 |
- if size == 0 {
|
|
| 1364 |
- return nil |
|
| 1374 |
+ } else {
|
|
| 1375 |
+ changes, _ := container.Changes() |
|
| 1376 |
+ if changes != nil {
|
|
| 1377 |
+ sizeRw = archive.ChangesSize(container.RootfsPath(), changes) |
|
| 1378 |
+ } else {
|
|
| 1379 |
+ sizeRw = -1 |
|
| 1365 | 1380 |
} |
| 1381 |
+ } |
|
| 1366 | 1382 |
|
| 1367 |
- inode := fileInfo.Sys().(*syscall.Stat_t).Ino |
|
| 1368 |
- if _, entryExists := data[inode]; entryExists {
|
|
| 1369 |
- return nil |
|
| 1383 |
+ if _, err = os.Stat(container.RootfsPath()); err != nil {
|
|
| 1384 |
+ if sizeRootfs, err = utils.TreeSize(container.RootfsPath()); err != nil {
|
|
| 1385 |
+ sizeRootfs = -1 |
|
| 1370 | 1386 |
} |
| 1371 |
- data[inode] = false |
|
| 1372 |
- |
|
| 1373 |
- sizeRw += size |
|
| 1374 |
- return nil |
|
| 1375 |
- }) |
|
| 1376 |
- |
|
| 1377 |
- data = make(map[uint64]bool) |
|
| 1378 |
- _, err := os.Stat(container.RootfsPath()) |
|
| 1379 |
- if err == nil {
|
|
| 1380 |
- filepath.Walk(container.RootfsPath(), func(path string, fileInfo os.FileInfo, err error) error {
|
|
| 1381 |
- if fileInfo == nil {
|
|
| 1382 |
- return nil |
|
| 1383 |
- } |
|
| 1384 |
- size := fileInfo.Size() |
|
| 1385 |
- if size == 0 {
|
|
| 1386 |
- return nil |
|
| 1387 |
- } |
|
| 1388 |
- |
|
| 1389 |
- inode := fileInfo.Sys().(*syscall.Stat_t).Ino |
|
| 1390 |
- if _, entryExists := data[inode]; entryExists {
|
|
| 1391 |
- return nil |
|
| 1392 |
- } |
|
| 1393 |
- data[inode] = false |
|
| 1394 |
- |
|
| 1395 |
- sizeRootfs += size |
|
| 1396 |
- return nil |
|
| 1397 |
- }) |
|
| 1398 | 1387 |
} |
| 1399 | 1388 |
return sizeRw, sizeRootfs |
| 1400 | 1389 |
} |
| ... | ... |
@@ -1417,7 +1393,11 @@ func (container *Container) Copy(resource string) (archive.Archive, error) {
|
| 1417 | 1417 |
filter = []string{path.Base(basePath)}
|
| 1418 | 1418 |
basePath = path.Dir(basePath) |
| 1419 | 1419 |
} |
| 1420 |
- return archive.TarFilter(basePath, archive.Uncompressed, filter) |
|
| 1420 |
+ return archive.TarFilter(basePath, &archive.TarOptions{
|
|
| 1421 |
+ Compression: archive.Uncompressed, |
|
| 1422 |
+ Includes: filter, |
|
| 1423 |
+ Recursive: true, |
|
| 1424 |
+ }) |
|
| 1421 | 1425 |
} |
| 1422 | 1426 |
|
| 1423 | 1427 |
// Returns true if the container exposes a certain port |
| 1424 | 1428 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,170 @@ |
| 0 |
+package main |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "flag" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "github.com/dotcloud/docker/graphdriver/devmapper" |
|
| 6 |
+ "os" |
|
| 7 |
+ "path" |
|
| 8 |
+ "sort" |
|
| 9 |
+ "strconv" |
|
| 10 |
+ "strings" |
|
| 11 |
+) |
|
| 12 |
+ |
|
| 13 |
+func usage() {
|
|
| 14 |
+ 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]) |
|
| 15 |
+ flag.PrintDefaults() |
|
| 16 |
+ os.Exit(1) |
|
| 17 |
+} |
|
| 18 |
+ |
|
| 19 |
+func byteSizeFromString(arg string) (int64, error) {
|
|
| 20 |
+ digits := "" |
|
| 21 |
+ rest := "" |
|
| 22 |
+ last := strings.LastIndexAny(arg, "0123456789") |
|
| 23 |
+ if last >= 0 {
|
|
| 24 |
+ digits = arg[:last+1] |
|
| 25 |
+ rest = arg[last+1:] |
|
| 26 |
+ } |
|
| 27 |
+ |
|
| 28 |
+ val, err := strconv.ParseInt(digits, 10, 64) |
|
| 29 |
+ if err != nil {
|
|
| 30 |
+ return val, err |
|
| 31 |
+ } |
|
| 32 |
+ |
|
| 33 |
+ rest = strings.ToLower(strings.TrimSpace(rest)) |
|
| 34 |
+ |
|
| 35 |
+ var multiplier int64 = 1 |
|
| 36 |
+ switch rest {
|
|
| 37 |
+ case "": |
|
| 38 |
+ multiplier = 1 |
|
| 39 |
+ case "k", "kb": |
|
| 40 |
+ multiplier = 1024 |
|
| 41 |
+ case "m", "mb": |
|
| 42 |
+ multiplier = 1024 * 1024 |
|
| 43 |
+ case "g", "gb": |
|
| 44 |
+ multiplier = 1024 * 1024 * 1024 |
|
| 45 |
+ case "t", "tb": |
|
| 46 |
+ multiplier = 1024 * 1024 * 1024 * 1024 |
|
| 47 |
+ default: |
|
| 48 |
+ return 0, fmt.Errorf("Unknown size unit: %s", rest)
|
|
| 49 |
+ } |
|
| 50 |
+ |
|
| 51 |
+ return val * multiplier, nil |
|
| 52 |
+} |
|
| 53 |
+ |
|
| 54 |
+func main() {
|
|
| 55 |
+ root := flag.String("r", "/var/lib/docker", "Docker root dir")
|
|
| 56 |
+ flDebug := flag.Bool("D", false, "Debug mode")
|
|
| 57 |
+ |
|
| 58 |
+ flag.Parse() |
|
| 59 |
+ |
|
| 60 |
+ if *flDebug {
|
|
| 61 |
+ os.Setenv("DEBUG", "1")
|
|
| 62 |
+ } |
|
| 63 |
+ |
|
| 64 |
+ if flag.NArg() < 1 {
|
|
| 65 |
+ usage() |
|
| 66 |
+ } |
|
| 67 |
+ |
|
| 68 |
+ args := flag.Args() |
|
| 69 |
+ |
|
| 70 |
+ home := path.Join(*root, "devicemapper") |
|
| 71 |
+ devices, err := devmapper.NewDeviceSet(home, false) |
|
| 72 |
+ if err != nil {
|
|
| 73 |
+ fmt.Println("Can't initialize device mapper: ", err)
|
|
| 74 |
+ os.Exit(1) |
|
| 75 |
+ } |
|
| 76 |
+ |
|
| 77 |
+ switch args[0] {
|
|
| 78 |
+ case "status": |
|
| 79 |
+ status := devices.Status() |
|
| 80 |
+ fmt.Printf("Pool name: %s\n", status.PoolName)
|
|
| 81 |
+ fmt.Printf("Data Loopback file: %s\n", status.DataLoopback)
|
|
| 82 |
+ fmt.Printf("Metadata Loopback file: %s\n", status.MetadataLoopback)
|
|
| 83 |
+ fmt.Printf("Sector size: %d\n", status.SectorSize)
|
|
| 84 |
+ 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))
|
|
| 85 |
+ 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))
|
|
| 86 |
+ break |
|
| 87 |
+ case "list": |
|
| 88 |
+ ids := devices.List() |
|
| 89 |
+ sort.Strings(ids) |
|
| 90 |
+ for _, id := range ids {
|
|
| 91 |
+ fmt.Println(id) |
|
| 92 |
+ } |
|
| 93 |
+ break |
|
| 94 |
+ case "device": |
|
| 95 |
+ if flag.NArg() < 2 {
|
|
| 96 |
+ usage() |
|
| 97 |
+ } |
|
| 98 |
+ status, err := devices.GetDeviceStatus(args[1]) |
|
| 99 |
+ if err != nil {
|
|
| 100 |
+ fmt.Println("Can't get device info: ", err)
|
|
| 101 |
+ os.Exit(1) |
|
| 102 |
+ } |
|
| 103 |
+ fmt.Printf("Id: %d\n", status.DeviceId)
|
|
| 104 |
+ fmt.Printf("Size: %d\n", status.Size)
|
|
| 105 |
+ fmt.Printf("Transaction Id: %d\n", status.TransactionId)
|
|
| 106 |
+ fmt.Printf("Size in Sectors: %d\n", status.SizeInSectors)
|
|
| 107 |
+ fmt.Printf("Mapped Sectors: %d\n", status.MappedSectors)
|
|
| 108 |
+ fmt.Printf("Highest Mapped Sector: %d\n", status.HighestMappedSector)
|
|
| 109 |
+ break |
|
| 110 |
+ case "resize": |
|
| 111 |
+ if flag.NArg() < 2 {
|
|
| 112 |
+ usage() |
|
| 113 |
+ } |
|
| 114 |
+ |
|
| 115 |
+ size, err := byteSizeFromString(args[1]) |
|
| 116 |
+ if err != nil {
|
|
| 117 |
+ fmt.Println("Invalid size: ", err)
|
|
| 118 |
+ os.Exit(1) |
|
| 119 |
+ } |
|
| 120 |
+ |
|
| 121 |
+ err = devices.ResizePool(size) |
|
| 122 |
+ if err != nil {
|
|
| 123 |
+ fmt.Println("Error resizeing pool: ", err)
|
|
| 124 |
+ os.Exit(1) |
|
| 125 |
+ } |
|
| 126 |
+ |
|
| 127 |
+ break |
|
| 128 |
+ case "snap": |
|
| 129 |
+ if flag.NArg() < 3 {
|
|
| 130 |
+ usage() |
|
| 131 |
+ } |
|
| 132 |
+ |
|
| 133 |
+ err := devices.AddDevice(args[1], args[2]) |
|
| 134 |
+ if err != nil {
|
|
| 135 |
+ fmt.Println("Can't create snap device: ", err)
|
|
| 136 |
+ os.Exit(1) |
|
| 137 |
+ } |
|
| 138 |
+ break |
|
| 139 |
+ case "remove": |
|
| 140 |
+ if flag.NArg() < 2 {
|
|
| 141 |
+ usage() |
|
| 142 |
+ } |
|
| 143 |
+ |
|
| 144 |
+ err := devices.RemoveDevice(args[1]) |
|
| 145 |
+ if err != nil {
|
|
| 146 |
+ fmt.Println("Can't remove device: ", err)
|
|
| 147 |
+ os.Exit(1) |
|
| 148 |
+ } |
|
| 149 |
+ break |
|
| 150 |
+ case "mount": |
|
| 151 |
+ if flag.NArg() < 3 {
|
|
| 152 |
+ usage() |
|
| 153 |
+ } |
|
| 154 |
+ |
|
| 155 |
+ err := devices.MountDevice(args[1], args[2], false) |
|
| 156 |
+ if err != nil {
|
|
| 157 |
+ fmt.Println("Can't create snap device: ", err)
|
|
| 158 |
+ os.Exit(1) |
|
| 159 |
+ } |
|
| 160 |
+ break |
|
| 161 |
+ default: |
|
| 162 |
+ fmt.Printf("Unknown command %s\n", args[0])
|
|
| 163 |
+ usage() |
|
| 164 |
+ |
|
| 165 |
+ os.Exit(1) |
|
| 166 |
+ } |
|
| 167 |
+ |
|
| 168 |
+ return |
|
| 169 |
+} |
| ... | ... |
@@ -25,19 +25,20 @@ func main() {
|
| 25 | 25 |
} |
| 26 | 26 |
// FIXME: Switch d and D ? (to be more sshd like) |
| 27 | 27 |
flVersion := flag.Bool("v", false, "Print version information and quit")
|
| 28 |
- flDaemon := flag.Bool("d", false, "Daemon mode")
|
|
| 29 |
- flDebug := flag.Bool("D", false, "Debug mode")
|
|
| 28 |
+ flDaemon := flag.Bool("d", false, "Enable daemon mode")
|
|
| 29 |
+ flDebug := flag.Bool("D", false, "Enable debug mode")
|
|
| 30 | 30 |
flAutoRestart := flag.Bool("r", true, "Restart previously running containers")
|
| 31 |
- bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge. Use 'none' to disable container networking")
|
|
| 32 |
- pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
|
|
| 33 |
- flRoot := flag.String("g", "/var/lib/docker", "Path to use as the root of the docker runtime.")
|
|
| 34 |
- flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
|
|
| 35 |
- flDns := flag.String("dns", "", "Set custom dns servers")
|
|
| 31 |
+ bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge; use 'none' to disable container networking")
|
|
| 32 |
+ pidfile := flag.String("p", "/var/run/docker.pid", "Path to use for daemon PID file")
|
|
| 33 |
+ flRoot := flag.String("g", "/var/lib/docker", "Path to use as the root of the docker runtime")
|
|
| 34 |
+ flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS headers in the remote API")
|
|
| 35 |
+ flDns := flag.String("dns", "", "Force docker to use specific DNS servers")
|
|
| 36 | 36 |
flHosts := utils.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)}
|
| 37 |
- flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use") |
|
| 38 |
- flEnableIptables := flag.Bool("iptables", true, "Disable iptables within docker")
|
|
| 39 |
- flDefaultIp := flag.String("ip", "0.0.0.0", "Default ip address to use when binding a containers ports")
|
|
| 37 |
+ flag.Var(&flHosts, "H", "Multiple tcp://host:port or unix://path/to/socket to bind in daemon mode, single connection otherwise") |
|
| 38 |
+ flEnableIptables := flag.Bool("iptables", true, "Disable docker's addition of iptables rules")
|
|
| 39 |
+ flDefaultIp := flag.String("ip", "0.0.0.0", "Default IP address to use when binding container ports")
|
|
| 40 | 40 |
flInterContainerComm := flag.Bool("icc", true, "Enable inter-container communication")
|
| 41 |
+ flGraphDriver := flag.String("s", "", "Force the docker runtime to use a specific storage driver")
|
|
| 41 | 42 |
|
| 42 | 43 |
flag.Parse() |
| 43 | 44 |
|
| ... | ... |
@@ -82,6 +83,7 @@ func main() {
|
| 82 | 82 |
job.Setenv("BridgeIface", *bridgeName)
|
| 83 | 83 |
job.Setenv("DefaultIp", *flDefaultIp)
|
| 84 | 84 |
job.SetenvBool("InterContainerCommunication", *flInterContainerComm)
|
| 85 |
+ job.Setenv("GraphDriver", *flGraphDriver)
|
|
| 85 | 86 |
if err := job.Run(); err != nil {
|
| 86 | 87 |
log.Fatal(err) |
| 87 | 88 |
} |
| ... | ... |
@@ -9,7 +9,7 @@ run apt-get install -y python-setuptools make |
| 9 | 9 |
run easy_install pip |
| 10 | 10 |
#from docs/requirements.txt, but here to increase cacheability |
| 11 | 11 |
run pip install Sphinx==1.1.3 |
| 12 |
-run pip install sphinxcontrib-httpdomain==1.1.8 |
|
| 12 |
+run pip install sphinxcontrib-httpdomain==1.1.9 |
|
| 13 | 13 |
add . /docs |
| 14 | 14 |
run cd /docs; make docs |
| 15 | 15 |
|
| ... | ... |
@@ -18,6 +18,38 @@ To list available commands, either run ``docker`` with no parameters or execute |
| 18 | 18 |
|
| 19 | 19 |
... |
| 20 | 20 |
|
| 21 |
+.. _cli_daemon: |
|
| 22 |
+ |
|
| 23 |
+``daemon`` |
|
| 24 |
+---------- |
|
| 25 |
+ |
|
| 26 |
+:: |
|
| 27 |
+ |
|
| 28 |
+ Usage of docker: |
|
| 29 |
+ -D=false: Enable debug mode |
|
| 30 |
+ -H=[unix:///var/run/docker.sock]: Multiple tcp://host:port or unix://path/to/socket to bind in daemon mode, single connection otherwise |
|
| 31 |
+ -api-enable-cors=false: Enable CORS headers in the remote API |
|
| 32 |
+ -b="": Attach containers to a pre-existing network bridge; use 'none' to disable container networking |
|
| 33 |
+ -d=false: Enable daemon mode |
|
| 34 |
+ -dns="": Force docker to use specific DNS servers |
|
| 35 |
+ -g="/var/lib/docker": Path to use as the root of the docker runtime |
|
| 36 |
+ -icc=true: Enable inter-container communication |
|
| 37 |
+ -ip="0.0.0.0": Default IP address to use when binding container ports |
|
| 38 |
+ -iptables=true: Disable docker's addition of iptables rules |
|
| 39 |
+ -p="/var/run/docker.pid": Path to use for daemon PID file |
|
| 40 |
+ -r=true: Restart previously running containers |
|
| 41 |
+ -s="": Force the docker runtime to use a specific storage driver |
|
| 42 |
+ -v=false: Print version information and quit |
|
| 43 |
+ |
|
| 44 |
+The docker daemon is the persistent process that manages containers. Docker uses the same binary for both the |
|
| 45 |
+daemon and client. To run the daemon you provide the ``-d`` flag. |
|
| 46 |
+ |
|
| 47 |
+To force docker to use devicemapper as the storage driver, use ``docker -d -s devicemapper`` |
|
| 48 |
+ |
|
| 49 |
+To set the dns server for all docker containers, use ``docker -d -dns 8.8.8.8`` |
|
| 50 |
+ |
|
| 51 |
+To run the daemon with debug output, use ``docker -d -D`` |
|
| 52 |
+ |
|
| 21 | 53 |
.. _cli_attach: |
| 22 | 54 |
|
| 23 | 55 |
``attach`` |
| ... | ... |
@@ -11,10 +11,10 @@ In short, Docker has the following kernel requirements: |
| 11 | 11 |
|
| 12 | 12 |
- Linux version 3.8 or above. |
| 13 | 13 |
|
| 14 |
-- `AUFS support <http://aufs.sourceforge.net/>`_. |
|
| 15 |
- |
|
| 16 | 14 |
- Cgroups and namespaces must be enabled. |
| 17 | 15 |
|
| 16 |
+*Note: as of 0.7 docker no longer requires aufs. AUFS support is still available as an optional driver.* |
|
| 17 |
+ |
|
| 18 | 18 |
The officially supported kernel is the one recommended by the |
| 19 | 19 |
:ref:`ubuntu_linux` installation path. It is the one that most developers |
| 20 | 20 |
will use, and the one that receives the most attention from the core |
| ... | ... |
@@ -58,17 +58,6 @@ detects something older than 3.8. |
| 58 | 58 |
See issue `#407 <https://github.com/dotcloud/docker/issues/407>`_ for details. |
| 59 | 59 |
|
| 60 | 60 |
|
| 61 |
-AUFS support |
|
| 62 |
- |
|
| 63 |
-Docker currently relies on AUFS, an unioning filesystem. |
|
| 64 |
-While AUFS is included in the kernels built by the Debian and Ubuntu |
|
| 65 |
-distributions, is not part of the standard kernel. This means that if |
|
| 66 |
-you decide to roll your own kernel, you will have to patch your |
|
| 67 |
-kernel tree to add AUFS. The process is documented on |
|
| 68 |
-`AUFS webpage <http://aufs.sourceforge.net/>`_. |
|
| 69 |
- |
|
| 70 |
- |
|
| 71 | 61 |
Cgroups and namespaces |
| 72 | 62 |
---------------------- |
| 73 | 63 |
|
| ... | ... |
@@ -14,16 +14,11 @@ Ubuntu Linux |
| 14 | 14 |
|
| 15 | 15 |
.. include:: install_header.inc |
| 16 | 16 |
|
| 17 |
-Right now, the officially supported distribution are: |
|
| 17 |
+Docker is supported on the following versions of Ubuntu: |
|
| 18 | 18 |
|
| 19 | 19 |
- :ref:`ubuntu_precise` |
| 20 | 20 |
- :ref:`ubuntu_raring` |
| 21 | 21 |
|
| 22 |
-Docker has the following dependencies |
|
| 23 |
- |
|
| 24 |
-* Linux kernel 3.8 (read more about :ref:`kernel`) |
|
| 25 |
-* AUFS file system support (we are working on BTRFS support as an alternative) |
|
| 26 |
- |
|
| 27 | 22 |
Please read :ref:`ufw`, if you plan to use `UFW (Uncomplicated |
| 28 | 23 |
Firewall) <https://help.ubuntu.com/community/UFW>`_ |
| 29 | 24 |
|
| ... | ... |
@@ -107,10 +102,13 @@ Ubuntu Raring 13.04 (64 bit) |
| 107 | 107 |
Dependencies |
| 108 | 108 |
------------ |
| 109 | 109 |
|
| 110 |
-**AUFS filesystem support** |
|
| 110 |
+**Optional AUFS filesystem support** |
|
| 111 | 111 |
|
| 112 | 112 |
Ubuntu Raring already comes with the 3.8 kernel, so we don't need to install it. However, not all systems |
| 113 |
-have AUFS filesystem support enabled, so we need to install it. |
|
| 113 |
+have AUFS filesystem support enabled. AUFS support is optional as of version 0.7, but it's still available as |
|
| 114 |
+a driver and we recommend using it if you can. |
|
| 115 |
+ |
|
| 116 |
+To make sure aufs is installed, run the following commands: |
|
| 114 | 117 |
|
| 115 | 118 |
.. code-block:: bash |
| 116 | 119 |
|
| 2 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,473 +0,0 @@ |
| 1 |
-package gograph |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "database/sql" |
|
| 5 |
- "fmt" |
|
| 6 |
- "path" |
|
| 7 |
- "sync" |
|
| 8 |
-) |
|
| 9 |
- |
|
| 10 |
-const ( |
|
| 11 |
- createEntityTable = ` |
|
| 12 |
- CREATE TABLE IF NOT EXISTS entity ( |
|
| 13 |
- id text NOT NULL PRIMARY KEY |
|
| 14 |
- );` |
|
| 15 |
- |
|
| 16 |
- createEdgeTable = ` |
|
| 17 |
- CREATE TABLE IF NOT EXISTS edge ( |
|
| 18 |
- "entity_id" text NOT NULL, |
|
| 19 |
- "parent_id" text NULL, |
|
| 20 |
- "name" text NOT NULL, |
|
| 21 |
- CONSTRAINT "parent_fk" FOREIGN KEY ("parent_id") REFERENCES "entity" ("id"),
|
|
| 22 |
- CONSTRAINT "entity_fk" FOREIGN KEY ("entity_id") REFERENCES "entity" ("id")
|
|
| 23 |
- ); |
|
| 24 |
- ` |
|
| 25 |
- |
|
| 26 |
- createEdgeIndices = ` |
|
| 27 |
- CREATE UNIQUE INDEX IF NOT EXISTS "name_parent_ix" ON "edge" (parent_id, name); |
|
| 28 |
- ` |
|
| 29 |
-) |
|
| 30 |
- |
|
| 31 |
-// Entity with a unique id |
|
| 32 |
-type Entity struct {
|
|
| 33 |
- id string |
|
| 34 |
-} |
|
| 35 |
- |
|
| 36 |
-// An Edge connects two entities together |
|
| 37 |
-type Edge struct {
|
|
| 38 |
- EntityID string |
|
| 39 |
- Name string |
|
| 40 |
- ParentID string |
|
| 41 |
-} |
|
| 42 |
- |
|
| 43 |
-type Entities map[string]*Entity |
|
| 44 |
-type Edges []*Edge |
|
| 45 |
- |
|
| 46 |
-type WalkFunc func(fullPath string, entity *Entity) error |
|
| 47 |
- |
|
| 48 |
-// Graph database for storing entities and their relationships |
|
| 49 |
-type Database struct {
|
|
| 50 |
- conn *sql.DB |
|
| 51 |
- mux sync.RWMutex |
|
| 52 |
-} |
|
| 53 |
- |
|
| 54 |
-// Create a new graph database initialized with a root entity |
|
| 55 |
-func NewDatabase(conn *sql.DB, init bool) (*Database, error) {
|
|
| 56 |
- if conn == nil {
|
|
| 57 |
- return nil, fmt.Errorf("Database connection cannot be nil")
|
|
| 58 |
- } |
|
| 59 |
- db := &Database{conn: conn}
|
|
| 60 |
- |
|
| 61 |
- if init {
|
|
| 62 |
- if _, err := conn.Exec(createEntityTable); err != nil {
|
|
| 63 |
- return nil, err |
|
| 64 |
- } |
|
| 65 |
- if _, err := conn.Exec(createEdgeTable); err != nil {
|
|
| 66 |
- return nil, err |
|
| 67 |
- } |
|
| 68 |
- if _, err := conn.Exec(createEdgeIndices); err != nil {
|
|
| 69 |
- return nil, err |
|
| 70 |
- } |
|
| 71 |
- |
|
| 72 |
- rollback := func() {
|
|
| 73 |
- conn.Exec("ROLLBACK")
|
|
| 74 |
- } |
|
| 75 |
- |
|
| 76 |
- // Create root entities |
|
| 77 |
- if _, err := conn.Exec("BEGIN"); err != nil {
|
|
| 78 |
- return nil, err |
|
| 79 |
- } |
|
| 80 |
- if _, err := conn.Exec("INSERT INTO entity (id) VALUES (?);", "0"); err != nil {
|
|
| 81 |
- rollback() |
|
| 82 |
- return nil, err |
|
| 83 |
- } |
|
| 84 |
- |
|
| 85 |
- if _, err := conn.Exec("INSERT INTO edge (entity_id, name) VALUES(?,?);", "0", "/"); err != nil {
|
|
| 86 |
- rollback() |
|
| 87 |
- return nil, err |
|
| 88 |
- } |
|
| 89 |
- |
|
| 90 |
- if _, err := conn.Exec("COMMIT"); err != nil {
|
|
| 91 |
- return nil, err |
|
| 92 |
- } |
|
| 93 |
- } |
|
| 94 |
- return db, nil |
|
| 95 |
-} |
|
| 96 |
- |
|
| 97 |
-// Close the underlying connection to the database |
|
| 98 |
-func (db *Database) Close() error {
|
|
| 99 |
- return db.conn.Close() |
|
| 100 |
-} |
|
| 101 |
- |
|
| 102 |
-// Set the entity id for a given path |
|
| 103 |
-func (db *Database) Set(fullPath, id string) (*Entity, error) {
|
|
| 104 |
- db.mux.Lock() |
|
| 105 |
- defer db.mux.Unlock() |
|
| 106 |
- |
|
| 107 |
- rollback := func() {
|
|
| 108 |
- db.conn.Exec("ROLLBACK")
|
|
| 109 |
- } |
|
| 110 |
- if _, err := db.conn.Exec("BEGIN EXCLUSIVE"); err != nil {
|
|
| 111 |
- return nil, err |
|
| 112 |
- } |
|
| 113 |
- var entityId string |
|
| 114 |
- if err := db.conn.QueryRow("SELECT id FROM entity WHERE id = ?;", id).Scan(&entityId); err != nil {
|
|
| 115 |
- if err == sql.ErrNoRows {
|
|
| 116 |
- if _, err := db.conn.Exec("INSERT INTO entity (id) VALUES(?);", id); err != nil {
|
|
| 117 |
- rollback() |
|
| 118 |
- return nil, err |
|
| 119 |
- } |
|
| 120 |
- } else {
|
|
| 121 |
- rollback() |
|
| 122 |
- return nil, err |
|
| 123 |
- } |
|
| 124 |
- } |
|
| 125 |
- e := &Entity{id}
|
|
| 126 |
- |
|
| 127 |
- parentPath, name := splitPath(fullPath) |
|
| 128 |
- if err := db.setEdge(parentPath, name, e); err != nil {
|
|
| 129 |
- rollback() |
|
| 130 |
- return nil, err |
|
| 131 |
- } |
|
| 132 |
- |
|
| 133 |
- if _, err := db.conn.Exec("COMMIT"); err != nil {
|
|
| 134 |
- return nil, err |
|
| 135 |
- } |
|
| 136 |
- return e, nil |
|
| 137 |
-} |
|
| 138 |
- |
|
| 139 |
-// Return true if a name already exists in the database |
|
| 140 |
-func (db *Database) Exists(name string) bool {
|
|
| 141 |
- db.mux.RLock() |
|
| 142 |
- defer db.mux.RUnlock() |
|
| 143 |
- |
|
| 144 |
- e, err := db.get(name) |
|
| 145 |
- if err != nil {
|
|
| 146 |
- return false |
|
| 147 |
- } |
|
| 148 |
- return e != nil |
|
| 149 |
-} |
|
| 150 |
- |
|
| 151 |
-func (db *Database) setEdge(parentPath, name string, e *Entity) error {
|
|
| 152 |
- parent, err := db.get(parentPath) |
|
| 153 |
- if err != nil {
|
|
| 154 |
- return err |
|
| 155 |
- } |
|
| 156 |
- if parent.id == e.id {
|
|
| 157 |
- return fmt.Errorf("Cannot set self as child")
|
|
| 158 |
- } |
|
| 159 |
- |
|
| 160 |
- if _, err := db.conn.Exec("INSERT INTO edge (parent_id, name, entity_id) VALUES (?,?,?);", parent.id, name, e.id); err != nil {
|
|
| 161 |
- return err |
|
| 162 |
- } |
|
| 163 |
- return nil |
|
| 164 |
-} |
|
| 165 |
- |
|
| 166 |
-// Return the root "/" entity for the database |
|
| 167 |
-func (db *Database) RootEntity() *Entity {
|
|
| 168 |
- return &Entity{
|
|
| 169 |
- id: "0", |
|
| 170 |
- } |
|
| 171 |
-} |
|
| 172 |
- |
|
| 173 |
-// Return the entity for a given path |
|
| 174 |
-func (db *Database) Get(name string) *Entity {
|
|
| 175 |
- db.mux.RLock() |
|
| 176 |
- defer db.mux.RUnlock() |
|
| 177 |
- |
|
| 178 |
- e, err := db.get(name) |
|
| 179 |
- if err != nil {
|
|
| 180 |
- return nil |
|
| 181 |
- } |
|
| 182 |
- return e |
|
| 183 |
-} |
|
| 184 |
- |
|
| 185 |
-func (db *Database) get(name string) (*Entity, error) {
|
|
| 186 |
- e := db.RootEntity() |
|
| 187 |
- // We always know the root name so return it if |
|
| 188 |
- // it is requested |
|
| 189 |
- if name == "/" {
|
|
| 190 |
- return e, nil |
|
| 191 |
- } |
|
| 192 |
- |
|
| 193 |
- parts := split(name) |
|
| 194 |
- for i := 1; i < len(parts); i++ {
|
|
| 195 |
- p := parts[i] |
|
| 196 |
- if p == "" {
|
|
| 197 |
- continue |
|
| 198 |
- } |
|
| 199 |
- |
|
| 200 |
- next := db.child(e, p) |
|
| 201 |
- if next == nil {
|
|
| 202 |
- return nil, fmt.Errorf("Cannot find child for %s", name)
|
|
| 203 |
- } |
|
| 204 |
- e = next |
|
| 205 |
- } |
|
| 206 |
- return e, nil |
|
| 207 |
- |
|
| 208 |
-} |
|
| 209 |
- |
|
| 210 |
-// List all entities by from the name |
|
| 211 |
-// The key will be the full path of the entity |
|
| 212 |
-func (db *Database) List(name string, depth int) Entities {
|
|
| 213 |
- db.mux.RLock() |
|
| 214 |
- defer db.mux.RUnlock() |
|
| 215 |
- |
|
| 216 |
- out := Entities{}
|
|
| 217 |
- e, err := db.get(name) |
|
| 218 |
- if err != nil {
|
|
| 219 |
- return out |
|
| 220 |
- } |
|
| 221 |
- |
|
| 222 |
- children, err := db.children(e, name, depth, nil) |
|
| 223 |
- if err != nil {
|
|
| 224 |
- return out |
|
| 225 |
- } |
|
| 226 |
- |
|
| 227 |
- for _, c := range children {
|
|
| 228 |
- out[c.FullPath] = c.Entity |
|
| 229 |
- } |
|
| 230 |
- return out |
|
| 231 |
-} |
|
| 232 |
- |
|
| 233 |
-// Walk through the child graph of an entity, calling walkFunc for each child entity. |
|
| 234 |
-// It is safe for walkFunc to call graph functions. |
|
| 235 |
-func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error {
|
|
| 236 |
- children, err := db.Children(name, depth) |
|
| 237 |
- if err != nil {
|
|
| 238 |
- return err |
|
| 239 |
- } |
|
| 240 |
- |
|
| 241 |
- // Note: the database lock must not be held while calling walkFunc |
|
| 242 |
- for _, c := range children {
|
|
| 243 |
- if err := walkFunc(c.FullPath, c.Entity); err != nil {
|
|
| 244 |
- return err |
|
| 245 |
- } |
|
| 246 |
- } |
|
| 247 |
- return nil |
|
| 248 |
-} |
|
| 249 |
- |
|
| 250 |
-// Return the children of the specified entity |
|
| 251 |
-func (db *Database) Children(name string, depth int) ([]WalkMeta, error) {
|
|
| 252 |
- db.mux.RLock() |
|
| 253 |
- defer db.mux.RUnlock() |
|
| 254 |
- |
|
| 255 |
- e, err := db.get(name) |
|
| 256 |
- if err != nil {
|
|
| 257 |
- return nil, err |
|
| 258 |
- } |
|
| 259 |
- |
|
| 260 |
- return db.children(e, name, depth, nil) |
|
| 261 |
-} |
|
| 262 |
- |
|
| 263 |
-// Return the refrence count for a specified id |
|
| 264 |
-func (db *Database) Refs(id string) int {
|
|
| 265 |
- db.mux.RLock() |
|
| 266 |
- defer db.mux.RUnlock() |
|
| 267 |
- |
|
| 268 |
- var count int |
|
| 269 |
- if err := db.conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil {
|
|
| 270 |
- return 0 |
|
| 271 |
- } |
|
| 272 |
- return count |
|
| 273 |
-} |
|
| 274 |
- |
|
| 275 |
-// Return all the id's path references |
|
| 276 |
-func (db *Database) RefPaths(id string) Edges {
|
|
| 277 |
- db.mux.RLock() |
|
| 278 |
- defer db.mux.RUnlock() |
|
| 279 |
- |
|
| 280 |
- refs := Edges{}
|
|
| 281 |
- |
|
| 282 |
- rows, err := db.conn.Query("SELECT name, parent_id FROM edge WHERE entity_id = ?;", id)
|
|
| 283 |
- if err != nil {
|
|
| 284 |
- return refs |
|
| 285 |
- } |
|
| 286 |
- defer rows.Close() |
|
| 287 |
- |
|
| 288 |
- for rows.Next() {
|
|
| 289 |
- var name string |
|
| 290 |
- var parentId string |
|
| 291 |
- if err := rows.Scan(&name, &parentId); err != nil {
|
|
| 292 |
- return refs |
|
| 293 |
- } |
|
| 294 |
- refs = append(refs, &Edge{
|
|
| 295 |
- EntityID: id, |
|
| 296 |
- Name: name, |
|
| 297 |
- ParentID: parentId, |
|
| 298 |
- }) |
|
| 299 |
- } |
|
| 300 |
- return refs |
|
| 301 |
-} |
|
| 302 |
- |
|
| 303 |
-// Delete the reference to an entity at a given path |
|
| 304 |
-func (db *Database) Delete(name string) error {
|
|
| 305 |
- db.mux.Lock() |
|
| 306 |
- defer db.mux.Unlock() |
|
| 307 |
- |
|
| 308 |
- if name == "/" {
|
|
| 309 |
- return fmt.Errorf("Cannot delete root entity")
|
|
| 310 |
- } |
|
| 311 |
- |
|
| 312 |
- parentPath, n := splitPath(name) |
|
| 313 |
- parent, err := db.get(parentPath) |
|
| 314 |
- if err != nil {
|
|
| 315 |
- return err |
|
| 316 |
- } |
|
| 317 |
- |
|
| 318 |
- if _, err := db.conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name = ?;", parent.id, n); err != nil {
|
|
| 319 |
- return err |
|
| 320 |
- } |
|
| 321 |
- return nil |
|
| 322 |
-} |
|
| 323 |
- |
|
| 324 |
-// Remove the entity with the specified id |
|
| 325 |
-// Walk the graph to make sure all references to the entity |
|
| 326 |
-// are removed and return the number of references removed |
|
| 327 |
-func (db *Database) Purge(id string) (int, error) {
|
|
| 328 |
- db.mux.Lock() |
|
| 329 |
- defer db.mux.Unlock() |
|
| 330 |
- |
|
| 331 |
- rollback := func() {
|
|
| 332 |
- db.conn.Exec("ROLLBACK")
|
|
| 333 |
- } |
|
| 334 |
- |
|
| 335 |
- if _, err := db.conn.Exec("BEGIN"); err != nil {
|
|
| 336 |
- return -1, err |
|
| 337 |
- } |
|
| 338 |
- |
|
| 339 |
- // Delete all edges |
|
| 340 |
- rows, err := db.conn.Exec("DELETE FROM edge WHERE entity_id = ?;", id)
|
|
| 341 |
- if err != nil {
|
|
| 342 |
- rollback() |
|
| 343 |
- return -1, err |
|
| 344 |
- } |
|
| 345 |
- |
|
| 346 |
- changes, err := rows.RowsAffected() |
|
| 347 |
- if err != nil {
|
|
| 348 |
- return -1, err |
|
| 349 |
- } |
|
| 350 |
- |
|
| 351 |
- // Delete entity |
|
| 352 |
- if _, err := db.conn.Exec("DELETE FROM entity where id = ?;", id); err != nil {
|
|
| 353 |
- rollback() |
|
| 354 |
- return -1, err |
|
| 355 |
- } |
|
| 356 |
- |
|
| 357 |
- if _, err := db.conn.Exec("COMMIT"); err != nil {
|
|
| 358 |
- return -1, err |
|
| 359 |
- } |
|
| 360 |
- return int(changes), nil |
|
| 361 |
-} |
|
| 362 |
- |
|
| 363 |
-// Rename an edge for a given path |
|
| 364 |
-func (db *Database) Rename(currentName, newName string) error {
|
|
| 365 |
- db.mux.Lock() |
|
| 366 |
- defer db.mux.Unlock() |
|
| 367 |
- |
|
| 368 |
- parentPath, name := splitPath(currentName) |
|
| 369 |
- newParentPath, newEdgeName := splitPath(newName) |
|
| 370 |
- |
|
| 371 |
- if parentPath != newParentPath {
|
|
| 372 |
- return fmt.Errorf("Cannot rename when root paths do not match %s != %s", parentPath, newParentPath)
|
|
| 373 |
- } |
|
| 374 |
- |
|
| 375 |
- parent, err := db.get(parentPath) |
|
| 376 |
- if err != nil {
|
|
| 377 |
- return err |
|
| 378 |
- } |
|
| 379 |
- |
|
| 380 |
- rows, err := db.conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name = ?;", newEdgeName, parent.id, name)
|
|
| 381 |
- if err != nil {
|
|
| 382 |
- return err |
|
| 383 |
- } |
|
| 384 |
- i, err := rows.RowsAffected() |
|
| 385 |
- if err != nil {
|
|
| 386 |
- return err |
|
| 387 |
- } |
|
| 388 |
- if i == 0 {
|
|
| 389 |
- return fmt.Errorf("Cannot locate edge for %s %s", parent.id, name)
|
|
| 390 |
- } |
|
| 391 |
- return nil |
|
| 392 |
-} |
|
| 393 |
- |
|
| 394 |
-type WalkMeta struct {
|
|
| 395 |
- Parent *Entity |
|
| 396 |
- Entity *Entity |
|
| 397 |
- FullPath string |
|
| 398 |
- Edge *Edge |
|
| 399 |
-} |
|
| 400 |
- |
|
| 401 |
-func (db *Database) children(e *Entity, name string, depth int, entities []WalkMeta) ([]WalkMeta, error) {
|
|
| 402 |
- if e == nil {
|
|
| 403 |
- return entities, nil |
|
| 404 |
- } |
|
| 405 |
- |
|
| 406 |
- rows, err := db.conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id)
|
|
| 407 |
- if err != nil {
|
|
| 408 |
- return nil, err |
|
| 409 |
- } |
|
| 410 |
- defer rows.Close() |
|
| 411 |
- |
|
| 412 |
- for rows.Next() {
|
|
| 413 |
- var entityId, entityName string |
|
| 414 |
- if err := rows.Scan(&entityId, &entityName); err != nil {
|
|
| 415 |
- return nil, err |
|
| 416 |
- } |
|
| 417 |
- child := &Entity{entityId}
|
|
| 418 |
- edge := &Edge{
|
|
| 419 |
- ParentID: e.id, |
|
| 420 |
- Name: entityName, |
|
| 421 |
- EntityID: child.id, |
|
| 422 |
- } |
|
| 423 |
- |
|
| 424 |
- meta := WalkMeta{
|
|
| 425 |
- Parent: e, |
|
| 426 |
- Entity: child, |
|
| 427 |
- FullPath: path.Join(name, edge.Name), |
|
| 428 |
- Edge: edge, |
|
| 429 |
- } |
|
| 430 |
- |
|
| 431 |
- entities = append(entities, meta) |
|
| 432 |
- |
|
| 433 |
- if depth != 0 {
|
|
| 434 |
- nDepth := depth |
|
| 435 |
- if depth != -1 {
|
|
| 436 |
- nDepth -= 1 |
|
| 437 |
- } |
|
| 438 |
- entities, err = db.children(child, meta.FullPath, nDepth, entities) |
|
| 439 |
- if err != nil {
|
|
| 440 |
- return nil, err |
|
| 441 |
- } |
|
| 442 |
- } |
|
| 443 |
- } |
|
| 444 |
- |
|
| 445 |
- return entities, nil |
|
| 446 |
-} |
|
| 447 |
- |
|
| 448 |
-// Return the entity based on the parent path and name |
|
| 449 |
-func (db *Database) child(parent *Entity, name string) *Entity {
|
|
| 450 |
- var id string |
|
| 451 |
- if err := db.conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name = ?;", parent.id, name).Scan(&id); err != nil {
|
|
| 452 |
- return nil |
|
| 453 |
- } |
|
| 454 |
- return &Entity{id}
|
|
| 455 |
-} |
|
| 456 |
- |
|
| 457 |
-// Return the id used to reference this entity |
|
| 458 |
-func (e *Entity) ID() string {
|
|
| 459 |
- return e.id |
|
| 460 |
-} |
|
| 461 |
- |
|
| 462 |
-// Return the paths sorted by depth |
|
| 463 |
-func (e Entities) Paths() []string {
|
|
| 464 |
- out := make([]string, len(e)) |
|
| 465 |
- var i int |
|
| 466 |
- for k := range e {
|
|
| 467 |
- out[i] = k |
|
| 468 |
- i++ |
|
| 469 |
- } |
|
| 470 |
- sortByDepth(out) |
|
| 471 |
- |
|
| 472 |
- return out |
|
| 473 |
-} |
| 474 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,540 +0,0 @@ |
| 1 |
-package gograph |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- _ "code.google.com/p/gosqlite/sqlite3" |
|
| 5 |
- "database/sql" |
|
| 6 |
- "fmt" |
|
| 7 |
- "os" |
|
| 8 |
- "path" |
|
| 9 |
- "strconv" |
|
| 10 |
- "testing" |
|
| 11 |
-) |
|
| 12 |
- |
|
| 13 |
-func newTestDb(t *testing.T) (*Database, string) {
|
|
| 14 |
- p := path.Join(os.TempDir(), "sqlite.db") |
|
| 15 |
- conn, err := sql.Open("sqlite3", p)
|
|
| 16 |
- db, err := NewDatabase(conn, true) |
|
| 17 |
- if err != nil {
|
|
| 18 |
- t.Fatal(err) |
|
| 19 |
- } |
|
| 20 |
- return db, p |
|
| 21 |
-} |
|
| 22 |
- |
|
| 23 |
-func destroyTestDb(dbPath string) {
|
|
| 24 |
- os.Remove(dbPath) |
|
| 25 |
-} |
|
| 26 |
- |
|
| 27 |
-func TestNewDatabase(t *testing.T) {
|
|
| 28 |
- db, dbpath := newTestDb(t) |
|
| 29 |
- if db == nil {
|
|
| 30 |
- t.Fatal("Database should not be nil")
|
|
| 31 |
- } |
|
| 32 |
- db.Close() |
|
| 33 |
- defer destroyTestDb(dbpath) |
|
| 34 |
-} |
|
| 35 |
- |
|
| 36 |
-func TestCreateRootEnity(t *testing.T) {
|
|
| 37 |
- db, dbpath := newTestDb(t) |
|
| 38 |
- defer destroyTestDb(dbpath) |
|
| 39 |
- root := db.RootEntity() |
|
| 40 |
- if root == nil {
|
|
| 41 |
- t.Fatal("Root entity should not be nil")
|
|
| 42 |
- } |
|
| 43 |
-} |
|
| 44 |
- |
|
| 45 |
-func TestGetRootEntity(t *testing.T) {
|
|
| 46 |
- db, dbpath := newTestDb(t) |
|
| 47 |
- defer destroyTestDb(dbpath) |
|
| 48 |
- |
|
| 49 |
- e := db.Get("/")
|
|
| 50 |
- if e == nil {
|
|
| 51 |
- t.Fatal("Entity should not be nil")
|
|
| 52 |
- } |
|
| 53 |
- if e.ID() != "0" {
|
|
| 54 |
- t.Fatalf("Enity id should be 0, got %s", e.ID())
|
|
| 55 |
- } |
|
| 56 |
-} |
|
| 57 |
- |
|
| 58 |
-func TestSetEntityWithDifferentName(t *testing.T) {
|
|
| 59 |
- db, dbpath := newTestDb(t) |
|
| 60 |
- defer destroyTestDb(dbpath) |
|
| 61 |
- |
|
| 62 |
- db.Set("/test", "1")
|
|
| 63 |
- if _, err := db.Set("/other", "1"); err != nil {
|
|
| 64 |
- t.Fatal(err) |
|
| 65 |
- } |
|
| 66 |
-} |
|
| 67 |
- |
|
| 68 |
-func TestSetDuplicateEntity(t *testing.T) {
|
|
| 69 |
- db, dbpath := newTestDb(t) |
|
| 70 |
- defer destroyTestDb(dbpath) |
|
| 71 |
- |
|
| 72 |
- if _, err := db.Set("/foo", "42"); err != nil {
|
|
| 73 |
- t.Fatal(err) |
|
| 74 |
- } |
|
| 75 |
- if _, err := db.Set("/foo", "43"); err == nil {
|
|
| 76 |
- t.Fatalf("Creating an entry with a duplciate path did not cause an error")
|
|
| 77 |
- } |
|
| 78 |
-} |
|
| 79 |
- |
|
| 80 |
-func TestCreateChild(t *testing.T) {
|
|
| 81 |
- db, dbpath := newTestDb(t) |
|
| 82 |
- defer destroyTestDb(dbpath) |
|
| 83 |
- |
|
| 84 |
- child, err := db.Set("/db", "1")
|
|
| 85 |
- if err != nil {
|
|
| 86 |
- t.Fatal(err) |
|
| 87 |
- } |
|
| 88 |
- if child == nil {
|
|
| 89 |
- t.Fatal("Child should not be nil")
|
|
| 90 |
- } |
|
| 91 |
- if child.ID() != "1" {
|
|
| 92 |
- t.Fail() |
|
| 93 |
- } |
|
| 94 |
-} |
|
| 95 |
- |
|
| 96 |
-func TestListAllRootChildren(t *testing.T) {
|
|
| 97 |
- db, dbpath := newTestDb(t) |
|
| 98 |
- defer destroyTestDb(dbpath) |
|
| 99 |
- |
|
| 100 |
- for i := 1; i < 6; i++ {
|
|
| 101 |
- a := strconv.Itoa(i) |
|
| 102 |
- if _, err := db.Set("/"+a, a); err != nil {
|
|
| 103 |
- t.Fatal(err) |
|
| 104 |
- } |
|
| 105 |
- } |
|
| 106 |
- entries := db.List("/", -1)
|
|
| 107 |
- if len(entries) != 5 {
|
|
| 108 |
- t.Fatalf("Expect 5 entries for / got %d", len(entries))
|
|
| 109 |
- } |
|
| 110 |
-} |
|
| 111 |
- |
|
| 112 |
-func TestListAllSubChildren(t *testing.T) {
|
|
| 113 |
- db, dbpath := newTestDb(t) |
|
| 114 |
- defer destroyTestDb(dbpath) |
|
| 115 |
- |
|
| 116 |
- _, err := db.Set("/webapp", "1")
|
|
| 117 |
- if err != nil {
|
|
| 118 |
- t.Fatal(err) |
|
| 119 |
- } |
|
| 120 |
- child2, err := db.Set("/db", "2")
|
|
| 121 |
- if err != nil {
|
|
| 122 |
- t.Fatal(err) |
|
| 123 |
- } |
|
| 124 |
- child4, err := db.Set("/logs", "4")
|
|
| 125 |
- if err != nil {
|
|
| 126 |
- t.Fatal(err) |
|
| 127 |
- } |
|
| 128 |
- if _, err := db.Set("/db/logs", child4.ID()); err != nil {
|
|
| 129 |
- t.Fatal(err) |
|
| 130 |
- } |
|
| 131 |
- |
|
| 132 |
- child3, err := db.Set("/sentry", "3")
|
|
| 133 |
- if err != nil {
|
|
| 134 |
- t.Fatal(err) |
|
| 135 |
- } |
|
| 136 |
- if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
|
|
| 137 |
- t.Fatal(err) |
|
| 138 |
- } |
|
| 139 |
- if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
|
|
| 140 |
- t.Fatal(err) |
|
| 141 |
- } |
|
| 142 |
- |
|
| 143 |
- entries := db.List("/webapp", 1)
|
|
| 144 |
- if len(entries) != 3 {
|
|
| 145 |
- t.Fatalf("Expect 3 entries for / got %d", len(entries))
|
|
| 146 |
- } |
|
| 147 |
- |
|
| 148 |
- entries = db.List("/webapp", 0)
|
|
| 149 |
- if len(entries) != 2 {
|
|
| 150 |
- t.Fatalf("Expect 2 entries for / got %d", len(entries))
|
|
| 151 |
- } |
|
| 152 |
-} |
|
| 153 |
- |
|
| 154 |
-func TestAddSelfAsChild(t *testing.T) {
|
|
| 155 |
- db, dbpath := newTestDb(t) |
|
| 156 |
- defer destroyTestDb(dbpath) |
|
| 157 |
- |
|
| 158 |
- child, err := db.Set("/test", "1")
|
|
| 159 |
- if err != nil {
|
|
| 160 |
- t.Fatal(err) |
|
| 161 |
- } |
|
| 162 |
- if _, err := db.Set("/test/other", child.ID()); err == nil {
|
|
| 163 |
- t.Fatal("Error should not be nil")
|
|
| 164 |
- } |
|
| 165 |
-} |
|
| 166 |
- |
|
| 167 |
-func TestAddChildToNonExistantRoot(t *testing.T) {
|
|
| 168 |
- db, dbpath := newTestDb(t) |
|
| 169 |
- defer destroyTestDb(dbpath) |
|
| 170 |
- |
|
| 171 |
- if _, err := db.Set("/myapp", "1"); err != nil {
|
|
| 172 |
- t.Fatal(err) |
|
| 173 |
- } |
|
| 174 |
- |
|
| 175 |
- if _, err := db.Set("/myapp/proxy/db", "2"); err == nil {
|
|
| 176 |
- t.Fatal("Error should not be nil")
|
|
| 177 |
- } |
|
| 178 |
-} |
|
| 179 |
- |
|
| 180 |
-func TestWalkAll(t *testing.T) {
|
|
| 181 |
- db, dbpath := newTestDb(t) |
|
| 182 |
- defer destroyTestDb(dbpath) |
|
| 183 |
- _, err := db.Set("/webapp", "1")
|
|
| 184 |
- if err != nil {
|
|
| 185 |
- t.Fatal(err) |
|
| 186 |
- } |
|
| 187 |
- child2, err := db.Set("/db", "2")
|
|
| 188 |
- if err != nil {
|
|
| 189 |
- t.Fatal(err) |
|
| 190 |
- } |
|
| 191 |
- child4, err := db.Set("/db/logs", "4")
|
|
| 192 |
- if err != nil {
|
|
| 193 |
- t.Fatal(err) |
|
| 194 |
- } |
|
| 195 |
- if _, err := db.Set("/webapp/logs", child4.ID()); err != nil {
|
|
| 196 |
- t.Fatal(err) |
|
| 197 |
- } |
|
| 198 |
- |
|
| 199 |
- child3, err := db.Set("/sentry", "3")
|
|
| 200 |
- if err != nil {
|
|
| 201 |
- t.Fatal(err) |
|
| 202 |
- } |
|
| 203 |
- if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
|
|
| 204 |
- t.Fatal(err) |
|
| 205 |
- } |
|
| 206 |
- if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
|
|
| 207 |
- t.Fatal(err) |
|
| 208 |
- } |
|
| 209 |
- |
|
| 210 |
- child5, err := db.Set("/gograph", "5")
|
|
| 211 |
- if err != nil {
|
|
| 212 |
- t.Fatal(err) |
|
| 213 |
- } |
|
| 214 |
- if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
|
|
| 215 |
- t.Fatal(err) |
|
| 216 |
- } |
|
| 217 |
- |
|
| 218 |
- if err := db.Walk("/", func(p string, e *Entity) error {
|
|
| 219 |
- t.Logf("Path: %s Entity: %s", p, e.ID())
|
|
| 220 |
- return nil |
|
| 221 |
- }, -1); err != nil {
|
|
| 222 |
- t.Fatal(err) |
|
| 223 |
- } |
|
| 224 |
-} |
|
| 225 |
- |
|
| 226 |
-func TestGetEntityByPath(t *testing.T) {
|
|
| 227 |
- db, dbpath := newTestDb(t) |
|
| 228 |
- defer destroyTestDb(dbpath) |
|
| 229 |
- _, err := db.Set("/webapp", "1")
|
|
| 230 |
- if err != nil {
|
|
| 231 |
- t.Fatal(err) |
|
| 232 |
- } |
|
| 233 |
- child2, err := db.Set("/db", "2")
|
|
| 234 |
- if err != nil {
|
|
| 235 |
- t.Fatal(err) |
|
| 236 |
- } |
|
| 237 |
- child4, err := db.Set("/logs", "4")
|
|
| 238 |
- if err != nil {
|
|
| 239 |
- t.Fatal(err) |
|
| 240 |
- } |
|
| 241 |
- if _, err := db.Set("/db/logs", child4.ID()); err != nil {
|
|
| 242 |
- t.Fatal(err) |
|
| 243 |
- } |
|
| 244 |
- |
|
| 245 |
- child3, err := db.Set("/sentry", "3")
|
|
| 246 |
- if err != nil {
|
|
| 247 |
- t.Fatal(err) |
|
| 248 |
- } |
|
| 249 |
- if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
|
|
| 250 |
- t.Fatal(err) |
|
| 251 |
- } |
|
| 252 |
- if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
|
|
| 253 |
- t.Fatal(err) |
|
| 254 |
- } |
|
| 255 |
- |
|
| 256 |
- child5, err := db.Set("/gograph", "5")
|
|
| 257 |
- if err != nil {
|
|
| 258 |
- t.Fatal(err) |
|
| 259 |
- } |
|
| 260 |
- if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
|
|
| 261 |
- t.Fatal(err) |
|
| 262 |
- } |
|
| 263 |
- |
|
| 264 |
- entity := db.Get("/webapp/db/logs")
|
|
| 265 |
- if entity == nil {
|
|
| 266 |
- t.Fatal("Entity should not be nil")
|
|
| 267 |
- } |
|
| 268 |
- if entity.ID() != "4" {
|
|
| 269 |
- t.Fatalf("Expected to get entity with id 4, got %s", entity.ID())
|
|
| 270 |
- } |
|
| 271 |
-} |
|
| 272 |
- |
|
| 273 |
-func TestEnitiesPaths(t *testing.T) {
|
|
| 274 |
- db, dbpath := newTestDb(t) |
|
| 275 |
- defer destroyTestDb(dbpath) |
|
| 276 |
- _, err := db.Set("/webapp", "1")
|
|
| 277 |
- if err != nil {
|
|
| 278 |
- t.Fatal(err) |
|
| 279 |
- } |
|
| 280 |
- child2, err := db.Set("/db", "2")
|
|
| 281 |
- if err != nil {
|
|
| 282 |
- t.Fatal(err) |
|
| 283 |
- } |
|
| 284 |
- child4, err := db.Set("/logs", "4")
|
|
| 285 |
- if err != nil {
|
|
| 286 |
- t.Fatal(err) |
|
| 287 |
- } |
|
| 288 |
- if _, err := db.Set("/db/logs", child4.ID()); err != nil {
|
|
| 289 |
- t.Fatal(err) |
|
| 290 |
- } |
|
| 291 |
- |
|
| 292 |
- child3, err := db.Set("/sentry", "3")
|
|
| 293 |
- if err != nil {
|
|
| 294 |
- t.Fatal(err) |
|
| 295 |
- } |
|
| 296 |
- if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
|
|
| 297 |
- t.Fatal(err) |
|
| 298 |
- } |
|
| 299 |
- if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
|
|
| 300 |
- t.Fatal(err) |
|
| 301 |
- } |
|
| 302 |
- |
|
| 303 |
- child5, err := db.Set("/gograph", "5")
|
|
| 304 |
- if err != nil {
|
|
| 305 |
- t.Fatal(err) |
|
| 306 |
- } |
|
| 307 |
- if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
|
|
| 308 |
- t.Fatal(err) |
|
| 309 |
- } |
|
| 310 |
- |
|
| 311 |
- out := db.List("/", -1)
|
|
| 312 |
- for _, p := range out.Paths() {
|
|
| 313 |
- t.Log(p) |
|
| 314 |
- } |
|
| 315 |
-} |
|
| 316 |
- |
|
| 317 |
-func TestDeleteRootEntity(t *testing.T) {
|
|
| 318 |
- db, dbpath := newTestDb(t) |
|
| 319 |
- defer destroyTestDb(dbpath) |
|
| 320 |
- |
|
| 321 |
- if err := db.Delete("/"); err == nil {
|
|
| 322 |
- t.Fatal("Error should not be nil")
|
|
| 323 |
- } |
|
| 324 |
-} |
|
| 325 |
- |
|
| 326 |
-func TestDeleteEntity(t *testing.T) {
|
|
| 327 |
- db, dbpath := newTestDb(t) |
|
| 328 |
- defer destroyTestDb(dbpath) |
|
| 329 |
- _, err := db.Set("/webapp", "1")
|
|
| 330 |
- if err != nil {
|
|
| 331 |
- t.Fatal(err) |
|
| 332 |
- } |
|
| 333 |
- child2, err := db.Set("/db", "2")
|
|
| 334 |
- if err != nil {
|
|
| 335 |
- t.Fatal(err) |
|
| 336 |
- } |
|
| 337 |
- child4, err := db.Set("/logs", "4")
|
|
| 338 |
- if err != nil {
|
|
| 339 |
- t.Fatal(err) |
|
| 340 |
- } |
|
| 341 |
- if _, err := db.Set("/db/logs", child4.ID()); err != nil {
|
|
| 342 |
- t.Fatal(err) |
|
| 343 |
- } |
|
| 344 |
- |
|
| 345 |
- child3, err := db.Set("/sentry", "3")
|
|
| 346 |
- if err != nil {
|
|
| 347 |
- t.Fatal(err) |
|
| 348 |
- } |
|
| 349 |
- if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
|
|
| 350 |
- t.Fatal(err) |
|
| 351 |
- } |
|
| 352 |
- if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
|
|
| 353 |
- t.Fatal(err) |
|
| 354 |
- } |
|
| 355 |
- |
|
| 356 |
- child5, err := db.Set("/gograph", "5")
|
|
| 357 |
- if err != nil {
|
|
| 358 |
- t.Fatal(err) |
|
| 359 |
- } |
|
| 360 |
- if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
|
|
| 361 |
- t.Fatal(err) |
|
| 362 |
- } |
|
| 363 |
- |
|
| 364 |
- if err := db.Delete("/webapp/sentry"); err != nil {
|
|
| 365 |
- t.Fatal(err) |
|
| 366 |
- } |
|
| 367 |
- entity := db.Get("/webapp/sentry")
|
|
| 368 |
- if entity != nil {
|
|
| 369 |
- t.Fatal("Entity /webapp/sentry should be nil")
|
|
| 370 |
- } |
|
| 371 |
-} |
|
| 372 |
- |
|
| 373 |
-func TestCountRefs(t *testing.T) {
|
|
| 374 |
- db, dbpath := newTestDb(t) |
|
| 375 |
- defer destroyTestDb(dbpath) |
|
| 376 |
- |
|
| 377 |
- db.Set("/webapp", "1")
|
|
| 378 |
- |
|
| 379 |
- if db.Refs("1") != 1 {
|
|
| 380 |
- t.Fatal("Expect reference count to be 1")
|
|
| 381 |
- } |
|
| 382 |
- |
|
| 383 |
- db.Set("/db", "2")
|
|
| 384 |
- db.Set("/webapp/db", "2")
|
|
| 385 |
- if db.Refs("2") != 2 {
|
|
| 386 |
- t.Fatal("Expect reference count to be 2")
|
|
| 387 |
- } |
|
| 388 |
-} |
|
| 389 |
- |
|
| 390 |
-func TestPurgeId(t *testing.T) {
|
|
| 391 |
- db, dbpath := newTestDb(t) |
|
| 392 |
- defer destroyTestDb(dbpath) |
|
| 393 |
- |
|
| 394 |
- db.Set("/webapp", "1")
|
|
| 395 |
- |
|
| 396 |
- if db.Refs("1") != 1 {
|
|
| 397 |
- t.Fatal("Expect reference count to be 1")
|
|
| 398 |
- } |
|
| 399 |
- |
|
| 400 |
- db.Set("/db", "2")
|
|
| 401 |
- db.Set("/webapp/db", "2")
|
|
| 402 |
- |
|
| 403 |
- count, err := db.Purge("2")
|
|
| 404 |
- if err != nil {
|
|
| 405 |
- t.Fatal(err) |
|
| 406 |
- } |
|
| 407 |
- if count != 2 {
|
|
| 408 |
- t.Fatal("Expected 2 references to be removed")
|
|
| 409 |
- } |
|
| 410 |
-} |
|
| 411 |
- |
|
| 412 |
-func TestRename(t *testing.T) {
|
|
| 413 |
- db, dbpath := newTestDb(t) |
|
| 414 |
- defer destroyTestDb(dbpath) |
|
| 415 |
- |
|
| 416 |
- db.Set("/webapp", "1")
|
|
| 417 |
- |
|
| 418 |
- if db.Refs("1") != 1 {
|
|
| 419 |
- t.Fatal("Expect reference count to be 1")
|
|
| 420 |
- } |
|
| 421 |
- |
|
| 422 |
- db.Set("/db", "2")
|
|
| 423 |
- db.Set("/webapp/db", "2")
|
|
| 424 |
- |
|
| 425 |
- if db.Get("/webapp/db") == nil {
|
|
| 426 |
- t.Fatal("Cannot find entity at path /webapp/db")
|
|
| 427 |
- } |
|
| 428 |
- |
|
| 429 |
- if err := db.Rename("/webapp/db", "/webapp/newdb"); err != nil {
|
|
| 430 |
- t.Fatal(err) |
|
| 431 |
- } |
|
| 432 |
- if db.Get("/webapp/db") != nil {
|
|
| 433 |
- t.Fatal("Entity should not exist at /webapp/db")
|
|
| 434 |
- } |
|
| 435 |
- if db.Get("/webapp/newdb") == nil {
|
|
| 436 |
- t.Fatal("Cannot find entity at path /webapp/newdb")
|
|
| 437 |
- } |
|
| 438 |
- |
|
| 439 |
-} |
|
| 440 |
- |
|
| 441 |
-func TestCreateMultipleNames(t *testing.T) {
|
|
| 442 |
- db, dbpath := newTestDb(t) |
|
| 443 |
- defer destroyTestDb(dbpath) |
|
| 444 |
- |
|
| 445 |
- db.Set("/db", "1")
|
|
| 446 |
- if _, err := db.Set("/myapp", "1"); err != nil {
|
|
| 447 |
- t.Fatal(err) |
|
| 448 |
- } |
|
| 449 |
- |
|
| 450 |
- db.Walk("/", func(p string, e *Entity) error {
|
|
| 451 |
- t.Logf("%s\n", p)
|
|
| 452 |
- return nil |
|
| 453 |
- }, -1) |
|
| 454 |
-} |
|
| 455 |
- |
|
| 456 |
-func TestRefPaths(t *testing.T) {
|
|
| 457 |
- db, dbpath := newTestDb(t) |
|
| 458 |
- defer destroyTestDb(dbpath) |
|
| 459 |
- |
|
| 460 |
- db.Set("/webapp", "1")
|
|
| 461 |
- |
|
| 462 |
- db.Set("/db", "2")
|
|
| 463 |
- db.Set("/webapp/db", "2")
|
|
| 464 |
- |
|
| 465 |
- refs := db.RefPaths("2")
|
|
| 466 |
- if len(refs) != 2 {
|
|
| 467 |
- t.Fatalf("Expected reference count to be 2, got %d", len(refs))
|
|
| 468 |
- } |
|
| 469 |
-} |
|
| 470 |
- |
|
| 471 |
-func TestExistsTrue(t *testing.T) {
|
|
| 472 |
- db, dbpath := newTestDb(t) |
|
| 473 |
- defer destroyTestDb(dbpath) |
|
| 474 |
- |
|
| 475 |
- db.Set("/testing", "1")
|
|
| 476 |
- |
|
| 477 |
- if !db.Exists("/testing") {
|
|
| 478 |
- t.Fatalf("/tesing should exist")
|
|
| 479 |
- } |
|
| 480 |
-} |
|
| 481 |
- |
|
| 482 |
-func TestExistsFalse(t *testing.T) {
|
|
| 483 |
- db, dbpath := newTestDb(t) |
|
| 484 |
- defer destroyTestDb(dbpath) |
|
| 485 |
- |
|
| 486 |
- db.Set("/toerhe", "1")
|
|
| 487 |
- |
|
| 488 |
- if db.Exists("/testing") {
|
|
| 489 |
- t.Fatalf("/tesing should not exist")
|
|
| 490 |
- } |
|
| 491 |
- |
|
| 492 |
-} |
|
| 493 |
- |
|
| 494 |
-func TestGetNameWithTrailingSlash(t *testing.T) {
|
|
| 495 |
- db, dbpath := newTestDb(t) |
|
| 496 |
- defer destroyTestDb(dbpath) |
|
| 497 |
- |
|
| 498 |
- db.Set("/todo", "1")
|
|
| 499 |
- |
|
| 500 |
- e := db.Get("/todo/")
|
|
| 501 |
- if e == nil {
|
|
| 502 |
- t.Fatalf("Entity should not be nil")
|
|
| 503 |
- } |
|
| 504 |
-} |
|
| 505 |
- |
|
| 506 |
-func TestConcurrentWrites(t *testing.T) {
|
|
| 507 |
- db, dbpath := newTestDb(t) |
|
| 508 |
- defer destroyTestDb(dbpath) |
|
| 509 |
- |
|
| 510 |
- errs := make(chan error, 2) |
|
| 511 |
- |
|
| 512 |
- save := func(name string, id string) {
|
|
| 513 |
- if _, err := db.Set(fmt.Sprintf("/%s", name), id); err != nil {
|
|
| 514 |
- errs <- err |
|
| 515 |
- } |
|
| 516 |
- errs <- nil |
|
| 517 |
- } |
|
| 518 |
- purge := func(id string) {
|
|
| 519 |
- if _, err := db.Purge(id); err != nil {
|
|
| 520 |
- errs <- err |
|
| 521 |
- } |
|
| 522 |
- errs <- nil |
|
| 523 |
- } |
|
| 524 |
- |
|
| 525 |
- save("/1", "1")
|
|
| 526 |
- |
|
| 527 |
- go purge("1")
|
|
| 528 |
- go save("/2", "2")
|
|
| 529 |
- |
|
| 530 |
- any := false |
|
| 531 |
- for i := 0; i < 2; i++ {
|
|
| 532 |
- if err := <-errs; err != nil {
|
|
| 533 |
- any = true |
|
| 534 |
- t.Log(err) |
|
| 535 |
- } |
|
| 536 |
- } |
|
| 537 |
- if any {
|
|
| 538 |
- t.Fatal() |
|
| 539 |
- } |
|
| 540 |
-} |
| 541 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,27 +0,0 @@ |
| 1 |
-package gograph |
|
| 2 |
- |
|
| 3 |
-import "sort" |
|
| 4 |
- |
|
| 5 |
-type pathSorter struct {
|
|
| 6 |
- paths []string |
|
| 7 |
- by func(i, j string) bool |
|
| 8 |
-} |
|
| 9 |
- |
|
| 10 |
-func sortByDepth(paths []string) {
|
|
| 11 |
- s := &pathSorter{paths, func(i, j string) bool {
|
|
| 12 |
- return PathDepth(i) > PathDepth(j) |
|
| 13 |
- }} |
|
| 14 |
- sort.Sort(s) |
|
| 15 |
-} |
|
| 16 |
- |
|
| 17 |
-func (s *pathSorter) Len() int {
|
|
| 18 |
- return len(s.paths) |
|
| 19 |
-} |
|
| 20 |
- |
|
| 21 |
-func (s *pathSorter) Swap(i, j int) {
|
|
| 22 |
- s.paths[i], s.paths[j] = s.paths[j], s.paths[i] |
|
| 23 |
-} |
|
| 24 |
- |
|
| 25 |
-func (s *pathSorter) Less(i, j int) bool {
|
|
| 26 |
- return s.by(s.paths[i], s.paths[j]) |
|
| 27 |
-} |
| 28 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,29 +0,0 @@ |
| 1 |
-package gograph |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "testing" |
|
| 5 |
-) |
|
| 6 |
- |
|
| 7 |
-func TestSort(t *testing.T) {
|
|
| 8 |
- paths := []string{
|
|
| 9 |
- "/", |
|
| 10 |
- "/myreallylongname", |
|
| 11 |
- "/app/db", |
|
| 12 |
- } |
|
| 13 |
- |
|
| 14 |
- sortByDepth(paths) |
|
| 15 |
- |
|
| 16 |
- if len(paths) != 3 {
|
|
| 17 |
- t.Fatalf("Expected 3 parts got %d", len(paths))
|
|
| 18 |
- } |
|
| 19 |
- |
|
| 20 |
- if paths[0] != "/app/db" {
|
|
| 21 |
- t.Fatalf("Expected /app/db got %s", paths[0])
|
|
| 22 |
- } |
|
| 23 |
- if paths[1] != "/myreallylongname" {
|
|
| 24 |
- t.Fatalf("Expected /myreallylongname got %s", paths[1])
|
|
| 25 |
- } |
|
| 26 |
- if paths[2] != "/" {
|
|
| 27 |
- t.Fatalf("Expected / got %s", paths[2])
|
|
| 28 |
- } |
|
| 29 |
-} |
| 30 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,32 +0,0 @@ |
| 1 |
-package gograph |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "path" |
|
| 5 |
- "strings" |
|
| 6 |
-) |
|
| 7 |
- |
|
| 8 |
-// Split p on / |
|
| 9 |
-func split(p string) []string {
|
|
| 10 |
- return strings.Split(p, "/") |
|
| 11 |
-} |
|
| 12 |
- |
|
| 13 |
-// Returns the depth or number of / in a given path |
|
| 14 |
-func PathDepth(p string) int {
|
|
| 15 |
- parts := split(p) |
|
| 16 |
- if len(parts) == 2 && parts[1] == "" {
|
|
| 17 |
- return 1 |
|
| 18 |
- } |
|
| 19 |
- return len(parts) |
|
| 20 |
-} |
|
| 21 |
- |
|
| 22 |
-func splitPath(p string) (parent, name string) {
|
|
| 23 |
- if p[0] != '/' {
|
|
| 24 |
- p = "/" + p |
|
| 25 |
- } |
|
| 26 |
- parent, name = path.Split(p) |
|
| 27 |
- l := len(parent) |
|
| 28 |
- if parent[l-1] == '/' {
|
|
| 29 |
- parent = parent[:l-1] |
|
| 30 |
- } |
|
| 31 |
- return |
|
| 32 |
-} |
| ... | ... |
@@ -3,6 +3,7 @@ package docker |
| 3 | 3 |
import ( |
| 4 | 4 |
"fmt" |
| 5 | 5 |
"github.com/dotcloud/docker/archive" |
| 6 |
+ "github.com/dotcloud/docker/graphdriver" |
|
| 6 | 7 |
"github.com/dotcloud/docker/utils" |
| 7 | 8 |
"io" |
| 8 | 9 |
"io/ioutil" |
| ... | ... |
@@ -10,6 +11,7 @@ import ( |
| 10 | 10 |
"path" |
| 11 | 11 |
"path/filepath" |
| 12 | 12 |
"strings" |
| 13 |
+ "syscall" |
|
| 13 | 14 |
"time" |
| 14 | 15 |
) |
| 15 | 16 |
|
| ... | ... |
@@ -17,11 +19,12 @@ import ( |
| 17 | 17 |
type Graph struct {
|
| 18 | 18 |
Root string |
| 19 | 19 |
idIndex *utils.TruncIndex |
| 20 |
+ driver graphdriver.Driver |
|
| 20 | 21 |
} |
| 21 | 22 |
|
| 22 | 23 |
// NewGraph instantiates a new graph at the given root path in the filesystem. |
| 23 | 24 |
// `root` will be created if it doesn't exist. |
| 24 |
-func NewGraph(root string) (*Graph, error) {
|
|
| 25 |
+func NewGraph(root string, driver graphdriver.Driver) (*Graph, error) {
|
|
| 25 | 26 |
abspath, err := filepath.Abs(root) |
| 26 | 27 |
if err != nil {
|
| 27 | 28 |
return nil, err |
| ... | ... |
@@ -30,9 +33,11 @@ func NewGraph(root string) (*Graph, error) {
|
| 30 | 30 |
if err := os.MkdirAll(root, 0700); err != nil && !os.IsExist(err) {
|
| 31 | 31 |
return nil, err |
| 32 | 32 |
} |
| 33 |
+ |
|
| 33 | 34 |
graph := &Graph{
|
| 34 | 35 |
Root: abspath, |
| 35 | 36 |
idIndex: utils.NewTruncIndex(), |
| 37 |
+ driver: driver, |
|
| 36 | 38 |
} |
| 37 | 39 |
if err := graph.restore(); err != nil {
|
| 38 | 40 |
return nil, err |
| ... | ... |
@@ -47,7 +52,9 @@ func (graph *Graph) restore() error {
|
| 47 | 47 |
} |
| 48 | 48 |
for _, v := range dir {
|
| 49 | 49 |
id := v.Name() |
| 50 |
- graph.idIndex.Add(id) |
|
| 50 |
+ if graph.driver.Exists(id) {
|
|
| 51 |
+ graph.idIndex.Add(id) |
|
| 52 |
+ } |
|
| 51 | 53 |
} |
| 52 | 54 |
return nil |
| 53 | 55 |
} |
| ... | ... |
@@ -78,16 +85,22 @@ func (graph *Graph) Get(name string) (*Image, error) {
|
| 78 | 78 |
if err != nil {
|
| 79 | 79 |
return nil, err |
| 80 | 80 |
} |
| 81 |
+ // Check that the filesystem layer exists |
|
| 82 |
+ rootfs, err := graph.driver.Get(img.ID) |
|
| 83 |
+ if err != nil {
|
|
| 84 |
+ return nil, fmt.Errorf("Driver %s failed to get image rootfs %s: %s", graph.driver, img.ID, err)
|
|
| 85 |
+ } |
|
| 81 | 86 |
if img.ID != id {
|
| 82 | 87 |
return nil, fmt.Errorf("Image stored at '%s' has wrong id '%s'", id, img.ID)
|
| 83 | 88 |
} |
| 84 | 89 |
img.graph = graph |
| 85 | 90 |
if img.Size == 0 {
|
| 86 |
- root, err := img.root() |
|
| 91 |
+ size, err := utils.TreeSize(rootfs) |
|
| 87 | 92 |
if err != nil {
|
| 88 |
- return nil, err |
|
| 93 |
+ return nil, fmt.Errorf("Error computing size of rootfs %s: %s", img.ID, err)
|
|
| 89 | 94 |
} |
| 90 |
- if err := StoreSize(img, root); err != nil {
|
|
| 95 |
+ img.Size = size |
|
| 96 |
+ if err := img.SaveSize(graph.imageRoot(id)); err != nil {
|
|
| 91 | 97 |
return nil, err |
| 92 | 98 |
} |
| 93 | 99 |
} |
| ... | ... |
@@ -126,19 +139,37 @@ func (graph *Graph) Register(jsonData []byte, layerData archive.Archive, img *Im |
| 126 | 126 |
if graph.Exists(img.ID) {
|
| 127 | 127 |
return fmt.Errorf("Image %s already exists", img.ID)
|
| 128 | 128 |
} |
| 129 |
+ |
|
| 130 |
+ // Ensure that the image root does not exist on the filesystem |
|
| 131 |
+ // when it is not registered in the graph. |
|
| 132 |
+ // This is common when you switch from one graph driver to another |
|
| 133 |
+ if err := os.RemoveAll(graph.imageRoot(img.ID)); err != nil && !os.IsNotExist(err) {
|
|
| 134 |
+ return err |
|
| 135 |
+ } |
|
| 136 |
+ |
|
| 129 | 137 |
tmp, err := graph.Mktemp("")
|
| 130 | 138 |
defer os.RemoveAll(tmp) |
| 131 | 139 |
if err != nil {
|
| 132 | 140 |
return fmt.Errorf("Mktemp failed: %s", err)
|
| 133 | 141 |
} |
| 134 |
- if err := StoreImage(img, jsonData, layerData, tmp); err != nil {
|
|
| 142 |
+ |
|
| 143 |
+ // Create root filesystem in the driver |
|
| 144 |
+ if err := graph.driver.Create(img.ID, img.Parent); err != nil {
|
|
| 145 |
+ return fmt.Errorf("Driver %s failed to create image rootfs %s: %s", graph.driver, img.ID, err)
|
|
| 146 |
+ } |
|
| 147 |
+ // Mount the root filesystem so we can apply the diff/layer |
|
| 148 |
+ rootfs, err := graph.driver.Get(img.ID) |
|
| 149 |
+ if err != nil {
|
|
| 150 |
+ return fmt.Errorf("Driver %s failed to get image rootfs %s: %s", graph.driver, img.ID, err)
|
|
| 151 |
+ } |
|
| 152 |
+ img.graph = graph |
|
| 153 |
+ if err := StoreImage(img, jsonData, layerData, tmp, rootfs); err != nil {
|
|
| 135 | 154 |
return err |
| 136 | 155 |
} |
| 137 | 156 |
// Commit |
| 138 | 157 |
if err := os.Rename(tmp, graph.imageRoot(img.ID)); err != nil {
|
| 139 | 158 |
return err |
| 140 | 159 |
} |
| 141 |
- img.graph = graph |
|
| 142 | 160 |
graph.idIndex.Add(img.ID) |
| 143 | 161 |
return nil |
| 144 | 162 |
} |
| ... | ... |
@@ -152,50 +183,33 @@ func (graph *Graph) TempLayerArchive(id string, compression archive.Compression, |
| 152 | 152 |
if err != nil {
|
| 153 | 153 |
return nil, err |
| 154 | 154 |
} |
| 155 |
- tmp, err := graph.tmp() |
|
| 155 |
+ tmp, err := graph.Mktemp("")
|
|
| 156 | 156 |
if err != nil {
|
| 157 | 157 |
return nil, err |
| 158 | 158 |
} |
| 159 |
- a, err := image.TarLayer(compression) |
|
| 159 |
+ a, err := image.TarLayer() |
|
| 160 | 160 |
if err != nil {
|
| 161 | 161 |
return nil, err |
| 162 | 162 |
} |
| 163 |
- return archive.NewTempArchive(utils.ProgressReader(ioutil.NopCloser(a), 0, output, sf.FormatProgress("", "Buffering to disk", "%v/%v (%v)"), sf, true), tmp.Root)
|
|
| 163 |
+ return archive.NewTempArchive(utils.ProgressReader(ioutil.NopCloser(a), 0, output, sf.FormatProgress("", "Buffering to disk", "%v/%v (%v)"), sf, true), tmp)
|
|
| 164 | 164 |
} |
| 165 | 165 |
|
| 166 | 166 |
// Mktemp creates a temporary sub-directory inside the graph's filesystem. |
| 167 | 167 |
func (graph *Graph) Mktemp(id string) (string, error) {
|
| 168 |
- if id == "" {
|
|
| 169 |
- id = GenerateID() |
|
| 170 |
- } |
|
| 171 |
- tmp, err := graph.tmp() |
|
| 172 |
- if err != nil {
|
|
| 173 |
- return "", fmt.Errorf("Couldn't create temp: %s", err)
|
|
| 174 |
- } |
|
| 175 |
- if tmp.Exists(id) {
|
|
| 176 |
- return "", fmt.Errorf("Image %s already exists", id)
|
|
| 168 |
+ dir := path.Join(graph.Root, "_tmp", GenerateID()) |
|
| 169 |
+ if err := os.MkdirAll(dir, 0700); err != nil {
|
|
| 170 |
+ return "", err |
|
| 177 | 171 |
} |
| 178 |
- return tmp.imageRoot(id), nil |
|
| 172 |
+ return dir, nil |
|
| 179 | 173 |
} |
| 180 | 174 |
|
| 181 |
-// getDockerInitLayer returns the path of a layer containing a mountpoint suitable |
|
| 175 |
+// setupInitLayer populates a directory with mountpoints suitable |
|
| 182 | 176 |
// for bind-mounting dockerinit into the container. The mountpoint is simply an |
| 183 | 177 |
// empty file at /.dockerinit |
| 184 | 178 |
// |
| 185 | 179 |
// This extra layer is used by all containers as the top-most ro layer. It protects |
| 186 | 180 |
// the container from unwanted side-effects on the rw layer. |
| 187 |
-func (graph *Graph) getDockerInitLayer() (string, error) {
|
|
| 188 |
- tmp, err := graph.tmp() |
|
| 189 |
- if err != nil {
|
|
| 190 |
- return "", err |
|
| 191 |
- } |
|
| 192 |
- initLayer := tmp.imageRoot("_dockerinit")
|
|
| 193 |
- if err := os.Mkdir(initLayer, 0755); err != nil && !os.IsExist(err) {
|
|
| 194 |
- // If directory already existed, keep going. |
|
| 195 |
- // For all other errors, abort. |
|
| 196 |
- return "", err |
|
| 197 |
- } |
|
| 198 |
- |
|
| 181 |
+func setupInitLayer(initLayer string) error {
|
|
| 199 | 182 |
for pth, typ := range map[string]string{
|
| 200 | 183 |
"/dev/pts": "dir", |
| 201 | 184 |
"/dev/shm": "dir", |
| ... | ... |
@@ -209,36 +223,38 @@ func (graph *Graph) getDockerInitLayer() (string, error) {
|
| 209 | 209 |
// "var/run": "dir", |
| 210 | 210 |
// "var/lock": "dir", |
| 211 | 211 |
} {
|
| 212 |
+ parts := strings.Split(pth, "/") |
|
| 213 |
+ prev := "/" |
|
| 214 |
+ for _, p := range parts[1:] {
|
|
| 215 |
+ prev = path.Join(prev, p) |
|
| 216 |
+ syscall.Unlink(path.Join(initLayer, prev)) |
|
| 217 |
+ } |
|
| 218 |
+ |
|
| 212 | 219 |
if _, err := os.Stat(path.Join(initLayer, pth)); err != nil {
|
| 213 | 220 |
if os.IsNotExist(err) {
|
| 214 | 221 |
switch typ {
|
| 215 | 222 |
case "dir": |
| 216 | 223 |
if err := os.MkdirAll(path.Join(initLayer, pth), 0755); err != nil {
|
| 217 |
- return "", err |
|
| 224 |
+ return err |
|
| 218 | 225 |
} |
| 219 | 226 |
case "file": |
| 220 | 227 |
if err := os.MkdirAll(path.Join(initLayer, path.Dir(pth)), 0755); err != nil {
|
| 221 |
- return "", err |
|
| 228 |
+ return err |
|
| 222 | 229 |
} |
| 223 | 230 |
f, err := os.OpenFile(path.Join(initLayer, pth), os.O_CREATE, 0755) |
| 224 | 231 |
if err != nil {
|
| 225 |
- return "", err |
|
| 232 |
+ return err |
|
| 226 | 233 |
} |
| 227 | 234 |
f.Close() |
| 228 | 235 |
} |
| 229 | 236 |
} else {
|
| 230 |
- return "", err |
|
| 237 |
+ return err |
|
| 231 | 238 |
} |
| 232 | 239 |
} |
| 233 | 240 |
} |
| 234 | 241 |
|
| 235 | 242 |
// Layer is ready to use, if it wasn't before. |
| 236 |
- return initLayer, nil |
|
| 237 |
-} |
|
| 238 |
- |
|
| 239 |
-func (graph *Graph) tmp() (*Graph, error) {
|
|
| 240 |
- // Changed to _tmp from :tmp:, because it messed with ":" separators in aufs branch syntax... |
|
| 241 |
- return NewGraph(path.Join(graph.Root, "_tmp")) |
|
| 243 |
+ return nil |
|
| 242 | 244 |
} |
| 243 | 245 |
|
| 244 | 246 |
// Check if given error is "not empty". |
| ... | ... |
@@ -270,6 +286,9 @@ func (graph *Graph) Delete(name string) error {
|
| 270 | 270 |
if err != nil {
|
| 271 | 271 |
return err |
| 272 | 272 |
} |
| 273 |
+ // Remove rootfs data from the driver |
|
| 274 |
+ graph.driver.Remove(id) |
|
| 275 |
+ // Remove the trashed image directory |
|
| 273 | 276 |
return os.RemoveAll(tmp) |
| 274 | 277 |
} |
| 275 | 278 |
|
| ... | ... |
@@ -5,6 +5,7 @@ import ( |
| 5 | 5 |
"bytes" |
| 6 | 6 |
"errors" |
| 7 | 7 |
"github.com/dotcloud/docker/archive" |
| 8 |
+ "github.com/dotcloud/docker/graphdriver" |
|
| 8 | 9 |
"github.com/dotcloud/docker/utils" |
| 9 | 10 |
"io" |
| 10 | 11 |
"io/ioutil" |
| ... | ... |
@@ -15,7 +16,7 @@ import ( |
| 15 | 15 |
|
| 16 | 16 |
func TestInit(t *testing.T) {
|
| 17 | 17 |
graph := tempGraph(t) |
| 18 |
- defer os.RemoveAll(graph.Root) |
|
| 18 |
+ defer nukeGraph(graph) |
|
| 19 | 19 |
// Root should exist |
| 20 | 20 |
if _, err := os.Stat(graph.Root); err != nil {
|
| 21 | 21 |
t.Fatal(err) |
| ... | ... |
@@ -31,7 +32,7 @@ func TestInit(t *testing.T) {
|
| 31 | 31 |
// Test that Register can be interrupted cleanly without side effects |
| 32 | 32 |
func TestInterruptedRegister(t *testing.T) {
|
| 33 | 33 |
graph := tempGraph(t) |
| 34 |
- defer os.RemoveAll(graph.Root) |
|
| 34 |
+ defer nukeGraph(graph) |
|
| 35 | 35 |
badArchive, w := io.Pipe() // Use a pipe reader as a fake archive which never yields data |
| 36 | 36 |
image := &Image{
|
| 37 | 37 |
ID: GenerateID(), |
| ... | ... |
@@ -58,7 +59,7 @@ func TestInterruptedRegister(t *testing.T) {
|
| 58 | 58 |
// create multiple, check the amount of images and paths, etc..) |
| 59 | 59 |
func TestGraphCreate(t *testing.T) {
|
| 60 | 60 |
graph := tempGraph(t) |
| 61 |
- defer os.RemoveAll(graph.Root) |
|
| 61 |
+ defer nukeGraph(graph) |
|
| 62 | 62 |
archive, err := fakeTar() |
| 63 | 63 |
if err != nil {
|
| 64 | 64 |
t.Fatal(err) |
| ... | ... |
@@ -89,7 +90,7 @@ func TestGraphCreate(t *testing.T) {
|
| 89 | 89 |
|
| 90 | 90 |
func TestRegister(t *testing.T) {
|
| 91 | 91 |
graph := tempGraph(t) |
| 92 |
- defer os.RemoveAll(graph.Root) |
|
| 92 |
+ defer nukeGraph(graph) |
|
| 93 | 93 |
archive, err := fakeTar() |
| 94 | 94 |
if err != nil {
|
| 95 | 95 |
t.Fatal(err) |
| ... | ... |
@@ -123,7 +124,7 @@ func TestRegister(t *testing.T) {
|
| 123 | 123 |
// Test that an image can be deleted by its shorthand prefix |
| 124 | 124 |
func TestDeletePrefix(t *testing.T) {
|
| 125 | 125 |
graph := tempGraph(t) |
| 126 |
- defer os.RemoveAll(graph.Root) |
|
| 126 |
+ defer nukeGraph(graph) |
|
| 127 | 127 |
img := createTestImage(graph, t) |
| 128 | 128 |
if err := graph.Delete(utils.TruncateID(img.ID)); err != nil {
|
| 129 | 129 |
t.Fatal(err) |
| ... | ... |
@@ -145,7 +146,7 @@ func createTestImage(graph *Graph, t *testing.T) *Image {
|
| 145 | 145 |
|
| 146 | 146 |
func TestDelete(t *testing.T) {
|
| 147 | 147 |
graph := tempGraph(t) |
| 148 |
- defer os.RemoveAll(graph.Root) |
|
| 148 |
+ defer nukeGraph(graph) |
|
| 149 | 149 |
archive, err := fakeTar() |
| 150 | 150 |
if err != nil {
|
| 151 | 151 |
t.Fatal(err) |
| ... | ... |
@@ -209,7 +210,7 @@ func TestByParent(t *testing.T) {
|
| 209 | 209 |
archive3, _ := fakeTar() |
| 210 | 210 |
|
| 211 | 211 |
graph := tempGraph(t) |
| 212 |
- defer os.RemoveAll(graph.Root) |
|
| 212 |
+ defer nukeGraph(graph) |
|
| 213 | 213 |
parentImage := &Image{
|
| 214 | 214 |
ID: GenerateID(), |
| 215 | 215 |
Comment: "parent", |
| ... | ... |
@@ -259,13 +260,22 @@ func tempGraph(t *testing.T) *Graph {
|
| 259 | 259 |
if err != nil {
|
| 260 | 260 |
t.Fatal(err) |
| 261 | 261 |
} |
| 262 |
- graph, err := NewGraph(tmp) |
|
| 262 |
+ backend, err := graphdriver.New(tmp) |
|
| 263 |
+ if err != nil {
|
|
| 264 |
+ t.Fatal(err) |
|
| 265 |
+ } |
|
| 266 |
+ graph, err := NewGraph(tmp, backend) |
|
| 263 | 267 |
if err != nil {
|
| 264 | 268 |
t.Fatal(err) |
| 265 | 269 |
} |
| 266 | 270 |
return graph |
| 267 | 271 |
} |
| 268 | 272 |
|
| 273 |
+func nukeGraph(graph *Graph) {
|
|
| 274 |
+ graph.driver.Cleanup() |
|
| 275 |
+ os.RemoveAll(graph.Root) |
|
| 276 |
+} |
|
| 277 |
+ |
|
| 269 | 278 |
func testArchive(t *testing.T) archive.Archive {
|
| 270 | 279 |
archive, err := fakeTar() |
| 271 | 280 |
if err != nil {
|
| 0 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,473 @@ |
| 0 |
+package graphdb |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "database/sql" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "path" |
|
| 6 |
+ "sync" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+const ( |
|
| 10 |
+ createEntityTable = ` |
|
| 11 |
+ CREATE TABLE IF NOT EXISTS entity ( |
|
| 12 |
+ id text NOT NULL PRIMARY KEY |
|
| 13 |
+ );` |
|
| 14 |
+ |
|
| 15 |
+ createEdgeTable = ` |
|
| 16 |
+ CREATE TABLE IF NOT EXISTS edge ( |
|
| 17 |
+ "entity_id" text NOT NULL, |
|
| 18 |
+ "parent_id" text NULL, |
|
| 19 |
+ "name" text NOT NULL, |
|
| 20 |
+ CONSTRAINT "parent_fk" FOREIGN KEY ("parent_id") REFERENCES "entity" ("id"),
|
|
| 21 |
+ CONSTRAINT "entity_fk" FOREIGN KEY ("entity_id") REFERENCES "entity" ("id")
|
|
| 22 |
+ ); |
|
| 23 |
+ ` |
|
| 24 |
+ |
|
| 25 |
+ createEdgeIndices = ` |
|
| 26 |
+ CREATE UNIQUE INDEX IF NOT EXISTS "name_parent_ix" ON "edge" (parent_id, name); |
|
| 27 |
+ ` |
|
| 28 |
+) |
|
| 29 |
+ |
|
| 30 |
+// Entity with a unique id |
|
| 31 |
+type Entity struct {
|
|
| 32 |
+ id string |
|
| 33 |
+} |
|
| 34 |
+ |
|
| 35 |
+// An Edge connects two entities together |
|
| 36 |
+type Edge struct {
|
|
| 37 |
+ EntityID string |
|
| 38 |
+ Name string |
|
| 39 |
+ ParentID string |
|
| 40 |
+} |
|
| 41 |
+ |
|
| 42 |
+type Entities map[string]*Entity |
|
| 43 |
+type Edges []*Edge |
|
| 44 |
+ |
|
| 45 |
+type WalkFunc func(fullPath string, entity *Entity) error |
|
| 46 |
+ |
|
| 47 |
+// Graph database for storing entities and their relationships |
|
| 48 |
+type Database struct {
|
|
| 49 |
+ conn *sql.DB |
|
| 50 |
+ mux sync.RWMutex |
|
| 51 |
+} |
|
| 52 |
+ |
|
| 53 |
+// Create a new graph database initialized with a root entity |
|
| 54 |
+func NewDatabase(conn *sql.DB, init bool) (*Database, error) {
|
|
| 55 |
+ if conn == nil {
|
|
| 56 |
+ return nil, fmt.Errorf("Database connection cannot be nil")
|
|
| 57 |
+ } |
|
| 58 |
+ db := &Database{conn: conn}
|
|
| 59 |
+ |
|
| 60 |
+ if init {
|
|
| 61 |
+ if _, err := conn.Exec(createEntityTable); err != nil {
|
|
| 62 |
+ return nil, err |
|
| 63 |
+ } |
|
| 64 |
+ if _, err := conn.Exec(createEdgeTable); err != nil {
|
|
| 65 |
+ return nil, err |
|
| 66 |
+ } |
|
| 67 |
+ if _, err := conn.Exec(createEdgeIndices); err != nil {
|
|
| 68 |
+ return nil, err |
|
| 69 |
+ } |
|
| 70 |
+ |
|
| 71 |
+ rollback := func() {
|
|
| 72 |
+ conn.Exec("ROLLBACK")
|
|
| 73 |
+ } |
|
| 74 |
+ |
|
| 75 |
+ // Create root entities |
|
| 76 |
+ if _, err := conn.Exec("BEGIN"); err != nil {
|
|
| 77 |
+ return nil, err |
|
| 78 |
+ } |
|
| 79 |
+ if _, err := conn.Exec("INSERT INTO entity (id) VALUES (?);", "0"); err != nil {
|
|
| 80 |
+ rollback() |
|
| 81 |
+ return nil, err |
|
| 82 |
+ } |
|
| 83 |
+ |
|
| 84 |
+ if _, err := conn.Exec("INSERT INTO edge (entity_id, name) VALUES(?,?);", "0", "/"); err != nil {
|
|
| 85 |
+ rollback() |
|
| 86 |
+ return nil, err |
|
| 87 |
+ } |
|
| 88 |
+ |
|
| 89 |
+ if _, err := conn.Exec("COMMIT"); err != nil {
|
|
| 90 |
+ return nil, err |
|
| 91 |
+ } |
|
| 92 |
+ } |
|
| 93 |
+ return db, nil |
|
| 94 |
+} |
|
| 95 |
+ |
|
| 96 |
+// Close the underlying connection to the database |
|
| 97 |
+func (db *Database) Close() error {
|
|
| 98 |
+ return db.conn.Close() |
|
| 99 |
+} |
|
| 100 |
+ |
|
| 101 |
+// Set the entity id for a given path |
|
| 102 |
+func (db *Database) Set(fullPath, id string) (*Entity, error) {
|
|
| 103 |
+ db.mux.Lock() |
|
| 104 |
+ defer db.mux.Unlock() |
|
| 105 |
+ |
|
| 106 |
+ rollback := func() {
|
|
| 107 |
+ db.conn.Exec("ROLLBACK")
|
|
| 108 |
+ } |
|
| 109 |
+ if _, err := db.conn.Exec("BEGIN EXCLUSIVE"); err != nil {
|
|
| 110 |
+ return nil, err |
|
| 111 |
+ } |
|
| 112 |
+ var entityId string |
|
| 113 |
+ if err := db.conn.QueryRow("SELECT id FROM entity WHERE id = ?;", id).Scan(&entityId); err != nil {
|
|
| 114 |
+ if err == sql.ErrNoRows {
|
|
| 115 |
+ if _, err := db.conn.Exec("INSERT INTO entity (id) VALUES(?);", id); err != nil {
|
|
| 116 |
+ rollback() |
|
| 117 |
+ return nil, err |
|
| 118 |
+ } |
|
| 119 |
+ } else {
|
|
| 120 |
+ rollback() |
|
| 121 |
+ return nil, err |
|
| 122 |
+ } |
|
| 123 |
+ } |
|
| 124 |
+ e := &Entity{id}
|
|
| 125 |
+ |
|
| 126 |
+ parentPath, name := splitPath(fullPath) |
|
| 127 |
+ if err := db.setEdge(parentPath, name, e); err != nil {
|
|
| 128 |
+ rollback() |
|
| 129 |
+ return nil, err |
|
| 130 |
+ } |
|
| 131 |
+ |
|
| 132 |
+ if _, err := db.conn.Exec("COMMIT"); err != nil {
|
|
| 133 |
+ return nil, err |
|
| 134 |
+ } |
|
| 135 |
+ return e, nil |
|
| 136 |
+} |
|
| 137 |
+ |
|
| 138 |
+// Return true if a name already exists in the database |
|
| 139 |
+func (db *Database) Exists(name string) bool {
|
|
| 140 |
+ db.mux.RLock() |
|
| 141 |
+ defer db.mux.RUnlock() |
|
| 142 |
+ |
|
| 143 |
+ e, err := db.get(name) |
|
| 144 |
+ if err != nil {
|
|
| 145 |
+ return false |
|
| 146 |
+ } |
|
| 147 |
+ return e != nil |
|
| 148 |
+} |
|
| 149 |
+ |
|
| 150 |
+func (db *Database) setEdge(parentPath, name string, e *Entity) error {
|
|
| 151 |
+ parent, err := db.get(parentPath) |
|
| 152 |
+ if err != nil {
|
|
| 153 |
+ return err |
|
| 154 |
+ } |
|
| 155 |
+ if parent.id == e.id {
|
|
| 156 |
+ return fmt.Errorf("Cannot set self as child")
|
|
| 157 |
+ } |
|
| 158 |
+ |
|
| 159 |
+ if _, err := db.conn.Exec("INSERT INTO edge (parent_id, name, entity_id) VALUES (?,?,?);", parent.id, name, e.id); err != nil {
|
|
| 160 |
+ return err |
|
| 161 |
+ } |
|
| 162 |
+ return nil |
|
| 163 |
+} |
|
| 164 |
+ |
|
| 165 |
+// Return the root "/" entity for the database |
|
| 166 |
+func (db *Database) RootEntity() *Entity {
|
|
| 167 |
+ return &Entity{
|
|
| 168 |
+ id: "0", |
|
| 169 |
+ } |
|
| 170 |
+} |
|
| 171 |
+ |
|
| 172 |
+// Return the entity for a given path |
|
| 173 |
+func (db *Database) Get(name string) *Entity {
|
|
| 174 |
+ db.mux.RLock() |
|
| 175 |
+ defer db.mux.RUnlock() |
|
| 176 |
+ |
|
| 177 |
+ e, err := db.get(name) |
|
| 178 |
+ if err != nil {
|
|
| 179 |
+ return nil |
|
| 180 |
+ } |
|
| 181 |
+ return e |
|
| 182 |
+} |
|
| 183 |
+ |
|
| 184 |
+func (db *Database) get(name string) (*Entity, error) {
|
|
| 185 |
+ e := db.RootEntity() |
|
| 186 |
+ // We always know the root name so return it if |
|
| 187 |
+ // it is requested |
|
| 188 |
+ if name == "/" {
|
|
| 189 |
+ return e, nil |
|
| 190 |
+ } |
|
| 191 |
+ |
|
| 192 |
+ parts := split(name) |
|
| 193 |
+ for i := 1; i < len(parts); i++ {
|
|
| 194 |
+ p := parts[i] |
|
| 195 |
+ if p == "" {
|
|
| 196 |
+ continue |
|
| 197 |
+ } |
|
| 198 |
+ |
|
| 199 |
+ next := db.child(e, p) |
|
| 200 |
+ if next == nil {
|
|
| 201 |
+ return nil, fmt.Errorf("Cannot find child for %s", name)
|
|
| 202 |
+ } |
|
| 203 |
+ e = next |
|
| 204 |
+ } |
|
| 205 |
+ return e, nil |
|
| 206 |
+ |
|
| 207 |
+} |
|
| 208 |
+ |
|
| 209 |
+// List all entities by from the name |
|
| 210 |
+// The key will be the full path of the entity |
|
| 211 |
+func (db *Database) List(name string, depth int) Entities {
|
|
| 212 |
+ db.mux.RLock() |
|
| 213 |
+ defer db.mux.RUnlock() |
|
| 214 |
+ |
|
| 215 |
+ out := Entities{}
|
|
| 216 |
+ e, err := db.get(name) |
|
| 217 |
+ if err != nil {
|
|
| 218 |
+ return out |
|
| 219 |
+ } |
|
| 220 |
+ |
|
| 221 |
+ children, err := db.children(e, name, depth, nil) |
|
| 222 |
+ if err != nil {
|
|
| 223 |
+ return out |
|
| 224 |
+ } |
|
| 225 |
+ |
|
| 226 |
+ for _, c := range children {
|
|
| 227 |
+ out[c.FullPath] = c.Entity |
|
| 228 |
+ } |
|
| 229 |
+ return out |
|
| 230 |
+} |
|
| 231 |
+ |
|
| 232 |
+// Walk through the child graph of an entity, calling walkFunc for each child entity. |
|
| 233 |
+// It is safe for walkFunc to call graph functions. |
|
| 234 |
+func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error {
|
|
| 235 |
+ children, err := db.Children(name, depth) |
|
| 236 |
+ if err != nil {
|
|
| 237 |
+ return err |
|
| 238 |
+ } |
|
| 239 |
+ |
|
| 240 |
+ // Note: the database lock must not be held while calling walkFunc |
|
| 241 |
+ for _, c := range children {
|
|
| 242 |
+ if err := walkFunc(c.FullPath, c.Entity); err != nil {
|
|
| 243 |
+ return err |
|
| 244 |
+ } |
|
| 245 |
+ } |
|
| 246 |
+ return nil |
|
| 247 |
+} |
|
| 248 |
+ |
|
| 249 |
+// Return the children of the specified entity |
|
| 250 |
+func (db *Database) Children(name string, depth int) ([]WalkMeta, error) {
|
|
| 251 |
+ db.mux.RLock() |
|
| 252 |
+ defer db.mux.RUnlock() |
|
| 253 |
+ |
|
| 254 |
+ e, err := db.get(name) |
|
| 255 |
+ if err != nil {
|
|
| 256 |
+ return nil, err |
|
| 257 |
+ } |
|
| 258 |
+ |
|
| 259 |
+ return db.children(e, name, depth, nil) |
|
| 260 |
+} |
|
| 261 |
+ |
|
| 262 |
+// Return the refrence count for a specified id |
|
| 263 |
+func (db *Database) Refs(id string) int {
|
|
| 264 |
+ db.mux.RLock() |
|
| 265 |
+ defer db.mux.RUnlock() |
|
| 266 |
+ |
|
| 267 |
+ var count int |
|
| 268 |
+ if err := db.conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil {
|
|
| 269 |
+ return 0 |
|
| 270 |
+ } |
|
| 271 |
+ return count |
|
| 272 |
+} |
|
| 273 |
+ |
|
| 274 |
+// Return all the id's path references |
|
| 275 |
+func (db *Database) RefPaths(id string) Edges {
|
|
| 276 |
+ db.mux.RLock() |
|
| 277 |
+ defer db.mux.RUnlock() |
|
| 278 |
+ |
|
| 279 |
+ refs := Edges{}
|
|
| 280 |
+ |
|
| 281 |
+ rows, err := db.conn.Query("SELECT name, parent_id FROM edge WHERE entity_id = ?;", id)
|
|
| 282 |
+ if err != nil {
|
|
| 283 |
+ return refs |
|
| 284 |
+ } |
|
| 285 |
+ defer rows.Close() |
|
| 286 |
+ |
|
| 287 |
+ for rows.Next() {
|
|
| 288 |
+ var name string |
|
| 289 |
+ var parentId string |
|
| 290 |
+ if err := rows.Scan(&name, &parentId); err != nil {
|
|
| 291 |
+ return refs |
|
| 292 |
+ } |
|
| 293 |
+ refs = append(refs, &Edge{
|
|
| 294 |
+ EntityID: id, |
|
| 295 |
+ Name: name, |
|
| 296 |
+ ParentID: parentId, |
|
| 297 |
+ }) |
|
| 298 |
+ } |
|
| 299 |
+ return refs |
|
| 300 |
+} |
|
| 301 |
+ |
|
| 302 |
+// Delete the reference to an entity at a given path |
|
| 303 |
+func (db *Database) Delete(name string) error {
|
|
| 304 |
+ db.mux.Lock() |
|
| 305 |
+ defer db.mux.Unlock() |
|
| 306 |
+ |
|
| 307 |
+ if name == "/" {
|
|
| 308 |
+ return fmt.Errorf("Cannot delete root entity")
|
|
| 309 |
+ } |
|
| 310 |
+ |
|
| 311 |
+ parentPath, n := splitPath(name) |
|
| 312 |
+ parent, err := db.get(parentPath) |
|
| 313 |
+ if err != nil {
|
|
| 314 |
+ return err |
|
| 315 |
+ } |
|
| 316 |
+ |
|
| 317 |
+ if _, err := db.conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name = ?;", parent.id, n); err != nil {
|
|
| 318 |
+ return err |
|
| 319 |
+ } |
|
| 320 |
+ return nil |
|
| 321 |
+} |
|
| 322 |
+ |
|
| 323 |
+// Remove the entity with the specified id |
|
| 324 |
+// Walk the graph to make sure all references to the entity |
|
| 325 |
+// are removed and return the number of references removed |
|
| 326 |
+func (db *Database) Purge(id string) (int, error) {
|
|
| 327 |
+ db.mux.Lock() |
|
| 328 |
+ defer db.mux.Unlock() |
|
| 329 |
+ |
|
| 330 |
+ rollback := func() {
|
|
| 331 |
+ db.conn.Exec("ROLLBACK")
|
|
| 332 |
+ } |
|
| 333 |
+ |
|
| 334 |
+ if _, err := db.conn.Exec("BEGIN"); err != nil {
|
|
| 335 |
+ return -1, err |
|
| 336 |
+ } |
|
| 337 |
+ |
|
| 338 |
+ // Delete all edges |
|
| 339 |
+ rows, err := db.conn.Exec("DELETE FROM edge WHERE entity_id = ?;", id)
|
|
| 340 |
+ if err != nil {
|
|
| 341 |
+ rollback() |
|
| 342 |
+ return -1, err |
|
| 343 |
+ } |
|
| 344 |
+ |
|
| 345 |
+ changes, err := rows.RowsAffected() |
|
| 346 |
+ if err != nil {
|
|
| 347 |
+ return -1, err |
|
| 348 |
+ } |
|
| 349 |
+ |
|
| 350 |
+ // Delete entity |
|
| 351 |
+ if _, err := db.conn.Exec("DELETE FROM entity where id = ?;", id); err != nil {
|
|
| 352 |
+ rollback() |
|
| 353 |
+ return -1, err |
|
| 354 |
+ } |
|
| 355 |
+ |
|
| 356 |
+ if _, err := db.conn.Exec("COMMIT"); err != nil {
|
|
| 357 |
+ return -1, err |
|
| 358 |
+ } |
|
| 359 |
+ return int(changes), nil |
|
| 360 |
+} |
|
| 361 |
+ |
|
| 362 |
+// Rename an edge for a given path |
|
| 363 |
+func (db *Database) Rename(currentName, newName string) error {
|
|
| 364 |
+ db.mux.Lock() |
|
| 365 |
+ defer db.mux.Unlock() |
|
| 366 |
+ |
|
| 367 |
+ parentPath, name := splitPath(currentName) |
|
| 368 |
+ newParentPath, newEdgeName := splitPath(newName) |
|
| 369 |
+ |
|
| 370 |
+ if parentPath != newParentPath {
|
|
| 371 |
+ return fmt.Errorf("Cannot rename when root paths do not match %s != %s", parentPath, newParentPath)
|
|
| 372 |
+ } |
|
| 373 |
+ |
|
| 374 |
+ parent, err := db.get(parentPath) |
|
| 375 |
+ if err != nil {
|
|
| 376 |
+ return err |
|
| 377 |
+ } |
|
| 378 |
+ |
|
| 379 |
+ rows, err := db.conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name = ?;", newEdgeName, parent.id, name)
|
|
| 380 |
+ if err != nil {
|
|
| 381 |
+ return err |
|
| 382 |
+ } |
|
| 383 |
+ i, err := rows.RowsAffected() |
|
| 384 |
+ if err != nil {
|
|
| 385 |
+ return err |
|
| 386 |
+ } |
|
| 387 |
+ if i == 0 {
|
|
| 388 |
+ return fmt.Errorf("Cannot locate edge for %s %s", parent.id, name)
|
|
| 389 |
+ } |
|
| 390 |
+ return nil |
|
| 391 |
+} |
|
| 392 |
+ |
|
| 393 |
+type WalkMeta struct {
|
|
| 394 |
+ Parent *Entity |
|
| 395 |
+ Entity *Entity |
|
| 396 |
+ FullPath string |
|
| 397 |
+ Edge *Edge |
|
| 398 |
+} |
|
| 399 |
+ |
|
| 400 |
+func (db *Database) children(e *Entity, name string, depth int, entities []WalkMeta) ([]WalkMeta, error) {
|
|
| 401 |
+ if e == nil {
|
|
| 402 |
+ return entities, nil |
|
| 403 |
+ } |
|
| 404 |
+ |
|
| 405 |
+ rows, err := db.conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id)
|
|
| 406 |
+ if err != nil {
|
|
| 407 |
+ return nil, err |
|
| 408 |
+ } |
|
| 409 |
+ defer rows.Close() |
|
| 410 |
+ |
|
| 411 |
+ for rows.Next() {
|
|
| 412 |
+ var entityId, entityName string |
|
| 413 |
+ if err := rows.Scan(&entityId, &entityName); err != nil {
|
|
| 414 |
+ return nil, err |
|
| 415 |
+ } |
|
| 416 |
+ child := &Entity{entityId}
|
|
| 417 |
+ edge := &Edge{
|
|
| 418 |
+ ParentID: e.id, |
|
| 419 |
+ Name: entityName, |
|
| 420 |
+ EntityID: child.id, |
|
| 421 |
+ } |
|
| 422 |
+ |
|
| 423 |
+ meta := WalkMeta{
|
|
| 424 |
+ Parent: e, |
|
| 425 |
+ Entity: child, |
|
| 426 |
+ FullPath: path.Join(name, edge.Name), |
|
| 427 |
+ Edge: edge, |
|
| 428 |
+ } |
|
| 429 |
+ |
|
| 430 |
+ entities = append(entities, meta) |
|
| 431 |
+ |
|
| 432 |
+ if depth != 0 {
|
|
| 433 |
+ nDepth := depth |
|
| 434 |
+ if depth != -1 {
|
|
| 435 |
+ nDepth -= 1 |
|
| 436 |
+ } |
|
| 437 |
+ entities, err = db.children(child, meta.FullPath, nDepth, entities) |
|
| 438 |
+ if err != nil {
|
|
| 439 |
+ return nil, err |
|
| 440 |
+ } |
|
| 441 |
+ } |
|
| 442 |
+ } |
|
| 443 |
+ |
|
| 444 |
+ return entities, nil |
|
| 445 |
+} |
|
| 446 |
+ |
|
| 447 |
+// Return the entity based on the parent path and name |
|
| 448 |
+func (db *Database) child(parent *Entity, name string) *Entity {
|
|
| 449 |
+ var id string |
|
| 450 |
+ if err := db.conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name = ?;", parent.id, name).Scan(&id); err != nil {
|
|
| 451 |
+ return nil |
|
| 452 |
+ } |
|
| 453 |
+ return &Entity{id}
|
|
| 454 |
+} |
|
| 455 |
+ |
|
| 456 |
+// Return the id used to reference this entity |
|
| 457 |
+func (e *Entity) ID() string {
|
|
| 458 |
+ return e.id |
|
| 459 |
+} |
|
| 460 |
+ |
|
| 461 |
+// Return the paths sorted by depth |
|
| 462 |
+func (e Entities) Paths() []string {
|
|
| 463 |
+ out := make([]string, len(e)) |
|
| 464 |
+ var i int |
|
| 465 |
+ for k := range e {
|
|
| 466 |
+ out[i] = k |
|
| 467 |
+ i++ |
|
| 468 |
+ } |
|
| 469 |
+ sortByDepth(out) |
|
| 470 |
+ |
|
| 471 |
+ return out |
|
| 472 |
+} |
| 0 | 473 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,540 @@ |
| 0 |
+package graphdb |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ _ "code.google.com/p/gosqlite/sqlite3" |
|
| 4 |
+ "database/sql" |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "os" |
|
| 7 |
+ "path" |
|
| 8 |
+ "strconv" |
|
| 9 |
+ "testing" |
|
| 10 |
+) |
|
| 11 |
+ |
|
| 12 |
+func newTestDb(t *testing.T) (*Database, string) {
|
|
| 13 |
+ p := path.Join(os.TempDir(), "sqlite.db") |
|
| 14 |
+ conn, err := sql.Open("sqlite3", p)
|
|
| 15 |
+ db, err := NewDatabase(conn, true) |
|
| 16 |
+ if err != nil {
|
|
| 17 |
+ t.Fatal(err) |
|
| 18 |
+ } |
|
| 19 |
+ return db, p |
|
| 20 |
+} |
|
| 21 |
+ |
|
| 22 |
+func destroyTestDb(dbPath string) {
|
|
| 23 |
+ os.Remove(dbPath) |
|
| 24 |
+} |
|
| 25 |
+ |
|
| 26 |
+func TestNewDatabase(t *testing.T) {
|
|
| 27 |
+ db, dbpath := newTestDb(t) |
|
| 28 |
+ if db == nil {
|
|
| 29 |
+ t.Fatal("Database should not be nil")
|
|
| 30 |
+ } |
|
| 31 |
+ db.Close() |
|
| 32 |
+ defer destroyTestDb(dbpath) |
|
| 33 |
+} |
|
| 34 |
+ |
|
| 35 |
+func TestCreateRootEnity(t *testing.T) {
|
|
| 36 |
+ db, dbpath := newTestDb(t) |
|
| 37 |
+ defer destroyTestDb(dbpath) |
|
| 38 |
+ root := db.RootEntity() |
|
| 39 |
+ if root == nil {
|
|
| 40 |
+ t.Fatal("Root entity should not be nil")
|
|
| 41 |
+ } |
|
| 42 |
+} |
|
| 43 |
+ |
|
| 44 |
+func TestGetRootEntity(t *testing.T) {
|
|
| 45 |
+ db, dbpath := newTestDb(t) |
|
| 46 |
+ defer destroyTestDb(dbpath) |
|
| 47 |
+ |
|
| 48 |
+ e := db.Get("/")
|
|
| 49 |
+ if e == nil {
|
|
| 50 |
+ t.Fatal("Entity should not be nil")
|
|
| 51 |
+ } |
|
| 52 |
+ if e.ID() != "0" {
|
|
| 53 |
+ t.Fatalf("Enity id should be 0, got %s", e.ID())
|
|
| 54 |
+ } |
|
| 55 |
+} |
|
| 56 |
+ |
|
| 57 |
+func TestSetEntityWithDifferentName(t *testing.T) {
|
|
| 58 |
+ db, dbpath := newTestDb(t) |
|
| 59 |
+ defer destroyTestDb(dbpath) |
|
| 60 |
+ |
|
| 61 |
+ db.Set("/test", "1")
|
|
| 62 |
+ if _, err := db.Set("/other", "1"); err != nil {
|
|
| 63 |
+ t.Fatal(err) |
|
| 64 |
+ } |
|
| 65 |
+} |
|
| 66 |
+ |
|
| 67 |
+func TestSetDuplicateEntity(t *testing.T) {
|
|
| 68 |
+ db, dbpath := newTestDb(t) |
|
| 69 |
+ defer destroyTestDb(dbpath) |
|
| 70 |
+ |
|
| 71 |
+ if _, err := db.Set("/foo", "42"); err != nil {
|
|
| 72 |
+ t.Fatal(err) |
|
| 73 |
+ } |
|
| 74 |
+ if _, err := db.Set("/foo", "43"); err == nil {
|
|
| 75 |
+ t.Fatalf("Creating an entry with a duplciate path did not cause an error")
|
|
| 76 |
+ } |
|
| 77 |
+} |
|
| 78 |
+ |
|
| 79 |
+func TestCreateChild(t *testing.T) {
|
|
| 80 |
+ db, dbpath := newTestDb(t) |
|
| 81 |
+ defer destroyTestDb(dbpath) |
|
| 82 |
+ |
|
| 83 |
+ child, err := db.Set("/db", "1")
|
|
| 84 |
+ if err != nil {
|
|
| 85 |
+ t.Fatal(err) |
|
| 86 |
+ } |
|
| 87 |
+ if child == nil {
|
|
| 88 |
+ t.Fatal("Child should not be nil")
|
|
| 89 |
+ } |
|
| 90 |
+ if child.ID() != "1" {
|
|
| 91 |
+ t.Fail() |
|
| 92 |
+ } |
|
| 93 |
+} |
|
| 94 |
+ |
|
| 95 |
+func TestListAllRootChildren(t *testing.T) {
|
|
| 96 |
+ db, dbpath := newTestDb(t) |
|
| 97 |
+ defer destroyTestDb(dbpath) |
|
| 98 |
+ |
|
| 99 |
+ for i := 1; i < 6; i++ {
|
|
| 100 |
+ a := strconv.Itoa(i) |
|
| 101 |
+ if _, err := db.Set("/"+a, a); err != nil {
|
|
| 102 |
+ t.Fatal(err) |
|
| 103 |
+ } |
|
| 104 |
+ } |
|
| 105 |
+ entries := db.List("/", -1)
|
|
| 106 |
+ if len(entries) != 5 {
|
|
| 107 |
+ t.Fatalf("Expect 5 entries for / got %d", len(entries))
|
|
| 108 |
+ } |
|
| 109 |
+} |
|
| 110 |
+ |
|
| 111 |
+func TestListAllSubChildren(t *testing.T) {
|
|
| 112 |
+ db, dbpath := newTestDb(t) |
|
| 113 |
+ defer destroyTestDb(dbpath) |
|
| 114 |
+ |
|
| 115 |
+ _, err := db.Set("/webapp", "1")
|
|
| 116 |
+ if err != nil {
|
|
| 117 |
+ t.Fatal(err) |
|
| 118 |
+ } |
|
| 119 |
+ child2, err := db.Set("/db", "2")
|
|
| 120 |
+ if err != nil {
|
|
| 121 |
+ t.Fatal(err) |
|
| 122 |
+ } |
|
| 123 |
+ child4, err := db.Set("/logs", "4")
|
|
| 124 |
+ if err != nil {
|
|
| 125 |
+ t.Fatal(err) |
|
| 126 |
+ } |
|
| 127 |
+ if _, err := db.Set("/db/logs", child4.ID()); err != nil {
|
|
| 128 |
+ t.Fatal(err) |
|
| 129 |
+ } |
|
| 130 |
+ |
|
| 131 |
+ child3, err := db.Set("/sentry", "3")
|
|
| 132 |
+ if err != nil {
|
|
| 133 |
+ t.Fatal(err) |
|
| 134 |
+ } |
|
| 135 |
+ if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
|
|
| 136 |
+ t.Fatal(err) |
|
| 137 |
+ } |
|
| 138 |
+ if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
|
|
| 139 |
+ t.Fatal(err) |
|
| 140 |
+ } |
|
| 141 |
+ |
|
| 142 |
+ entries := db.List("/webapp", 1)
|
|
| 143 |
+ if len(entries) != 3 {
|
|
| 144 |
+ t.Fatalf("Expect 3 entries for / got %d", len(entries))
|
|
| 145 |
+ } |
|
| 146 |
+ |
|
| 147 |
+ entries = db.List("/webapp", 0)
|
|
| 148 |
+ if len(entries) != 2 {
|
|
| 149 |
+ t.Fatalf("Expect 2 entries for / got %d", len(entries))
|
|
| 150 |
+ } |
|
| 151 |
+} |
|
| 152 |
+ |
|
| 153 |
+func TestAddSelfAsChild(t *testing.T) {
|
|
| 154 |
+ db, dbpath := newTestDb(t) |
|
| 155 |
+ defer destroyTestDb(dbpath) |
|
| 156 |
+ |
|
| 157 |
+ child, err := db.Set("/test", "1")
|
|
| 158 |
+ if err != nil {
|
|
| 159 |
+ t.Fatal(err) |
|
| 160 |
+ } |
|
| 161 |
+ if _, err := db.Set("/test/other", child.ID()); err == nil {
|
|
| 162 |
+ t.Fatal("Error should not be nil")
|
|
| 163 |
+ } |
|
| 164 |
+} |
|
| 165 |
+ |
|
| 166 |
+func TestAddChildToNonExistantRoot(t *testing.T) {
|
|
| 167 |
+ db, dbpath := newTestDb(t) |
|
| 168 |
+ defer destroyTestDb(dbpath) |
|
| 169 |
+ |
|
| 170 |
+ if _, err := db.Set("/myapp", "1"); err != nil {
|
|
| 171 |
+ t.Fatal(err) |
|
| 172 |
+ } |
|
| 173 |
+ |
|
| 174 |
+ if _, err := db.Set("/myapp/proxy/db", "2"); err == nil {
|
|
| 175 |
+ t.Fatal("Error should not be nil")
|
|
| 176 |
+ } |
|
| 177 |
+} |
|
| 178 |
+ |
|
| 179 |
+func TestWalkAll(t *testing.T) {
|
|
| 180 |
+ db, dbpath := newTestDb(t) |
|
| 181 |
+ defer destroyTestDb(dbpath) |
|
| 182 |
+ _, err := db.Set("/webapp", "1")
|
|
| 183 |
+ if err != nil {
|
|
| 184 |
+ t.Fatal(err) |
|
| 185 |
+ } |
|
| 186 |
+ child2, err := db.Set("/db", "2")
|
|
| 187 |
+ if err != nil {
|
|
| 188 |
+ t.Fatal(err) |
|
| 189 |
+ } |
|
| 190 |
+ child4, err := db.Set("/db/logs", "4")
|
|
| 191 |
+ if err != nil {
|
|
| 192 |
+ t.Fatal(err) |
|
| 193 |
+ } |
|
| 194 |
+ if _, err := db.Set("/webapp/logs", child4.ID()); err != nil {
|
|
| 195 |
+ t.Fatal(err) |
|
| 196 |
+ } |
|
| 197 |
+ |
|
| 198 |
+ child3, err := db.Set("/sentry", "3")
|
|
| 199 |
+ if err != nil {
|
|
| 200 |
+ t.Fatal(err) |
|
| 201 |
+ } |
|
| 202 |
+ if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
|
|
| 203 |
+ t.Fatal(err) |
|
| 204 |
+ } |
|
| 205 |
+ if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
|
|
| 206 |
+ t.Fatal(err) |
|
| 207 |
+ } |
|
| 208 |
+ |
|
| 209 |
+ child5, err := db.Set("/gograph", "5")
|
|
| 210 |
+ if err != nil {
|
|
| 211 |
+ t.Fatal(err) |
|
| 212 |
+ } |
|
| 213 |
+ if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
|
|
| 214 |
+ t.Fatal(err) |
|
| 215 |
+ } |
|
| 216 |
+ |
|
| 217 |
+ if err := db.Walk("/", func(p string, e *Entity) error {
|
|
| 218 |
+ t.Logf("Path: %s Entity: %s", p, e.ID())
|
|
| 219 |
+ return nil |
|
| 220 |
+ }, -1); err != nil {
|
|
| 221 |
+ t.Fatal(err) |
|
| 222 |
+ } |
|
| 223 |
+} |
|
| 224 |
+ |
|
| 225 |
+func TestGetEntityByPath(t *testing.T) {
|
|
| 226 |
+ db, dbpath := newTestDb(t) |
|
| 227 |
+ defer destroyTestDb(dbpath) |
|
| 228 |
+ _, err := db.Set("/webapp", "1")
|
|
| 229 |
+ if err != nil {
|
|
| 230 |
+ t.Fatal(err) |
|
| 231 |
+ } |
|
| 232 |
+ child2, err := db.Set("/db", "2")
|
|
| 233 |
+ if err != nil {
|
|
| 234 |
+ t.Fatal(err) |
|
| 235 |
+ } |
|
| 236 |
+ child4, err := db.Set("/logs", "4")
|
|
| 237 |
+ if err != nil {
|
|
| 238 |
+ t.Fatal(err) |
|
| 239 |
+ } |
|
| 240 |
+ if _, err := db.Set("/db/logs", child4.ID()); err != nil {
|
|
| 241 |
+ t.Fatal(err) |
|
| 242 |
+ } |
|
| 243 |
+ |
|
| 244 |
+ child3, err := db.Set("/sentry", "3")
|
|
| 245 |
+ if err != nil {
|
|
| 246 |
+ t.Fatal(err) |
|
| 247 |
+ } |
|
| 248 |
+ if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
|
|
| 249 |
+ t.Fatal(err) |
|
| 250 |
+ } |
|
| 251 |
+ if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
|
|
| 252 |
+ t.Fatal(err) |
|
| 253 |
+ } |
|
| 254 |
+ |
|
| 255 |
+ child5, err := db.Set("/gograph", "5")
|
|
| 256 |
+ if err != nil {
|
|
| 257 |
+ t.Fatal(err) |
|
| 258 |
+ } |
|
| 259 |
+ if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
|
|
| 260 |
+ t.Fatal(err) |
|
| 261 |
+ } |
|
| 262 |
+ |
|
| 263 |
+ entity := db.Get("/webapp/db/logs")
|
|
| 264 |
+ if entity == nil {
|
|
| 265 |
+ t.Fatal("Entity should not be nil")
|
|
| 266 |
+ } |
|
| 267 |
+ if entity.ID() != "4" {
|
|
| 268 |
+ t.Fatalf("Expected to get entity with id 4, got %s", entity.ID())
|
|
| 269 |
+ } |
|
| 270 |
+} |
|
| 271 |
+ |
|
| 272 |
+func TestEnitiesPaths(t *testing.T) {
|
|
| 273 |
+ db, dbpath := newTestDb(t) |
|
| 274 |
+ defer destroyTestDb(dbpath) |
|
| 275 |
+ _, err := db.Set("/webapp", "1")
|
|
| 276 |
+ if err != nil {
|
|
| 277 |
+ t.Fatal(err) |
|
| 278 |
+ } |
|
| 279 |
+ child2, err := db.Set("/db", "2")
|
|
| 280 |
+ if err != nil {
|
|
| 281 |
+ t.Fatal(err) |
|
| 282 |
+ } |
|
| 283 |
+ child4, err := db.Set("/logs", "4")
|
|
| 284 |
+ if err != nil {
|
|
| 285 |
+ t.Fatal(err) |
|
| 286 |
+ } |
|
| 287 |
+ if _, err := db.Set("/db/logs", child4.ID()); err != nil {
|
|
| 288 |
+ t.Fatal(err) |
|
| 289 |
+ } |
|
| 290 |
+ |
|
| 291 |
+ child3, err := db.Set("/sentry", "3")
|
|
| 292 |
+ if err != nil {
|
|
| 293 |
+ t.Fatal(err) |
|
| 294 |
+ } |
|
| 295 |
+ if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
|
|
| 296 |
+ t.Fatal(err) |
|
| 297 |
+ } |
|
| 298 |
+ if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
|
|
| 299 |
+ t.Fatal(err) |
|
| 300 |
+ } |
|
| 301 |
+ |
|
| 302 |
+ child5, err := db.Set("/gograph", "5")
|
|
| 303 |
+ if err != nil {
|
|
| 304 |
+ t.Fatal(err) |
|
| 305 |
+ } |
|
| 306 |
+ if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
|
|
| 307 |
+ t.Fatal(err) |
|
| 308 |
+ } |
|
| 309 |
+ |
|
| 310 |
+ out := db.List("/", -1)
|
|
| 311 |
+ for _, p := range out.Paths() {
|
|
| 312 |
+ t.Log(p) |
|
| 313 |
+ } |
|
| 314 |
+} |
|
| 315 |
+ |
|
| 316 |
+func TestDeleteRootEntity(t *testing.T) {
|
|
| 317 |
+ db, dbpath := newTestDb(t) |
|
| 318 |
+ defer destroyTestDb(dbpath) |
|
| 319 |
+ |
|
| 320 |
+ if err := db.Delete("/"); err == nil {
|
|
| 321 |
+ t.Fatal("Error should not be nil")
|
|
| 322 |
+ } |
|
| 323 |
+} |
|
| 324 |
+ |
|
| 325 |
+func TestDeleteEntity(t *testing.T) {
|
|
| 326 |
+ db, dbpath := newTestDb(t) |
|
| 327 |
+ defer destroyTestDb(dbpath) |
|
| 328 |
+ _, err := db.Set("/webapp", "1")
|
|
| 329 |
+ if err != nil {
|
|
| 330 |
+ t.Fatal(err) |
|
| 331 |
+ } |
|
| 332 |
+ child2, err := db.Set("/db", "2")
|
|
| 333 |
+ if err != nil {
|
|
| 334 |
+ t.Fatal(err) |
|
| 335 |
+ } |
|
| 336 |
+ child4, err := db.Set("/logs", "4")
|
|
| 337 |
+ if err != nil {
|
|
| 338 |
+ t.Fatal(err) |
|
| 339 |
+ } |
|
| 340 |
+ if _, err := db.Set("/db/logs", child4.ID()); err != nil {
|
|
| 341 |
+ t.Fatal(err) |
|
| 342 |
+ } |
|
| 343 |
+ |
|
| 344 |
+ child3, err := db.Set("/sentry", "3")
|
|
| 345 |
+ if err != nil {
|
|
| 346 |
+ t.Fatal(err) |
|
| 347 |
+ } |
|
| 348 |
+ if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
|
|
| 349 |
+ t.Fatal(err) |
|
| 350 |
+ } |
|
| 351 |
+ if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
|
|
| 352 |
+ t.Fatal(err) |
|
| 353 |
+ } |
|
| 354 |
+ |
|
| 355 |
+ child5, err := db.Set("/gograph", "5")
|
|
| 356 |
+ if err != nil {
|
|
| 357 |
+ t.Fatal(err) |
|
| 358 |
+ } |
|
| 359 |
+ if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
|
|
| 360 |
+ t.Fatal(err) |
|
| 361 |
+ } |
|
| 362 |
+ |
|
| 363 |
+ if err := db.Delete("/webapp/sentry"); err != nil {
|
|
| 364 |
+ t.Fatal(err) |
|
| 365 |
+ } |
|
| 366 |
+ entity := db.Get("/webapp/sentry")
|
|
| 367 |
+ if entity != nil {
|
|
| 368 |
+ t.Fatal("Entity /webapp/sentry should be nil")
|
|
| 369 |
+ } |
|
| 370 |
+} |
|
| 371 |
+ |
|
| 372 |
+func TestCountRefs(t *testing.T) {
|
|
| 373 |
+ db, dbpath := newTestDb(t) |
|
| 374 |
+ defer destroyTestDb(dbpath) |
|
| 375 |
+ |
|
| 376 |
+ db.Set("/webapp", "1")
|
|
| 377 |
+ |
|
| 378 |
+ if db.Refs("1") != 1 {
|
|
| 379 |
+ t.Fatal("Expect reference count to be 1")
|
|
| 380 |
+ } |
|
| 381 |
+ |
|
| 382 |
+ db.Set("/db", "2")
|
|
| 383 |
+ db.Set("/webapp/db", "2")
|
|
| 384 |
+ if db.Refs("2") != 2 {
|
|
| 385 |
+ t.Fatal("Expect reference count to be 2")
|
|
| 386 |
+ } |
|
| 387 |
+} |
|
| 388 |
+ |
|
| 389 |
+func TestPurgeId(t *testing.T) {
|
|
| 390 |
+ db, dbpath := newTestDb(t) |
|
| 391 |
+ defer destroyTestDb(dbpath) |
|
| 392 |
+ |
|
| 393 |
+ db.Set("/webapp", "1")
|
|
| 394 |
+ |
|
| 395 |
+ if db.Refs("1") != 1 {
|
|
| 396 |
+ t.Fatal("Expect reference count to be 1")
|
|
| 397 |
+ } |
|
| 398 |
+ |
|
| 399 |
+ db.Set("/db", "2")
|
|
| 400 |
+ db.Set("/webapp/db", "2")
|
|
| 401 |
+ |
|
| 402 |
+ count, err := db.Purge("2")
|
|
| 403 |
+ if err != nil {
|
|
| 404 |
+ t.Fatal(err) |
|
| 405 |
+ } |
|
| 406 |
+ if count != 2 {
|
|
| 407 |
+ t.Fatal("Expected 2 references to be removed")
|
|
| 408 |
+ } |
|
| 409 |
+} |
|
| 410 |
+ |
|
| 411 |
+func TestRename(t *testing.T) {
|
|
| 412 |
+ db, dbpath := newTestDb(t) |
|
| 413 |
+ defer destroyTestDb(dbpath) |
|
| 414 |
+ |
|
| 415 |
+ db.Set("/webapp", "1")
|
|
| 416 |
+ |
|
| 417 |
+ if db.Refs("1") != 1 {
|
|
| 418 |
+ t.Fatal("Expect reference count to be 1")
|
|
| 419 |
+ } |
|
| 420 |
+ |
|
| 421 |
+ db.Set("/db", "2")
|
|
| 422 |
+ db.Set("/webapp/db", "2")
|
|
| 423 |
+ |
|
| 424 |
+ if db.Get("/webapp/db") == nil {
|
|
| 425 |
+ t.Fatal("Cannot find entity at path /webapp/db")
|
|
| 426 |
+ } |
|
| 427 |
+ |
|
| 428 |
+ if err := db.Rename("/webapp/db", "/webapp/newdb"); err != nil {
|
|
| 429 |
+ t.Fatal(err) |
|
| 430 |
+ } |
|
| 431 |
+ if db.Get("/webapp/db") != nil {
|
|
| 432 |
+ t.Fatal("Entity should not exist at /webapp/db")
|
|
| 433 |
+ } |
|
| 434 |
+ if db.Get("/webapp/newdb") == nil {
|
|
| 435 |
+ t.Fatal("Cannot find entity at path /webapp/newdb")
|
|
| 436 |
+ } |
|
| 437 |
+ |
|
| 438 |
+} |
|
| 439 |
+ |
|
| 440 |
+func TestCreateMultipleNames(t *testing.T) {
|
|
| 441 |
+ db, dbpath := newTestDb(t) |
|
| 442 |
+ defer destroyTestDb(dbpath) |
|
| 443 |
+ |
|
| 444 |
+ db.Set("/db", "1")
|
|
| 445 |
+ if _, err := db.Set("/myapp", "1"); err != nil {
|
|
| 446 |
+ t.Fatal(err) |
|
| 447 |
+ } |
|
| 448 |
+ |
|
| 449 |
+ db.Walk("/", func(p string, e *Entity) error {
|
|
| 450 |
+ t.Logf("%s\n", p)
|
|
| 451 |
+ return nil |
|
| 452 |
+ }, -1) |
|
| 453 |
+} |
|
| 454 |
+ |
|
| 455 |
+func TestRefPaths(t *testing.T) {
|
|
| 456 |
+ db, dbpath := newTestDb(t) |
|
| 457 |
+ defer destroyTestDb(dbpath) |
|
| 458 |
+ |
|
| 459 |
+ db.Set("/webapp", "1")
|
|
| 460 |
+ |
|
| 461 |
+ db.Set("/db", "2")
|
|
| 462 |
+ db.Set("/webapp/db", "2")
|
|
| 463 |
+ |
|
| 464 |
+ refs := db.RefPaths("2")
|
|
| 465 |
+ if len(refs) != 2 {
|
|
| 466 |
+ t.Fatalf("Expected reference count to be 2, got %d", len(refs))
|
|
| 467 |
+ } |
|
| 468 |
+} |
|
| 469 |
+ |
|
| 470 |
+func TestExistsTrue(t *testing.T) {
|
|
| 471 |
+ db, dbpath := newTestDb(t) |
|
| 472 |
+ defer destroyTestDb(dbpath) |
|
| 473 |
+ |
|
| 474 |
+ db.Set("/testing", "1")
|
|
| 475 |
+ |
|
| 476 |
+ if !db.Exists("/testing") {
|
|
| 477 |
+ t.Fatalf("/tesing should exist")
|
|
| 478 |
+ } |
|
| 479 |
+} |
|
| 480 |
+ |
|
| 481 |
+func TestExistsFalse(t *testing.T) {
|
|
| 482 |
+ db, dbpath := newTestDb(t) |
|
| 483 |
+ defer destroyTestDb(dbpath) |
|
| 484 |
+ |
|
| 485 |
+ db.Set("/toerhe", "1")
|
|
| 486 |
+ |
|
| 487 |
+ if db.Exists("/testing") {
|
|
| 488 |
+ t.Fatalf("/tesing should not exist")
|
|
| 489 |
+ } |
|
| 490 |
+ |
|
| 491 |
+} |
|
| 492 |
+ |
|
| 493 |
+func TestGetNameWithTrailingSlash(t *testing.T) {
|
|
| 494 |
+ db, dbpath := newTestDb(t) |
|
| 495 |
+ defer destroyTestDb(dbpath) |
|
| 496 |
+ |
|
| 497 |
+ db.Set("/todo", "1")
|
|
| 498 |
+ |
|
| 499 |
+ e := db.Get("/todo/")
|
|
| 500 |
+ if e == nil {
|
|
| 501 |
+ t.Fatalf("Entity should not be nil")
|
|
| 502 |
+ } |
|
| 503 |
+} |
|
| 504 |
+ |
|
| 505 |
+func TestConcurrentWrites(t *testing.T) {
|
|
| 506 |
+ db, dbpath := newTestDb(t) |
|
| 507 |
+ defer destroyTestDb(dbpath) |
|
| 508 |
+ |
|
| 509 |
+ errs := make(chan error, 2) |
|
| 510 |
+ |
|
| 511 |
+ save := func(name string, id string) {
|
|
| 512 |
+ if _, err := db.Set(fmt.Sprintf("/%s", name), id); err != nil {
|
|
| 513 |
+ errs <- err |
|
| 514 |
+ } |
|
| 515 |
+ errs <- nil |
|
| 516 |
+ } |
|
| 517 |
+ purge := func(id string) {
|
|
| 518 |
+ if _, err := db.Purge(id); err != nil {
|
|
| 519 |
+ errs <- err |
|
| 520 |
+ } |
|
| 521 |
+ errs <- nil |
|
| 522 |
+ } |
|
| 523 |
+ |
|
| 524 |
+ save("/1", "1")
|
|
| 525 |
+ |
|
| 526 |
+ go purge("1")
|
|
| 527 |
+ go save("/2", "2")
|
|
| 528 |
+ |
|
| 529 |
+ any := false |
|
| 530 |
+ for i := 0; i < 2; i++ {
|
|
| 531 |
+ if err := <-errs; err != nil {
|
|
| 532 |
+ any = true |
|
| 533 |
+ t.Log(err) |
|
| 534 |
+ } |
|
| 535 |
+ } |
|
| 536 |
+ if any {
|
|
| 537 |
+ t.Fatal() |
|
| 538 |
+ } |
|
| 539 |
+} |
| 0 | 540 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,27 @@ |
| 0 |
+package graphdb |
|
| 1 |
+ |
|
| 2 |
+import "sort" |
|
| 3 |
+ |
|
| 4 |
+type pathSorter struct {
|
|
| 5 |
+ paths []string |
|
| 6 |
+ by func(i, j string) bool |
|
| 7 |
+} |
|
| 8 |
+ |
|
| 9 |
+func sortByDepth(paths []string) {
|
|
| 10 |
+ s := &pathSorter{paths, func(i, j string) bool {
|
|
| 11 |
+ return PathDepth(i) > PathDepth(j) |
|
| 12 |
+ }} |
|
| 13 |
+ sort.Sort(s) |
|
| 14 |
+} |
|
| 15 |
+ |
|
| 16 |
+func (s *pathSorter) Len() int {
|
|
| 17 |
+ return len(s.paths) |
|
| 18 |
+} |
|
| 19 |
+ |
|
| 20 |
+func (s *pathSorter) Swap(i, j int) {
|
|
| 21 |
+ s.paths[i], s.paths[j] = s.paths[j], s.paths[i] |
|
| 22 |
+} |
|
| 23 |
+ |
|
| 24 |
+func (s *pathSorter) Less(i, j int) bool {
|
|
| 25 |
+ return s.by(s.paths[i], s.paths[j]) |
|
| 26 |
+} |
| 0 | 27 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,29 @@ |
| 0 |
+package graphdb |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "testing" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+func TestSort(t *testing.T) {
|
|
| 7 |
+ paths := []string{
|
|
| 8 |
+ "/", |
|
| 9 |
+ "/myreallylongname", |
|
| 10 |
+ "/app/db", |
|
| 11 |
+ } |
|
| 12 |
+ |
|
| 13 |
+ sortByDepth(paths) |
|
| 14 |
+ |
|
| 15 |
+ if len(paths) != 3 {
|
|
| 16 |
+ t.Fatalf("Expected 3 parts got %d", len(paths))
|
|
| 17 |
+ } |
|
| 18 |
+ |
|
| 19 |
+ if paths[0] != "/app/db" {
|
|
| 20 |
+ t.Fatalf("Expected /app/db got %s", paths[0])
|
|
| 21 |
+ } |
|
| 22 |
+ if paths[1] != "/myreallylongname" {
|
|
| 23 |
+ t.Fatalf("Expected /myreallylongname got %s", paths[1])
|
|
| 24 |
+ } |
|
| 25 |
+ if paths[2] != "/" {
|
|
| 26 |
+ t.Fatalf("Expected / got %s", paths[2])
|
|
| 27 |
+ } |
|
| 28 |
+} |
| 0 | 29 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,32 @@ |
| 0 |
+package graphdb |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "path" |
|
| 4 |
+ "strings" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 7 |
+// Split p on / |
|
| 8 |
+func split(p string) []string {
|
|
| 9 |
+ return strings.Split(p, "/") |
|
| 10 |
+} |
|
| 11 |
+ |
|
| 12 |
+// Returns the depth or number of / in a given path |
|
| 13 |
+func PathDepth(p string) int {
|
|
| 14 |
+ parts := split(p) |
|
| 15 |
+ if len(parts) == 2 && parts[1] == "" {
|
|
| 16 |
+ return 1 |
|
| 17 |
+ } |
|
| 18 |
+ return len(parts) |
|
| 19 |
+} |
|
| 20 |
+ |
|
| 21 |
+func splitPath(p string) (parent, name string) {
|
|
| 22 |
+ if p[0] != '/' {
|
|
| 23 |
+ p = "/" + p |
|
| 24 |
+ } |
|
| 25 |
+ parent, name = path.Split(p) |
|
| 26 |
+ l := len(parent) |
|
| 27 |
+ if parent[l-1] == '/' {
|
|
| 28 |
+ parent = parent[:l-1] |
|
| 29 |
+ } |
|
| 30 |
+ return |
|
| 31 |
+} |
| 0 | 32 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,336 @@ |
| 0 |
+/* |
|
| 1 |
+ |
|
| 2 |
+aufs driver directory structure |
|
| 3 |
+ |
|
| 4 |
+. |
|
| 5 |
+├── layers // Metadata of layers |
|
| 6 |
+│  ├── 1 |
|
| 7 |
+│  ├── 2 |
|
| 8 |
+│  └── 3 |
|
| 9 |
+├── diffs // Content of the layer |
|
| 10 |
+│  ├── 1 // Contains layers that need to be mounted for the id |
|
| 11 |
+│  ├── 2 |
|
| 12 |
+│  └── 3 |
|
| 13 |
+└── mnt // Mount points for the rw layers to be mounted |
|
| 14 |
+ ├── 1 |
|
| 15 |
+ ├── 2 |
|
| 16 |
+ └── 3 |
|
| 17 |
+ |
|
| 18 |
+*/ |
|
| 19 |
+ |
|
| 20 |
+package aufs |
|
| 21 |
+ |
|
| 22 |
+import ( |
|
| 23 |
+ "bufio" |
|
| 24 |
+ "fmt" |
|
| 25 |
+ "github.com/dotcloud/docker/archive" |
|
| 26 |
+ "github.com/dotcloud/docker/graphdriver" |
|
| 27 |
+ "github.com/dotcloud/docker/utils" |
|
| 28 |
+ "log" |
|
| 29 |
+ "os" |
|
| 30 |
+ "os/exec" |
|
| 31 |
+ "path" |
|
| 32 |
+ "strings" |
|
| 33 |
+) |
|
| 34 |
+ |
|
| 35 |
+func init() {
|
|
| 36 |
+ graphdriver.Register("aufs", Init)
|
|
| 37 |
+} |
|
| 38 |
+ |
|
| 39 |
+type Driver struct {
|
|
| 40 |
+ root string |
|
| 41 |
+} |
|
| 42 |
+ |
|
| 43 |
+// New returns a new AUFS driver. |
|
| 44 |
+// An error is returned if AUFS is not supported. |
|
| 45 |
+func Init(root string) (graphdriver.Driver, error) {
|
|
| 46 |
+ // Try to load the aufs kernel module |
|
| 47 |
+ if err := supportsAufs(); err != nil {
|
|
| 48 |
+ return nil, err |
|
| 49 |
+ } |
|
| 50 |
+ paths := []string{
|
|
| 51 |
+ "mnt", |
|
| 52 |
+ "diff", |
|
| 53 |
+ "layers", |
|
| 54 |
+ } |
|
| 55 |
+ |
|
| 56 |
+ // Create the root aufs driver dir and return |
|
| 57 |
+ // if it already exists |
|
| 58 |
+ // If not populate the dir structure |
|
| 59 |
+ if err := os.MkdirAll(root, 0755); err != nil {
|
|
| 60 |
+ if os.IsExist(err) {
|
|
| 61 |
+ return &Driver{root}, nil
|
|
| 62 |
+ } |
|
| 63 |
+ return nil, err |
|
| 64 |
+ } |
|
| 65 |
+ |
|
| 66 |
+ for _, p := range paths {
|
|
| 67 |
+ if err := os.MkdirAll(path.Join(root, p), 0755); err != nil {
|
|
| 68 |
+ return nil, err |
|
| 69 |
+ } |
|
| 70 |
+ } |
|
| 71 |
+ return &Driver{root}, nil
|
|
| 72 |
+} |
|
| 73 |
+ |
|
| 74 |
+// Return a nil error if the kernel supports aufs |
|
| 75 |
+// We cannot modprobe because inside dind modprobe fails |
|
| 76 |
+// to run |
|
| 77 |
+func supportsAufs() error {
|
|
| 78 |
+ // We can try to modprobe aufs first before looking at |
|
| 79 |
+ // proc/filesystems for when aufs is supported |
|
| 80 |
+ exec.Command("modprobe", "aufs").Run()
|
|
| 81 |
+ |
|
| 82 |
+ f, err := os.Open("/proc/filesystems")
|
|
| 83 |
+ if err != nil {
|
|
| 84 |
+ return err |
|
| 85 |
+ } |
|
| 86 |
+ defer f.Close() |
|
| 87 |
+ |
|
| 88 |
+ s := bufio.NewScanner(f) |
|
| 89 |
+ for s.Scan() {
|
|
| 90 |
+ if strings.Contains(s.Text(), "aufs") {
|
|
| 91 |
+ return nil |
|
| 92 |
+ } |
|
| 93 |
+ } |
|
| 94 |
+ return fmt.Errorf("AUFS was not found in /proc/filesystems")
|
|
| 95 |
+} |
|
| 96 |
+ |
|
| 97 |
+func (a Driver) rootPath() string {
|
|
| 98 |
+ return a.root |
|
| 99 |
+} |
|
| 100 |
+ |
|
| 101 |
+func (Driver) String() string {
|
|
| 102 |
+ return "aufs" |
|
| 103 |
+} |
|
| 104 |
+ |
|
| 105 |
+func (a Driver) Status() [][2]string {
|
|
| 106 |
+ ids, _ := loadIds(path.Join(a.rootPath(), "layers")) |
|
| 107 |
+ return [][2]string{
|
|
| 108 |
+ {"Root Dir", a.rootPath()},
|
|
| 109 |
+ {"Dirs", fmt.Sprintf("%d", len(ids))},
|
|
| 110 |
+ } |
|
| 111 |
+} |
|
| 112 |
+ |
|
| 113 |
+// Exists returns true if the given id is registered with |
|
| 114 |
+// this driver |
|
| 115 |
+func (a Driver) Exists(id string) bool {
|
|
| 116 |
+ if _, err := os.Lstat(path.Join(a.rootPath(), "layers", id)); err != nil {
|
|
| 117 |
+ return false |
|
| 118 |
+ } |
|
| 119 |
+ return true |
|
| 120 |
+} |
|
| 121 |
+ |
|
| 122 |
+// Three folders are created for each id |
|
| 123 |
+// mnt, layers, and diff |
|
| 124 |
+func (a *Driver) Create(id, parent string) error {
|
|
| 125 |
+ if err := a.createDirsFor(id); err != nil {
|
|
| 126 |
+ return err |
|
| 127 |
+ } |
|
| 128 |
+ // Write the layers metadata |
|
| 129 |
+ f, err := os.Create(path.Join(a.rootPath(), "layers", id)) |
|
| 130 |
+ if err != nil {
|
|
| 131 |
+ return err |
|
| 132 |
+ } |
|
| 133 |
+ defer f.Close() |
|
| 134 |
+ |
|
| 135 |
+ if parent != "" {
|
|
| 136 |
+ ids, err := getParentIds(a.rootPath(), parent) |
|
| 137 |
+ if err != nil {
|
|
| 138 |
+ return err |
|
| 139 |
+ } |
|
| 140 |
+ |
|
| 141 |
+ if _, err := fmt.Fprintln(f, parent); err != nil {
|
|
| 142 |
+ return err |
|
| 143 |
+ } |
|
| 144 |
+ for _, i := range ids {
|
|
| 145 |
+ if _, err := fmt.Fprintln(f, i); err != nil {
|
|
| 146 |
+ return err |
|
| 147 |
+ } |
|
| 148 |
+ } |
|
| 149 |
+ } |
|
| 150 |
+ return nil |
|
| 151 |
+} |
|
| 152 |
+ |
|
| 153 |
+func (a *Driver) createDirsFor(id string) error {
|
|
| 154 |
+ paths := []string{
|
|
| 155 |
+ "mnt", |
|
| 156 |
+ "diff", |
|
| 157 |
+ } |
|
| 158 |
+ |
|
| 159 |
+ for _, p := range paths {
|
|
| 160 |
+ if err := os.MkdirAll(path.Join(a.rootPath(), p, id), 0755); err != nil {
|
|
| 161 |
+ return err |
|
| 162 |
+ } |
|
| 163 |
+ } |
|
| 164 |
+ return nil |
|
| 165 |
+} |
|
| 166 |
+ |
|
| 167 |
+// Unmount and remove the dir information |
|
| 168 |
+func (a *Driver) Remove(id string) error {
|
|
| 169 |
+ // Make sure the dir is umounted first |
|
| 170 |
+ if err := a.unmount(id); err != nil {
|
|
| 171 |
+ return err |
|
| 172 |
+ } |
|
| 173 |
+ tmpDirs := []string{
|
|
| 174 |
+ "mnt", |
|
| 175 |
+ "diff", |
|
| 176 |
+ } |
|
| 177 |
+ |
|
| 178 |
+ // Remove the dirs atomically |
|
| 179 |
+ for _, p := range tmpDirs {
|
|
| 180 |
+ // We need to use a temp dir in the same dir as the driver so Rename |
|
| 181 |
+ // does not fall back to the slow copy if /tmp and the driver dir |
|
| 182 |
+ // are on different devices |
|
| 183 |
+ tmp := path.Join(a.rootPath(), "tmp", p, id) |
|
| 184 |
+ if err := os.MkdirAll(tmp, 0755); err != nil {
|
|
| 185 |
+ return err |
|
| 186 |
+ } |
|
| 187 |
+ realPath := path.Join(a.rootPath(), p, id) |
|
| 188 |
+ if err := os.Rename(realPath, tmp); err != nil && !os.IsNotExist(err) {
|
|
| 189 |
+ return err |
|
| 190 |
+ } |
|
| 191 |
+ defer os.RemoveAll(tmp) |
|
| 192 |
+ } |
|
| 193 |
+ |
|
| 194 |
+ // Remove the layers file for the id |
|
| 195 |
+ if err := os.Remove(path.Join(a.rootPath(), "layers", id)); err != nil && !os.IsNotExist(err) {
|
|
| 196 |
+ return err |
|
| 197 |
+ } |
|
| 198 |
+ return nil |
|
| 199 |
+} |
|
| 200 |
+ |
|
| 201 |
+// Return the rootfs path for the id |
|
| 202 |
+// This will mount the dir at it's given path |
|
| 203 |
+func (a *Driver) Get(id string) (string, error) {
|
|
| 204 |
+ ids, err := getParentIds(a.rootPath(), id) |
|
| 205 |
+ if err != nil {
|
|
| 206 |
+ if !os.IsNotExist(err) {
|
|
| 207 |
+ return "", err |
|
| 208 |
+ } |
|
| 209 |
+ ids = []string{}
|
|
| 210 |
+ } |
|
| 211 |
+ |
|
| 212 |
+ // If a dir does not have a parent ( no layers )do not try to mount |
|
| 213 |
+ // just return the diff path to the data |
|
| 214 |
+ out := path.Join(a.rootPath(), "diff", id) |
|
| 215 |
+ if len(ids) > 0 {
|
|
| 216 |
+ out = path.Join(a.rootPath(), "mnt", id) |
|
| 217 |
+ if err := a.mount(id); err != nil {
|
|
| 218 |
+ return "", err |
|
| 219 |
+ } |
|
| 220 |
+ } |
|
| 221 |
+ return out, nil |
|
| 222 |
+} |
|
| 223 |
+ |
|
| 224 |
+// Returns an archive of the contents for the id |
|
| 225 |
+func (a *Driver) Diff(id string) (archive.Archive, error) {
|
|
| 226 |
+ return archive.TarFilter(path.Join(a.rootPath(), "diff", id), &archive.TarOptions{
|
|
| 227 |
+ Recursive: true, |
|
| 228 |
+ Compression: archive.Uncompressed, |
|
| 229 |
+ }) |
|
| 230 |
+} |
|
| 231 |
+ |
|
| 232 |
+func (a *Driver) ApplyDiff(id string, diff archive.Archive) error {
|
|
| 233 |
+ return archive.Untar(diff, path.Join(a.rootPath(), "diff", id), nil) |
|
| 234 |
+} |
|
| 235 |
+ |
|
| 236 |
+// Returns the size of the contents for the id |
|
| 237 |
+func (a *Driver) DiffSize(id string) (int64, error) {
|
|
| 238 |
+ return utils.TreeSize(path.Join(a.rootPath(), "diff", id)) |
|
| 239 |
+} |
|
| 240 |
+ |
|
| 241 |
+func (a *Driver) Changes(id string) ([]archive.Change, error) {
|
|
| 242 |
+ layers, err := a.getParentLayerPaths(id) |
|
| 243 |
+ if err != nil {
|
|
| 244 |
+ return nil, err |
|
| 245 |
+ } |
|
| 246 |
+ return archive.Changes(layers, path.Join(a.rootPath(), "diff", id)) |
|
| 247 |
+} |
|
| 248 |
+ |
|
| 249 |
+func (a *Driver) getParentLayerPaths(id string) ([]string, error) {
|
|
| 250 |
+ parentIds, err := getParentIds(a.rootPath(), id) |
|
| 251 |
+ if err != nil {
|
|
| 252 |
+ return nil, err |
|
| 253 |
+ } |
|
| 254 |
+ if len(parentIds) == 0 {
|
|
| 255 |
+ return nil, fmt.Errorf("Dir %s does not have any parent layers", id)
|
|
| 256 |
+ } |
|
| 257 |
+ layers := make([]string, len(parentIds)) |
|
| 258 |
+ |
|
| 259 |
+ // Get the diff paths for all the parent ids |
|
| 260 |
+ for i, p := range parentIds {
|
|
| 261 |
+ layers[i] = path.Join(a.rootPath(), "diff", p) |
|
| 262 |
+ } |
|
| 263 |
+ return layers, nil |
|
| 264 |
+} |
|
| 265 |
+ |
|
| 266 |
+func (a *Driver) mount(id string) error {
|
|
| 267 |
+ // If the id is mounted or we get an error return |
|
| 268 |
+ if mounted, err := a.mounted(id); err != nil || mounted {
|
|
| 269 |
+ return err |
|
| 270 |
+ } |
|
| 271 |
+ |
|
| 272 |
+ var ( |
|
| 273 |
+ target = path.Join(a.rootPath(), "mnt", id) |
|
| 274 |
+ rw = path.Join(a.rootPath(), "diff", id) |
|
| 275 |
+ ) |
|
| 276 |
+ |
|
| 277 |
+ layers, err := a.getParentLayerPaths(id) |
|
| 278 |
+ if err != nil {
|
|
| 279 |
+ return err |
|
| 280 |
+ } |
|
| 281 |
+ |
|
| 282 |
+ if err := a.aufsMount(layers, rw, target); err != nil {
|
|
| 283 |
+ return err |
|
| 284 |
+ } |
|
| 285 |
+ return nil |
|
| 286 |
+} |
|
| 287 |
+ |
|
| 288 |
+func (a *Driver) unmount(id string) error {
|
|
| 289 |
+ if mounted, err := a.mounted(id); err != nil || !mounted {
|
|
| 290 |
+ return err |
|
| 291 |
+ } |
|
| 292 |
+ target := path.Join(a.rootPath(), "mnt", id) |
|
| 293 |
+ return Unmount(target) |
|
| 294 |
+} |
|
| 295 |
+ |
|
| 296 |
+func (a *Driver) mounted(id string) (bool, error) {
|
|
| 297 |
+ target := path.Join(a.rootPath(), "mnt", id) |
|
| 298 |
+ return Mounted(target) |
|
| 299 |
+} |
|
| 300 |
+ |
|
| 301 |
+// During cleanup aufs needs to unmount all mountpoints |
|
| 302 |
+func (a *Driver) Cleanup() error {
|
|
| 303 |
+ ids, err := loadIds(path.Join(a.rootPath(), "layers")) |
|
| 304 |
+ if err != nil {
|
|
| 305 |
+ return err |
|
| 306 |
+ } |
|
| 307 |
+ for _, id := range ids {
|
|
| 308 |
+ if err := a.unmount(id); err != nil {
|
|
| 309 |
+ utils.Errorf("Unmounting %s: %s", utils.TruncateID(id), err)
|
|
| 310 |
+ } |
|
| 311 |
+ } |
|
| 312 |
+ return nil |
|
| 313 |
+} |
|
| 314 |
+ |
|
| 315 |
+func (a *Driver) aufsMount(ro []string, rw, target string) error {
|
|
| 316 |
+ rwBranch := fmt.Sprintf("%v=rw", rw)
|
|
| 317 |
+ roBranches := "" |
|
| 318 |
+ for _, layer := range ro {
|
|
| 319 |
+ roBranches += fmt.Sprintf("%v=ro+wh:", layer)
|
|
| 320 |
+ } |
|
| 321 |
+ branches := fmt.Sprintf("br:%v:%v,xino=/dev/shm/aufs.xino", rwBranch, roBranches)
|
|
| 322 |
+ |
|
| 323 |
+ //if error, try to load aufs kernel module |
|
| 324 |
+ if err := mount("none", target, "aufs", 0, branches); err != nil {
|
|
| 325 |
+ log.Printf("Kernel does not support AUFS, trying to load the AUFS module with modprobe...")
|
|
| 326 |
+ if err := exec.Command("modprobe", "aufs").Run(); err != nil {
|
|
| 327 |
+ return fmt.Errorf("Unable to load the AUFS module")
|
|
| 328 |
+ } |
|
| 329 |
+ log.Printf("...module loaded.")
|
|
| 330 |
+ if err := mount("none", target, "aufs", 0, branches); err != nil {
|
|
| 331 |
+ return fmt.Errorf("Unable to mount using aufs %s", err)
|
|
| 332 |
+ } |
|
| 333 |
+ } |
|
| 334 |
+ return nil |
|
| 335 |
+} |
| 0 | 336 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,623 @@ |
| 0 |
+package aufs |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "github.com/dotcloud/docker/archive" |
|
| 4 |
+ "os" |
|
| 5 |
+ "path" |
|
| 6 |
+ "testing" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+var ( |
|
| 10 |
+ tmp = path.Join(os.TempDir(), "aufs-tests", "aufs") |
|
| 11 |
+) |
|
| 12 |
+ |
|
| 13 |
+func newDriver(t *testing.T) *Driver {
|
|
| 14 |
+ if err := os.MkdirAll(tmp, 0755); err != nil {
|
|
| 15 |
+ t.Fatal(err) |
|
| 16 |
+ } |
|
| 17 |
+ |
|
| 18 |
+ d, err := Init(tmp) |
|
| 19 |
+ if err != nil {
|
|
| 20 |
+ t.Fatal(err) |
|
| 21 |
+ } |
|
| 22 |
+ return d.(*Driver) |
|
| 23 |
+} |
|
| 24 |
+ |
|
| 25 |
+func TestNewDriver(t *testing.T) {
|
|
| 26 |
+ if err := os.MkdirAll(tmp, 0755); err != nil {
|
|
| 27 |
+ t.Fatal(err) |
|
| 28 |
+ } |
|
| 29 |
+ |
|
| 30 |
+ d, err := Init(tmp) |
|
| 31 |
+ if err != nil {
|
|
| 32 |
+ t.Fatal(err) |
|
| 33 |
+ } |
|
| 34 |
+ defer os.RemoveAll(tmp) |
|
| 35 |
+ if d == nil {
|
|
| 36 |
+ t.Fatalf("Driver should not be nil")
|
|
| 37 |
+ } |
|
| 38 |
+} |
|
| 39 |
+ |
|
| 40 |
+func TestAufsString(t *testing.T) {
|
|
| 41 |
+ d := newDriver(t) |
|
| 42 |
+ defer os.RemoveAll(tmp) |
|
| 43 |
+ |
|
| 44 |
+ if d.String() != "aufs" {
|
|
| 45 |
+ t.Fatalf("Expected aufs got %s", d.String())
|
|
| 46 |
+ } |
|
| 47 |
+} |
|
| 48 |
+ |
|
| 49 |
+func TestCreateDirStructure(t *testing.T) {
|
|
| 50 |
+ newDriver(t) |
|
| 51 |
+ defer os.RemoveAll(tmp) |
|
| 52 |
+ |
|
| 53 |
+ paths := []string{
|
|
| 54 |
+ "mnt", |
|
| 55 |
+ "layers", |
|
| 56 |
+ "diff", |
|
| 57 |
+ } |
|
| 58 |
+ |
|
| 59 |
+ for _, p := range paths {
|
|
| 60 |
+ if _, err := os.Stat(path.Join(tmp, p)); err != nil {
|
|
| 61 |
+ t.Fatal(err) |
|
| 62 |
+ } |
|
| 63 |
+ } |
|
| 64 |
+} |
|
| 65 |
+ |
|
| 66 |
+// We should be able to create two drivers with the same dir structure |
|
| 67 |
+func TestNewDriverFromExistingDir(t *testing.T) {
|
|
| 68 |
+ if err := os.MkdirAll(tmp, 0755); err != nil {
|
|
| 69 |
+ t.Fatal(err) |
|
| 70 |
+ } |
|
| 71 |
+ |
|
| 72 |
+ if _, err := Init(tmp); err != nil {
|
|
| 73 |
+ t.Fatal(err) |
|
| 74 |
+ } |
|
| 75 |
+ if _, err := Init(tmp); err != nil {
|
|
| 76 |
+ t.Fatal(err) |
|
| 77 |
+ } |
|
| 78 |
+ os.RemoveAll(tmp) |
|
| 79 |
+} |
|
| 80 |
+ |
|
| 81 |
+func TestCreateNewDir(t *testing.T) {
|
|
| 82 |
+ d := newDriver(t) |
|
| 83 |
+ defer os.RemoveAll(tmp) |
|
| 84 |
+ |
|
| 85 |
+ if err := d.Create("1", ""); err != nil {
|
|
| 86 |
+ t.Fatal(err) |
|
| 87 |
+ } |
|
| 88 |
+} |
|
| 89 |
+ |
|
| 90 |
+func TestCreateNewDirStructure(t *testing.T) {
|
|
| 91 |
+ d := newDriver(t) |
|
| 92 |
+ defer os.RemoveAll(tmp) |
|
| 93 |
+ |
|
| 94 |
+ if err := d.Create("1", ""); err != nil {
|
|
| 95 |
+ t.Fatal(err) |
|
| 96 |
+ } |
|
| 97 |
+ |
|
| 98 |
+ paths := []string{
|
|
| 99 |
+ "mnt", |
|
| 100 |
+ "diff", |
|
| 101 |
+ "layers", |
|
| 102 |
+ } |
|
| 103 |
+ |
|
| 104 |
+ for _, p := range paths {
|
|
| 105 |
+ if _, err := os.Stat(path.Join(tmp, p, "1")); err != nil {
|
|
| 106 |
+ t.Fatal(err) |
|
| 107 |
+ } |
|
| 108 |
+ } |
|
| 109 |
+} |
|
| 110 |
+ |
|
| 111 |
+func TestRemoveImage(t *testing.T) {
|
|
| 112 |
+ d := newDriver(t) |
|
| 113 |
+ defer os.RemoveAll(tmp) |
|
| 114 |
+ |
|
| 115 |
+ if err := d.Create("1", ""); err != nil {
|
|
| 116 |
+ t.Fatal(err) |
|
| 117 |
+ } |
|
| 118 |
+ |
|
| 119 |
+ if err := d.Remove("1"); err != nil {
|
|
| 120 |
+ t.Fatal(err) |
|
| 121 |
+ } |
|
| 122 |
+ |
|
| 123 |
+ paths := []string{
|
|
| 124 |
+ "mnt", |
|
| 125 |
+ "diff", |
|
| 126 |
+ "layers", |
|
| 127 |
+ } |
|
| 128 |
+ |
|
| 129 |
+ for _, p := range paths {
|
|
| 130 |
+ if _, err := os.Stat(path.Join(tmp, p, "1")); err == nil {
|
|
| 131 |
+ t.Fatalf("Error should not be nil because dirs with id 1 should be delted: %s", p)
|
|
| 132 |
+ } |
|
| 133 |
+ } |
|
| 134 |
+} |
|
| 135 |
+ |
|
| 136 |
+func TestGetWithoutParent(t *testing.T) {
|
|
| 137 |
+ d := newDriver(t) |
|
| 138 |
+ defer os.RemoveAll(tmp) |
|
| 139 |
+ |
|
| 140 |
+ if err := d.Create("1", ""); err != nil {
|
|
| 141 |
+ t.Fatal(err) |
|
| 142 |
+ } |
|
| 143 |
+ |
|
| 144 |
+ diffPath, err := d.Get("1")
|
|
| 145 |
+ if err != nil {
|
|
| 146 |
+ t.Fatal(err) |
|
| 147 |
+ } |
|
| 148 |
+ expected := path.Join(tmp, "diff", "1") |
|
| 149 |
+ if diffPath != expected {
|
|
| 150 |
+ t.Fatalf("Expected path %s got %s", expected, diffPath)
|
|
| 151 |
+ } |
|
| 152 |
+} |
|
| 153 |
+ |
|
| 154 |
+func TestCleanupWithNoDirs(t *testing.T) {
|
|
| 155 |
+ d := newDriver(t) |
|
| 156 |
+ defer os.RemoveAll(tmp) |
|
| 157 |
+ |
|
| 158 |
+ if err := d.Cleanup(); err != nil {
|
|
| 159 |
+ t.Fatal(err) |
|
| 160 |
+ } |
|
| 161 |
+} |
|
| 162 |
+ |
|
| 163 |
+func TestCleanupWithDir(t *testing.T) {
|
|
| 164 |
+ d := newDriver(t) |
|
| 165 |
+ defer os.RemoveAll(tmp) |
|
| 166 |
+ |
|
| 167 |
+ if err := d.Create("1", ""); err != nil {
|
|
| 168 |
+ t.Fatal(err) |
|
| 169 |
+ } |
|
| 170 |
+ |
|
| 171 |
+ if err := d.Cleanup(); err != nil {
|
|
| 172 |
+ t.Fatal(err) |
|
| 173 |
+ } |
|
| 174 |
+} |
|
| 175 |
+ |
|
| 176 |
+func TestMountedFalseResponse(t *testing.T) {
|
|
| 177 |
+ d := newDriver(t) |
|
| 178 |
+ defer os.RemoveAll(tmp) |
|
| 179 |
+ |
|
| 180 |
+ if err := d.Create("1", ""); err != nil {
|
|
| 181 |
+ t.Fatal(err) |
|
| 182 |
+ } |
|
| 183 |
+ |
|
| 184 |
+ response, err := d.mounted("1")
|
|
| 185 |
+ if err != nil {
|
|
| 186 |
+ t.Fatal(err) |
|
| 187 |
+ } |
|
| 188 |
+ |
|
| 189 |
+ if response != false {
|
|
| 190 |
+ t.Fatalf("Response if dir id 1 is mounted should be false")
|
|
| 191 |
+ } |
|
| 192 |
+} |
|
| 193 |
+ |
|
| 194 |
+func TestMountedTrueReponse(t *testing.T) {
|
|
| 195 |
+ d := newDriver(t) |
|
| 196 |
+ defer os.RemoveAll(tmp) |
|
| 197 |
+ defer d.Cleanup() |
|
| 198 |
+ |
|
| 199 |
+ if err := d.Create("1", ""); err != nil {
|
|
| 200 |
+ t.Fatal(err) |
|
| 201 |
+ } |
|
| 202 |
+ if err := d.Create("2", "1"); err != nil {
|
|
| 203 |
+ t.Fatal(err) |
|
| 204 |
+ } |
|
| 205 |
+ |
|
| 206 |
+ _, err := d.Get("2")
|
|
| 207 |
+ if err != nil {
|
|
| 208 |
+ t.Fatal(err) |
|
| 209 |
+ } |
|
| 210 |
+ |
|
| 211 |
+ response, err := d.mounted("2")
|
|
| 212 |
+ if err != nil {
|
|
| 213 |
+ t.Fatal(err) |
|
| 214 |
+ } |
|
| 215 |
+ |
|
| 216 |
+ if response != true {
|
|
| 217 |
+ t.Fatalf("Response if dir id 2 is mounted should be true")
|
|
| 218 |
+ } |
|
| 219 |
+} |
|
| 220 |
+ |
|
| 221 |
+func TestMountWithParent(t *testing.T) {
|
|
| 222 |
+ d := newDriver(t) |
|
| 223 |
+ defer os.RemoveAll(tmp) |
|
| 224 |
+ |
|
| 225 |
+ if err := d.Create("1", ""); err != nil {
|
|
| 226 |
+ t.Fatal(err) |
|
| 227 |
+ } |
|
| 228 |
+ if err := d.Create("2", "1"); err != nil {
|
|
| 229 |
+ t.Fatal(err) |
|
| 230 |
+ } |
|
| 231 |
+ |
|
| 232 |
+ defer func() {
|
|
| 233 |
+ if err := d.Cleanup(); err != nil {
|
|
| 234 |
+ t.Fatal(err) |
|
| 235 |
+ } |
|
| 236 |
+ }() |
|
| 237 |
+ |
|
| 238 |
+ mntPath, err := d.Get("2")
|
|
| 239 |
+ if err != nil {
|
|
| 240 |
+ t.Fatal(err) |
|
| 241 |
+ } |
|
| 242 |
+ if mntPath == "" {
|
|
| 243 |
+ t.Fatal("mntPath should not be empty string")
|
|
| 244 |
+ } |
|
| 245 |
+ |
|
| 246 |
+ expected := path.Join(tmp, "mnt", "2") |
|
| 247 |
+ if mntPath != expected {
|
|
| 248 |
+ t.Fatalf("Expected %s got %s", expected, mntPath)
|
|
| 249 |
+ } |
|
| 250 |
+} |
|
| 251 |
+ |
|
| 252 |
+func TestRemoveMountedDir(t *testing.T) {
|
|
| 253 |
+ d := newDriver(t) |
|
| 254 |
+ defer os.RemoveAll(tmp) |
|
| 255 |
+ |
|
| 256 |
+ if err := d.Create("1", ""); err != nil {
|
|
| 257 |
+ t.Fatal(err) |
|
| 258 |
+ } |
|
| 259 |
+ if err := d.Create("2", "1"); err != nil {
|
|
| 260 |
+ t.Fatal(err) |
|
| 261 |
+ } |
|
| 262 |
+ |
|
| 263 |
+ defer func() {
|
|
| 264 |
+ if err := d.Cleanup(); err != nil {
|
|
| 265 |
+ t.Fatal(err) |
|
| 266 |
+ } |
|
| 267 |
+ }() |
|
| 268 |
+ |
|
| 269 |
+ mntPath, err := d.Get("2")
|
|
| 270 |
+ if err != nil {
|
|
| 271 |
+ t.Fatal(err) |
|
| 272 |
+ } |
|
| 273 |
+ if mntPath == "" {
|
|
| 274 |
+ t.Fatal("mntPath should not be empty string")
|
|
| 275 |
+ } |
|
| 276 |
+ |
|
| 277 |
+ mounted, err := d.mounted("2")
|
|
| 278 |
+ if err != nil {
|
|
| 279 |
+ t.Fatal(err) |
|
| 280 |
+ } |
|
| 281 |
+ |
|
| 282 |
+ if !mounted {
|
|
| 283 |
+ t.Fatalf("Dir id 2 should be mounted")
|
|
| 284 |
+ } |
|
| 285 |
+ |
|
| 286 |
+ if err := d.Remove("2"); err != nil {
|
|
| 287 |
+ t.Fatal(err) |
|
| 288 |
+ } |
|
| 289 |
+} |
|
| 290 |
+ |
|
| 291 |
+func TestCreateWithInvalidParent(t *testing.T) {
|
|
| 292 |
+ d := newDriver(t) |
|
| 293 |
+ defer os.RemoveAll(tmp) |
|
| 294 |
+ |
|
| 295 |
+ if err := d.Create("1", "docker"); err == nil {
|
|
| 296 |
+ t.Fatalf("Error should not be nil with parent does not exist")
|
|
| 297 |
+ } |
|
| 298 |
+} |
|
| 299 |
+ |
|
| 300 |
+func TestGetDiff(t *testing.T) {
|
|
| 301 |
+ d := newDriver(t) |
|
| 302 |
+ defer os.RemoveAll(tmp) |
|
| 303 |
+ |
|
| 304 |
+ if err := d.Create("1", ""); err != nil {
|
|
| 305 |
+ t.Fatal(err) |
|
| 306 |
+ } |
|
| 307 |
+ |
|
| 308 |
+ diffPath, err := d.Get("1")
|
|
| 309 |
+ if err != nil {
|
|
| 310 |
+ t.Fatal(err) |
|
| 311 |
+ } |
|
| 312 |
+ |
|
| 313 |
+ // Add a file to the diff path with a fixed size |
|
| 314 |
+ size := int64(1024) |
|
| 315 |
+ |
|
| 316 |
+ f, err := os.Create(path.Join(diffPath, "test_file")) |
|
| 317 |
+ if err != nil {
|
|
| 318 |
+ t.Fatal(err) |
|
| 319 |
+ } |
|
| 320 |
+ if err := f.Truncate(size); err != nil {
|
|
| 321 |
+ t.Fatal(err) |
|
| 322 |
+ } |
|
| 323 |
+ f.Close() |
|
| 324 |
+ |
|
| 325 |
+ a, err := d.Diff("1")
|
|
| 326 |
+ if err != nil {
|
|
| 327 |
+ t.Fatal(err) |
|
| 328 |
+ } |
|
| 329 |
+ if a == nil {
|
|
| 330 |
+ t.Fatalf("Archive should not be nil")
|
|
| 331 |
+ } |
|
| 332 |
+} |
|
| 333 |
+ |
|
| 334 |
+func TestChanges(t *testing.T) {
|
|
| 335 |
+ d := newDriver(t) |
|
| 336 |
+ defer os.RemoveAll(tmp) |
|
| 337 |
+ |
|
| 338 |
+ if err := d.Create("1", ""); err != nil {
|
|
| 339 |
+ t.Fatal(err) |
|
| 340 |
+ } |
|
| 341 |
+ if err := d.Create("2", "1"); err != nil {
|
|
| 342 |
+ t.Fatal(err) |
|
| 343 |
+ } |
|
| 344 |
+ |
|
| 345 |
+ defer func() {
|
|
| 346 |
+ if err := d.Cleanup(); err != nil {
|
|
| 347 |
+ t.Fatal(err) |
|
| 348 |
+ } |
|
| 349 |
+ }() |
|
| 350 |
+ |
|
| 351 |
+ mntPoint, err := d.Get("2")
|
|
| 352 |
+ if err != nil {
|
|
| 353 |
+ t.Fatal(err) |
|
| 354 |
+ } |
|
| 355 |
+ |
|
| 356 |
+ // Create a file to save in the mountpoint |
|
| 357 |
+ f, err := os.Create(path.Join(mntPoint, "test.txt")) |
|
| 358 |
+ if err != nil {
|
|
| 359 |
+ t.Fatal(err) |
|
| 360 |
+ } |
|
| 361 |
+ |
|
| 362 |
+ if _, err := f.WriteString("testline"); err != nil {
|
|
| 363 |
+ t.Fatal(err) |
|
| 364 |
+ } |
|
| 365 |
+ if err := f.Close(); err != nil {
|
|
| 366 |
+ t.Fatal(err) |
|
| 367 |
+ } |
|
| 368 |
+ |
|
| 369 |
+ changes, err := d.Changes("2")
|
|
| 370 |
+ if err != nil {
|
|
| 371 |
+ t.Fatal(err) |
|
| 372 |
+ } |
|
| 373 |
+ if len(changes) != 1 {
|
|
| 374 |
+ t.Fatalf("Dir 2 should have one change from parent got %d", len(changes))
|
|
| 375 |
+ } |
|
| 376 |
+ change := changes[0] |
|
| 377 |
+ |
|
| 378 |
+ expectedPath := "/test.txt" |
|
| 379 |
+ if change.Path != expectedPath {
|
|
| 380 |
+ t.Fatalf("Expected path %s got %s", expectedPath, change.Path)
|
|
| 381 |
+ } |
|
| 382 |
+ |
|
| 383 |
+ if change.Kind != archive.ChangeAdd {
|
|
| 384 |
+ t.Fatalf("Change kind should be ChangeAdd got %s", change.Kind)
|
|
| 385 |
+ } |
|
| 386 |
+ |
|
| 387 |
+ if err := d.Create("3", "2"); err != nil {
|
|
| 388 |
+ t.Fatal(err) |
|
| 389 |
+ } |
|
| 390 |
+ mntPoint, err = d.Get("3")
|
|
| 391 |
+ if err != nil {
|
|
| 392 |
+ t.Fatal(err) |
|
| 393 |
+ } |
|
| 394 |
+ |
|
| 395 |
+ // Create a file to save in the mountpoint |
|
| 396 |
+ f, err = os.Create(path.Join(mntPoint, "test2.txt")) |
|
| 397 |
+ if err != nil {
|
|
| 398 |
+ t.Fatal(err) |
|
| 399 |
+ } |
|
| 400 |
+ |
|
| 401 |
+ if _, err := f.WriteString("testline"); err != nil {
|
|
| 402 |
+ t.Fatal(err) |
|
| 403 |
+ } |
|
| 404 |
+ if err := f.Close(); err != nil {
|
|
| 405 |
+ t.Fatal(err) |
|
| 406 |
+ } |
|
| 407 |
+ |
|
| 408 |
+ changes, err = d.Changes("3")
|
|
| 409 |
+ if err != nil {
|
|
| 410 |
+ t.Fatal(err) |
|
| 411 |
+ } |
|
| 412 |
+ |
|
| 413 |
+ if len(changes) != 1 {
|
|
| 414 |
+ t.Fatalf("Dir 2 should have one change from parent got %d", len(changes))
|
|
| 415 |
+ } |
|
| 416 |
+ change = changes[0] |
|
| 417 |
+ |
|
| 418 |
+ expectedPath = "/test2.txt" |
|
| 419 |
+ if change.Path != expectedPath {
|
|
| 420 |
+ t.Fatalf("Expected path %s got %s", expectedPath, change.Path)
|
|
| 421 |
+ } |
|
| 422 |
+ |
|
| 423 |
+ if change.Kind != archive.ChangeAdd {
|
|
| 424 |
+ t.Fatalf("Change kind should be ChangeAdd got %s", change.Kind)
|
|
| 425 |
+ } |
|
| 426 |
+} |
|
| 427 |
+ |
|
| 428 |
+func TestDiffSize(t *testing.T) {
|
|
| 429 |
+ d := newDriver(t) |
|
| 430 |
+ defer os.RemoveAll(tmp) |
|
| 431 |
+ |
|
| 432 |
+ if err := d.Create("1", ""); err != nil {
|
|
| 433 |
+ t.Fatal(err) |
|
| 434 |
+ } |
|
| 435 |
+ |
|
| 436 |
+ diffPath, err := d.Get("1")
|
|
| 437 |
+ if err != nil {
|
|
| 438 |
+ t.Fatal(err) |
|
| 439 |
+ } |
|
| 440 |
+ |
|
| 441 |
+ // Add a file to the diff path with a fixed size |
|
| 442 |
+ size := int64(1024) |
|
| 443 |
+ |
|
| 444 |
+ f, err := os.Create(path.Join(diffPath, "test_file")) |
|
| 445 |
+ if err != nil {
|
|
| 446 |
+ t.Fatal(err) |
|
| 447 |
+ } |
|
| 448 |
+ if err := f.Truncate(size); err != nil {
|
|
| 449 |
+ t.Fatal(err) |
|
| 450 |
+ } |
|
| 451 |
+ s, err := f.Stat() |
|
| 452 |
+ if err != nil {
|
|
| 453 |
+ t.Fatal(err) |
|
| 454 |
+ } |
|
| 455 |
+ size = s.Size() |
|
| 456 |
+ if err := f.Close(); err != nil {
|
|
| 457 |
+ t.Fatal(err) |
|
| 458 |
+ } |
|
| 459 |
+ |
|
| 460 |
+ diffSize, err := d.DiffSize("1")
|
|
| 461 |
+ if err != nil {
|
|
| 462 |
+ t.Fatal(err) |
|
| 463 |
+ } |
|
| 464 |
+ if diffSize != size {
|
|
| 465 |
+ t.Fatalf("Expected size to be %d got %d", size, diffSize)
|
|
| 466 |
+ } |
|
| 467 |
+} |
|
| 468 |
+ |
|
| 469 |
+func TestChildDiffSize(t *testing.T) {
|
|
| 470 |
+ d := newDriver(t) |
|
| 471 |
+ defer os.RemoveAll(tmp) |
|
| 472 |
+ defer d.Cleanup() |
|
| 473 |
+ |
|
| 474 |
+ if err := d.Create("1", ""); err != nil {
|
|
| 475 |
+ t.Fatal(err) |
|
| 476 |
+ } |
|
| 477 |
+ |
|
| 478 |
+ diffPath, err := d.Get("1")
|
|
| 479 |
+ if err != nil {
|
|
| 480 |
+ t.Fatal(err) |
|
| 481 |
+ } |
|
| 482 |
+ |
|
| 483 |
+ // Add a file to the diff path with a fixed size |
|
| 484 |
+ size := int64(1024) |
|
| 485 |
+ |
|
| 486 |
+ f, err := os.Create(path.Join(diffPath, "test_file")) |
|
| 487 |
+ if err != nil {
|
|
| 488 |
+ t.Fatal(err) |
|
| 489 |
+ } |
|
| 490 |
+ if err := f.Truncate(size); err != nil {
|
|
| 491 |
+ t.Fatal(err) |
|
| 492 |
+ } |
|
| 493 |
+ s, err := f.Stat() |
|
| 494 |
+ if err != nil {
|
|
| 495 |
+ t.Fatal(err) |
|
| 496 |
+ } |
|
| 497 |
+ size = s.Size() |
|
| 498 |
+ if err := f.Close(); err != nil {
|
|
| 499 |
+ t.Fatal(err) |
|
| 500 |
+ } |
|
| 501 |
+ |
|
| 502 |
+ diffSize, err := d.DiffSize("1")
|
|
| 503 |
+ if err != nil {
|
|
| 504 |
+ t.Fatal(err) |
|
| 505 |
+ } |
|
| 506 |
+ if diffSize != size {
|
|
| 507 |
+ t.Fatalf("Expected size to be %d got %d", size, diffSize)
|
|
| 508 |
+ } |
|
| 509 |
+ |
|
| 510 |
+ if err := d.Create("2", "1"); err != nil {
|
|
| 511 |
+ t.Fatal(err) |
|
| 512 |
+ } |
|
| 513 |
+ |
|
| 514 |
+ diffSize, err = d.DiffSize("2")
|
|
| 515 |
+ if err != nil {
|
|
| 516 |
+ t.Fatal(err) |
|
| 517 |
+ } |
|
| 518 |
+ // The diff size for the child should be zero |
|
| 519 |
+ if diffSize != 0 {
|
|
| 520 |
+ t.Fatalf("Expected size to be %d got %d", 0, diffSize)
|
|
| 521 |
+ } |
|
| 522 |
+} |
|
| 523 |
+ |
|
| 524 |
+func TestExists(t *testing.T) {
|
|
| 525 |
+ d := newDriver(t) |
|
| 526 |
+ defer os.RemoveAll(tmp) |
|
| 527 |
+ defer d.Cleanup() |
|
| 528 |
+ |
|
| 529 |
+ if err := d.Create("1", ""); err != nil {
|
|
| 530 |
+ t.Fatal(err) |
|
| 531 |
+ } |
|
| 532 |
+ |
|
| 533 |
+ if d.Exists("none") {
|
|
| 534 |
+ t.Fatal("id name should not exist in the driver")
|
|
| 535 |
+ } |
|
| 536 |
+ |
|
| 537 |
+ if !d.Exists("1") {
|
|
| 538 |
+ t.Fatal("id 1 should exist in the driver")
|
|
| 539 |
+ } |
|
| 540 |
+} |
|
| 541 |
+ |
|
| 542 |
+func TestStatus(t *testing.T) {
|
|
| 543 |
+ d := newDriver(t) |
|
| 544 |
+ defer os.RemoveAll(tmp) |
|
| 545 |
+ defer d.Cleanup() |
|
| 546 |
+ |
|
| 547 |
+ if err := d.Create("1", ""); err != nil {
|
|
| 548 |
+ t.Fatal(err) |
|
| 549 |
+ } |
|
| 550 |
+ |
|
| 551 |
+ status := d.Status() |
|
| 552 |
+ if status == nil || len(status) == 0 {
|
|
| 553 |
+ t.Fatal("Status should not be nil or empty")
|
|
| 554 |
+ } |
|
| 555 |
+ rootDir := status[0] |
|
| 556 |
+ dirs := status[1] |
|
| 557 |
+ if rootDir[0] != "Root Dir" {
|
|
| 558 |
+ t.Fatalf("Expected Root Dir got %s", rootDir[0])
|
|
| 559 |
+ } |
|
| 560 |
+ if rootDir[1] != d.rootPath() {
|
|
| 561 |
+ t.Fatalf("Expected %s got %s", d.rootPath(), rootDir[1])
|
|
| 562 |
+ } |
|
| 563 |
+ if dirs[0] != "Dirs" {
|
|
| 564 |
+ t.Fatalf("Expected Dirs got %s", dirs[0])
|
|
| 565 |
+ } |
|
| 566 |
+ if dirs[1] != "1" {
|
|
| 567 |
+ t.Fatalf("Expected 1 got %s", dirs[1])
|
|
| 568 |
+ } |
|
| 569 |
+} |
|
| 570 |
+ |
|
| 571 |
+func TestApplyDiff(t *testing.T) {
|
|
| 572 |
+ d := newDriver(t) |
|
| 573 |
+ defer os.RemoveAll(tmp) |
|
| 574 |
+ defer d.Cleanup() |
|
| 575 |
+ |
|
| 576 |
+ if err := d.Create("1", ""); err != nil {
|
|
| 577 |
+ t.Fatal(err) |
|
| 578 |
+ } |
|
| 579 |
+ |
|
| 580 |
+ diffPath, err := d.Get("1")
|
|
| 581 |
+ if err != nil {
|
|
| 582 |
+ t.Fatal(err) |
|
| 583 |
+ } |
|
| 584 |
+ |
|
| 585 |
+ // Add a file to the diff path with a fixed size |
|
| 586 |
+ size := int64(1024) |
|
| 587 |
+ |
|
| 588 |
+ f, err := os.Create(path.Join(diffPath, "test_file")) |
|
| 589 |
+ if err != nil {
|
|
| 590 |
+ t.Fatal(err) |
|
| 591 |
+ } |
|
| 592 |
+ if err := f.Truncate(size); err != nil {
|
|
| 593 |
+ t.Fatal(err) |
|
| 594 |
+ } |
|
| 595 |
+ f.Close() |
|
| 596 |
+ |
|
| 597 |
+ diff, err := d.Diff("1")
|
|
| 598 |
+ if err != nil {
|
|
| 599 |
+ t.Fatal(err) |
|
| 600 |
+ } |
|
| 601 |
+ |
|
| 602 |
+ if err := d.Create("2", ""); err != nil {
|
|
| 603 |
+ t.Fatal(err) |
|
| 604 |
+ } |
|
| 605 |
+ if err := d.Create("3", "2"); err != nil {
|
|
| 606 |
+ t.Fatal(err) |
|
| 607 |
+ } |
|
| 608 |
+ |
|
| 609 |
+ if err := d.ApplyDiff("3", diff); err != nil {
|
|
| 610 |
+ t.Fatal(err) |
|
| 611 |
+ } |
|
| 612 |
+ |
|
| 613 |
+ // Ensure that the file is in the mount point for id 3 |
|
| 614 |
+ |
|
| 615 |
+ mountPoint, err := d.Get("3")
|
|
| 616 |
+ if err != nil {
|
|
| 617 |
+ t.Fatal(err) |
|
| 618 |
+ } |
|
| 619 |
+ if _, err := os.Stat(path.Join(mountPoint, "test_file")); err != nil {
|
|
| 620 |
+ t.Fatal(err) |
|
| 621 |
+ } |
|
| 622 |
+} |
| 0 | 623 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,46 @@ |
| 0 |
+package aufs |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bufio" |
|
| 4 |
+ "io/ioutil" |
|
| 5 |
+ "os" |
|
| 6 |
+ "path" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+// Return all the directories |
|
| 10 |
+func loadIds(root string) ([]string, error) {
|
|
| 11 |
+ dirs, err := ioutil.ReadDir(root) |
|
| 12 |
+ if err != nil {
|
|
| 13 |
+ return nil, err |
|
| 14 |
+ } |
|
| 15 |
+ out := []string{}
|
|
| 16 |
+ for _, d := range dirs {
|
|
| 17 |
+ if !d.IsDir() {
|
|
| 18 |
+ out = append(out, d.Name()) |
|
| 19 |
+ } |
|
| 20 |
+ } |
|
| 21 |
+ return out, nil |
|
| 22 |
+} |
|
| 23 |
+ |
|
| 24 |
+// Read the layers file for the current id and return all the |
|
| 25 |
+// layers represented by new lines in the file |
|
| 26 |
+// |
|
| 27 |
+// If there are no lines in the file then the id has no parent |
|
| 28 |
+// and an empty slice is returned. |
|
| 29 |
+func getParentIds(root, id string) ([]string, error) {
|
|
| 30 |
+ f, err := os.Open(path.Join(root, "layers", id)) |
|
| 31 |
+ if err != nil {
|
|
| 32 |
+ return nil, err |
|
| 33 |
+ } |
|
| 34 |
+ defer f.Close() |
|
| 35 |
+ |
|
| 36 |
+ out := []string{}
|
|
| 37 |
+ s := bufio.NewScanner(f) |
|
| 38 |
+ |
|
| 39 |
+ for s.Scan() {
|
|
| 40 |
+ if t := s.Text(); t != "" {
|
|
| 41 |
+ out = append(out, s.Text()) |
|
| 42 |
+ } |
|
| 43 |
+ } |
|
| 44 |
+ return out, s.Err() |
|
| 45 |
+} |
| 0 | 46 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,194 @@ |
| 0 |
+package aufs |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "encoding/json" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "io/ioutil" |
|
| 6 |
+ "os" |
|
| 7 |
+ "path" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+type metadata struct {
|
|
| 11 |
+ ID string `json:"id"` |
|
| 12 |
+ ParentID string `json:"parent,omitempty"` |
|
| 13 |
+ Image string `json:"Image,omitempty"` |
|
| 14 |
+ |
|
| 15 |
+ parent *metadata |
|
| 16 |
+} |
|
| 17 |
+ |
|
| 18 |
+func pathExists(pth string) bool {
|
|
| 19 |
+ if _, err := os.Stat(pth); err != nil {
|
|
| 20 |
+ return false |
|
| 21 |
+ } |
|
| 22 |
+ return true |
|
| 23 |
+} |
|
| 24 |
+ |
|
| 25 |
+// Migrate existing images and containers from docker < 0.7.x |
|
| 26 |
+// |
|
| 27 |
+// The format pre 0.7 is for docker to store the metadata and filesystem |
|
| 28 |
+// content in the same directory. For the migration to work we need to move Image layer |
|
| 29 |
+// data from /var/lib/docker/graph/<id>/layers to the diff of the registered id. |
|
| 30 |
+// |
|
| 31 |
+// Next we need to migrate the container's rw layer to diff of the driver. After the |
|
| 32 |
+// contents are migrated we need to register the image and container ids with the |
|
| 33 |
+// driver. |
|
| 34 |
+// |
|
| 35 |
+// For the migration we try to move the folder containing the layer files, if that |
|
| 36 |
+// fails because the data is currently mounted we will fallback to creating a |
|
| 37 |
+// symlink. |
|
| 38 |
+func (a *Driver) Migrate(pth string, setupInit func(p string) error) error {
|
|
| 39 |
+ if pathExists(path.Join(pth, "graph")) {
|
|
| 40 |
+ if err := a.migrateRepositories(pth); err != nil {
|
|
| 41 |
+ return err |
|
| 42 |
+ } |
|
| 43 |
+ if err := a.migrateImages(path.Join(pth, "graph")); err != nil {
|
|
| 44 |
+ return err |
|
| 45 |
+ } |
|
| 46 |
+ return a.migrateContainers(path.Join(pth, "containers"), setupInit) |
|
| 47 |
+ } |
|
| 48 |
+ return nil |
|
| 49 |
+} |
|
| 50 |
+ |
|
| 51 |
+func (a *Driver) migrateRepositories(pth string) error {
|
|
| 52 |
+ name := path.Join(pth, "repositories") |
|
| 53 |
+ if err := os.Rename(name, name+"-aufs"); err != nil && !os.IsNotExist(err) {
|
|
| 54 |
+ return err |
|
| 55 |
+ } |
|
| 56 |
+ return nil |
|
| 57 |
+} |
|
| 58 |
+ |
|
| 59 |
+func (a *Driver) migrateContainers(pth string, setupInit func(p string) error) error {
|
|
| 60 |
+ fis, err := ioutil.ReadDir(pth) |
|
| 61 |
+ if err != nil {
|
|
| 62 |
+ return err |
|
| 63 |
+ } |
|
| 64 |
+ |
|
| 65 |
+ for _, fi := range fis {
|
|
| 66 |
+ if id := fi.Name(); fi.IsDir() && pathExists(path.Join(pth, id, "rw")) {
|
|
| 67 |
+ if err := tryRelocate(path.Join(pth, id, "rw"), path.Join(a.rootPath(), "diff", id)); err != nil {
|
|
| 68 |
+ return err |
|
| 69 |
+ } |
|
| 70 |
+ |
|
| 71 |
+ if !a.Exists(id) {
|
|
| 72 |
+ |
|
| 73 |
+ metadata, err := loadMetadata(path.Join(pth, id, "config.json")) |
|
| 74 |
+ if err != nil {
|
|
| 75 |
+ return err |
|
| 76 |
+ } |
|
| 77 |
+ |
|
| 78 |
+ initID := fmt.Sprintf("%s-init", id)
|
|
| 79 |
+ if err := a.Create(initID, metadata.Image); err != nil {
|
|
| 80 |
+ return err |
|
| 81 |
+ } |
|
| 82 |
+ |
|
| 83 |
+ initPath, err := a.Get(initID) |
|
| 84 |
+ if err != nil {
|
|
| 85 |
+ return err |
|
| 86 |
+ } |
|
| 87 |
+ // setup init layer |
|
| 88 |
+ if err := setupInit(initPath); err != nil {
|
|
| 89 |
+ return err |
|
| 90 |
+ } |
|
| 91 |
+ |
|
| 92 |
+ if err := a.Create(id, initID); err != nil {
|
|
| 93 |
+ return err |
|
| 94 |
+ } |
|
| 95 |
+ } |
|
| 96 |
+ } |
|
| 97 |
+ } |
|
| 98 |
+ return nil |
|
| 99 |
+} |
|
| 100 |
+ |
|
| 101 |
+func (a *Driver) migrateImages(pth string) error {
|
|
| 102 |
+ fis, err := ioutil.ReadDir(pth) |
|
| 103 |
+ if err != nil {
|
|
| 104 |
+ return err |
|
| 105 |
+ } |
|
| 106 |
+ var ( |
|
| 107 |
+ m = make(map[string]*metadata) |
|
| 108 |
+ current *metadata |
|
| 109 |
+ exists bool |
|
| 110 |
+ ) |
|
| 111 |
+ |
|
| 112 |
+ for _, fi := range fis {
|
|
| 113 |
+ if id := fi.Name(); fi.IsDir() && pathExists(path.Join(pth, id, "layer")) {
|
|
| 114 |
+ if current, exists = m[id]; !exists {
|
|
| 115 |
+ current, err = loadMetadata(path.Join(pth, id, "json")) |
|
| 116 |
+ if err != nil {
|
|
| 117 |
+ return err |
|
| 118 |
+ } |
|
| 119 |
+ m[id] = current |
|
| 120 |
+ } |
|
| 121 |
+ } |
|
| 122 |
+ } |
|
| 123 |
+ |
|
| 124 |
+ for _, v := range m {
|
|
| 125 |
+ v.parent = m[v.ParentID] |
|
| 126 |
+ } |
|
| 127 |
+ |
|
| 128 |
+ migrated := make(map[string]bool) |
|
| 129 |
+ for _, v := range m {
|
|
| 130 |
+ if err := a.migrateImage(v, pth, migrated); err != nil {
|
|
| 131 |
+ return err |
|
| 132 |
+ } |
|
| 133 |
+ } |
|
| 134 |
+ return nil |
|
| 135 |
+} |
|
| 136 |
+ |
|
| 137 |
+func (a *Driver) migrateImage(m *metadata, pth string, migrated map[string]bool) error {
|
|
| 138 |
+ if !migrated[m.ID] {
|
|
| 139 |
+ if m.parent != nil {
|
|
| 140 |
+ a.migrateImage(m.parent, pth, migrated) |
|
| 141 |
+ } |
|
| 142 |
+ if err := tryRelocate(path.Join(pth, m.ID, "layer"), path.Join(a.rootPath(), "diff", m.ID)); err != nil {
|
|
| 143 |
+ return err |
|
| 144 |
+ } |
|
| 145 |
+ if !a.Exists(m.ID) {
|
|
| 146 |
+ if err := a.Create(m.ID, m.ParentID); err != nil {
|
|
| 147 |
+ return err |
|
| 148 |
+ } |
|
| 149 |
+ } |
|
| 150 |
+ migrated[m.ID] = true |
|
| 151 |
+ } |
|
| 152 |
+ return nil |
|
| 153 |
+} |
|
| 154 |
+ |
|
| 155 |
+// tryRelocate will try to rename the old path to the new pack and if |
|
| 156 |
+// the operation fails, it will fallback to a symlink |
|
| 157 |
+func tryRelocate(oldPath, newPath string) error {
|
|
| 158 |
+ s, err := os.Lstat(newPath) |
|
| 159 |
+ if err != nil && !os.IsNotExist(err) {
|
|
| 160 |
+ return err |
|
| 161 |
+ } |
|
| 162 |
+ // If the destination is a symlink then we already tried to relocate once before |
|
| 163 |
+ // and it failed so we delete it and try to remove |
|
| 164 |
+ if s != nil && s.Mode()&os.ModeSymlink == os.ModeSymlink {
|
|
| 165 |
+ if err := os.RemoveAll(newPath); err != nil {
|
|
| 166 |
+ return err |
|
| 167 |
+ } |
|
| 168 |
+ } |
|
| 169 |
+ if err := os.Rename(oldPath, newPath); err != nil {
|
|
| 170 |
+ if sErr := os.Symlink(oldPath, newPath); sErr != nil {
|
|
| 171 |
+ return fmt.Errorf("Unable to relocate %s to %s: Rename err %s Symlink err %s", oldPath, newPath, err, sErr)
|
|
| 172 |
+ } |
|
| 173 |
+ } |
|
| 174 |
+ return nil |
|
| 175 |
+} |
|
| 176 |
+ |
|
| 177 |
+func loadMetadata(pth string) (*metadata, error) {
|
|
| 178 |
+ f, err := os.Open(pth) |
|
| 179 |
+ if err != nil {
|
|
| 180 |
+ return nil, err |
|
| 181 |
+ } |
|
| 182 |
+ defer f.Close() |
|
| 183 |
+ |
|
| 184 |
+ var ( |
|
| 185 |
+ out = &metadata{}
|
|
| 186 |
+ dec = json.NewDecoder(f) |
|
| 187 |
+ ) |
|
| 188 |
+ |
|
| 189 |
+ if err := dec.Decode(out); err != nil {
|
|
| 190 |
+ return nil, err |
|
| 191 |
+ } |
|
| 192 |
+ return out, nil |
|
| 193 |
+} |
| 0 | 194 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,37 @@ |
| 0 |
+package aufs |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "github.com/dotcloud/docker/utils" |
|
| 4 |
+ "os" |
|
| 5 |
+ "os/exec" |
|
| 6 |
+ "path/filepath" |
|
| 7 |
+ "syscall" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+func Unmount(target string) error {
|
|
| 11 |
+ if err := exec.Command("auplink", target, "flush").Run(); err != nil {
|
|
| 12 |
+ utils.Errorf("[warning]: couldn't run auplink before unmount: %s", err)
|
|
| 13 |
+ } |
|
| 14 |
+ if err := syscall.Unmount(target, 0); err != nil {
|
|
| 15 |
+ return err |
|
| 16 |
+ } |
|
| 17 |
+ return nil |
|
| 18 |
+} |
|
| 19 |
+ |
|
| 20 |
+func Mounted(mountpoint string) (bool, error) {
|
|
| 21 |
+ mntpoint, err := os.Stat(mountpoint) |
|
| 22 |
+ if err != nil {
|
|
| 23 |
+ if os.IsNotExist(err) {
|
|
| 24 |
+ return false, nil |
|
| 25 |
+ } |
|
| 26 |
+ return false, err |
|
| 27 |
+ } |
|
| 28 |
+ parent, err := os.Stat(filepath.Join(mountpoint, "..")) |
|
| 29 |
+ if err != nil {
|
|
| 30 |
+ return false, err |
|
| 31 |
+ } |
|
| 32 |
+ mntpointSt := mntpoint.Sys().(*syscall.Stat_t) |
|
| 33 |
+ parentSt := parent.Sys().(*syscall.Stat_t) |
|
| 34 |
+ |
|
| 35 |
+ return mntpointSt.Dev != parentSt.Dev, nil |
|
| 36 |
+} |
| 0 | 7 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,956 @@ |
| 0 |
+package devmapper |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "encoding/json" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "github.com/dotcloud/docker/utils" |
|
| 6 |
+ "io" |
|
| 7 |
+ "io/ioutil" |
|
| 8 |
+ "path" |
|
| 9 |
+ "path/filepath" |
|
| 10 |
+ "strconv" |
|
| 11 |
+ "sync" |
|
| 12 |
+ "time" |
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+var ( |
|
| 16 |
+ DefaultDataLoopbackSize int64 = 100 * 1024 * 1024 * 1024 |
|
| 17 |
+ DefaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024 |
|
| 18 |
+ DefaultBaseFsSize uint64 = 10 * 1024 * 1024 * 1024 |
|
| 19 |
+) |
|
| 20 |
+ |
|
| 21 |
+type DevInfo struct {
|
|
| 22 |
+ Hash string `json:"-"` |
|
| 23 |
+ DeviceId int `json:"device_id"` |
|
| 24 |
+ Size uint64 `json:"size"` |
|
| 25 |
+ TransactionId uint64 `json:"transaction_id"` |
|
| 26 |
+ Initialized bool `json:"initialized"` |
|
| 27 |
+ devices *DeviceSet `json:"-"` |
|
| 28 |
+} |
|
| 29 |
+ |
|
| 30 |
+type MetaData struct {
|
|
| 31 |
+ Devices map[string]*DevInfo `json:devices` |
|
| 32 |
+} |
|
| 33 |
+ |
|
| 34 |
+type DeviceSet struct {
|
|
| 35 |
+ MetaData |
|
| 36 |
+ sync.Mutex |
|
| 37 |
+ root string |
|
| 38 |
+ devicePrefix string |
|
| 39 |
+ TransactionId uint64 |
|
| 40 |
+ NewTransactionId uint64 |
|
| 41 |
+ nextFreeDevice int |
|
| 42 |
+ activeMounts map[string]int |
|
| 43 |
+} |
|
| 44 |
+ |
|
| 45 |
+type DiskUsage struct {
|
|
| 46 |
+ Used uint64 |
|
| 47 |
+ Total uint64 |
|
| 48 |
+} |
|
| 49 |
+ |
|
| 50 |
+type Status struct {
|
|
| 51 |
+ PoolName string |
|
| 52 |
+ DataLoopback string |
|
| 53 |
+ MetadataLoopback string |
|
| 54 |
+ Data DiskUsage |
|
| 55 |
+ Metadata DiskUsage |
|
| 56 |
+ SectorSize uint64 |
|
| 57 |
+} |
|
| 58 |
+ |
|
| 59 |
+type DevStatus struct {
|
|
| 60 |
+ DeviceId int |
|
| 61 |
+ Size uint64 |
|
| 62 |
+ TransactionId uint64 |
|
| 63 |
+ SizeInSectors uint64 |
|
| 64 |
+ MappedSectors uint64 |
|
| 65 |
+ HighestMappedSector uint64 |
|
| 66 |
+} |
|
| 67 |
+ |
|
| 68 |
+func getDevName(name string) string {
|
|
| 69 |
+ return "/dev/mapper/" + name |
|
| 70 |
+} |
|
| 71 |
+ |
|
| 72 |
+func (info *DevInfo) Name() string {
|
|
| 73 |
+ hash := info.Hash |
|
| 74 |
+ if hash == "" {
|
|
| 75 |
+ hash = "base" |
|
| 76 |
+ } |
|
| 77 |
+ return fmt.Sprintf("%s-%s", info.devices.devicePrefix, hash)
|
|
| 78 |
+} |
|
| 79 |
+ |
|
| 80 |
+func (info *DevInfo) DevName() string {
|
|
| 81 |
+ return getDevName(info.Name()) |
|
| 82 |
+} |
|
| 83 |
+ |
|
| 84 |
+func (devices *DeviceSet) loopbackDir() string {
|
|
| 85 |
+ return path.Join(devices.root, "devicemapper") |
|
| 86 |
+} |
|
| 87 |
+ |
|
| 88 |
+func (devices *DeviceSet) jsonFile() string {
|
|
| 89 |
+ return path.Join(devices.loopbackDir(), "json") |
|
| 90 |
+} |
|
| 91 |
+ |
|
| 92 |
+func (devices *DeviceSet) getPoolName() string {
|
|
| 93 |
+ return devices.devicePrefix + "-pool" |
|
| 94 |
+} |
|
| 95 |
+ |
|
| 96 |
+func (devices *DeviceSet) getPoolDevName() string {
|
|
| 97 |
+ return getDevName(devices.getPoolName()) |
|
| 98 |
+} |
|
| 99 |
+ |
|
| 100 |
+func (devices *DeviceSet) hasImage(name string) bool {
|
|
| 101 |
+ dirname := devices.loopbackDir() |
|
| 102 |
+ filename := path.Join(dirname, name) |
|
| 103 |
+ |
|
| 104 |
+ _, err := osStat(filename) |
|
| 105 |
+ return err == nil |
|
| 106 |
+} |
|
| 107 |
+ |
|
| 108 |
+// ensureImage creates a sparse file of <size> bytes at the path |
|
| 109 |
+// <root>/devicemapper/<name>. |
|
| 110 |
+// If the file already exists, it does nothing. |
|
| 111 |
+// Either way it returns the full path. |
|
| 112 |
+func (devices *DeviceSet) ensureImage(name string, size int64) (string, error) {
|
|
| 113 |
+ dirname := devices.loopbackDir() |
|
| 114 |
+ filename := path.Join(dirname, name) |
|
| 115 |
+ |
|
| 116 |
+ if err := osMkdirAll(dirname, 0700); err != nil && !osIsExist(err) {
|
|
| 117 |
+ return "", err |
|
| 118 |
+ } |
|
| 119 |
+ |
|
| 120 |
+ if _, err := osStat(filename); err != nil {
|
|
| 121 |
+ if !osIsNotExist(err) {
|
|
| 122 |
+ return "", err |
|
| 123 |
+ } |
|
| 124 |
+ utils.Debugf("Creating loopback file %s for device-manage use", filename)
|
|
| 125 |
+ file, err := osOpenFile(filename, osORdWr|osOCreate, 0600) |
|
| 126 |
+ if err != nil {
|
|
| 127 |
+ return "", err |
|
| 128 |
+ } |
|
| 129 |
+ defer file.Close() |
|
| 130 |
+ |
|
| 131 |
+ if err = file.Truncate(size); err != nil {
|
|
| 132 |
+ return "", err |
|
| 133 |
+ } |
|
| 134 |
+ } |
|
| 135 |
+ return filename, nil |
|
| 136 |
+} |
|
| 137 |
+ |
|
| 138 |
+func (devices *DeviceSet) allocateDeviceId() int {
|
|
| 139 |
+ // TODO: Add smarter reuse of deleted devices |
|
| 140 |
+ id := devices.nextFreeDevice |
|
| 141 |
+ devices.nextFreeDevice = devices.nextFreeDevice + 1 |
|
| 142 |
+ return id |
|
| 143 |
+} |
|
| 144 |
+ |
|
| 145 |
+func (devices *DeviceSet) allocateTransactionId() uint64 {
|
|
| 146 |
+ devices.NewTransactionId = devices.NewTransactionId + 1 |
|
| 147 |
+ return devices.NewTransactionId |
|
| 148 |
+} |
|
| 149 |
+ |
|
| 150 |
+func (devices *DeviceSet) saveMetadata() error {
|
|
| 151 |
+ jsonData, err := json.Marshal(devices.MetaData) |
|
| 152 |
+ if err != nil {
|
|
| 153 |
+ return fmt.Errorf("Error encoding metaadata to json: %s", err)
|
|
| 154 |
+ } |
|
| 155 |
+ tmpFile, err := ioutil.TempFile(filepath.Dir(devices.jsonFile()), ".json") |
|
| 156 |
+ if err != nil {
|
|
| 157 |
+ return fmt.Errorf("Error creating metadata file: %s", err)
|
|
| 158 |
+ } |
|
| 159 |
+ |
|
| 160 |
+ n, err := tmpFile.Write(jsonData) |
|
| 161 |
+ if err != nil {
|
|
| 162 |
+ return fmt.Errorf("Error writing metadata to %s: %s", tmpFile.Name(), err)
|
|
| 163 |
+ } |
|
| 164 |
+ if n < len(jsonData) {
|
|
| 165 |
+ return io.ErrShortWrite |
|
| 166 |
+ } |
|
| 167 |
+ if err := tmpFile.Sync(); err != nil {
|
|
| 168 |
+ return fmt.Errorf("Error syncing metadata file %s: %s", tmpFile.Name(), err)
|
|
| 169 |
+ } |
|
| 170 |
+ if err := tmpFile.Close(); err != nil {
|
|
| 171 |
+ return fmt.Errorf("Error closing metadata file %s: %s", tmpFile.Name(), err)
|
|
| 172 |
+ } |
|
| 173 |
+ if err := osRename(tmpFile.Name(), devices.jsonFile()); err != nil {
|
|
| 174 |
+ return fmt.Errorf("Error committing metadata file", err)
|
|
| 175 |
+ } |
|
| 176 |
+ |
|
| 177 |
+ if devices.NewTransactionId != devices.TransactionId {
|
|
| 178 |
+ if err = setTransactionId(devices.getPoolDevName(), devices.TransactionId, devices.NewTransactionId); err != nil {
|
|
| 179 |
+ return fmt.Errorf("Error setting devmapper transition ID: %s", err)
|
|
| 180 |
+ } |
|
| 181 |
+ devices.TransactionId = devices.NewTransactionId |
|
| 182 |
+ } |
|
| 183 |
+ return nil |
|
| 184 |
+} |
|
| 185 |
+ |
|
| 186 |
+func (devices *DeviceSet) registerDevice(id int, hash string, size uint64) (*DevInfo, error) {
|
|
| 187 |
+ utils.Debugf("registerDevice(%v, %v)", id, hash)
|
|
| 188 |
+ info := &DevInfo{
|
|
| 189 |
+ Hash: hash, |
|
| 190 |
+ DeviceId: id, |
|
| 191 |
+ Size: size, |
|
| 192 |
+ TransactionId: devices.allocateTransactionId(), |
|
| 193 |
+ Initialized: false, |
|
| 194 |
+ devices: devices, |
|
| 195 |
+ } |
|
| 196 |
+ |
|
| 197 |
+ devices.Devices[hash] = info |
|
| 198 |
+ if err := devices.saveMetadata(); err != nil {
|
|
| 199 |
+ // Try to remove unused device |
|
| 200 |
+ delete(devices.Devices, hash) |
|
| 201 |
+ return nil, err |
|
| 202 |
+ } |
|
| 203 |
+ |
|
| 204 |
+ return info, nil |
|
| 205 |
+} |
|
| 206 |
+ |
|
| 207 |
+func (devices *DeviceSet) activateDeviceIfNeeded(hash string) error {
|
|
| 208 |
+ utils.Debugf("activateDeviceIfNeeded(%v)", hash)
|
|
| 209 |
+ info := devices.Devices[hash] |
|
| 210 |
+ if info == nil {
|
|
| 211 |
+ return fmt.Errorf("Unknown device %s", hash)
|
|
| 212 |
+ } |
|
| 213 |
+ |
|
| 214 |
+ if devinfo, _ := getInfo(info.Name()); devinfo != nil && devinfo.Exists != 0 {
|
|
| 215 |
+ return nil |
|
| 216 |
+ } |
|
| 217 |
+ |
|
| 218 |
+ return activateDevice(devices.getPoolDevName(), info.Name(), info.DeviceId, info.Size) |
|
| 219 |
+} |
|
| 220 |
+ |
|
| 221 |
+func (devices *DeviceSet) createFilesystem(info *DevInfo) error {
|
|
| 222 |
+ devname := info.DevName() |
|
| 223 |
+ |
|
| 224 |
+ err := execRun("mkfs.ext4", "-E", "discard,lazy_itable_init=0,lazy_journal_init=0", devname)
|
|
| 225 |
+ if err != nil {
|
|
| 226 |
+ err = execRun("mkfs.ext4", "-E", "discard,lazy_itable_init=0", devname)
|
|
| 227 |
+ } |
|
| 228 |
+ if err != nil {
|
|
| 229 |
+ utils.Debugf("\n--->Err: %s\n", err)
|
|
| 230 |
+ return err |
|
| 231 |
+ } |
|
| 232 |
+ return nil |
|
| 233 |
+} |
|
| 234 |
+ |
|
| 235 |
+func (devices *DeviceSet) loadMetaData() error {
|
|
| 236 |
+ utils.Debugf("loadMetadata()")
|
|
| 237 |
+ defer utils.Debugf("loadMetadata END")
|
|
| 238 |
+ _, _, _, params, err := getStatus(devices.getPoolName()) |
|
| 239 |
+ if err != nil {
|
|
| 240 |
+ utils.Debugf("\n--->Err: %s\n", err)
|
|
| 241 |
+ return err |
|
| 242 |
+ } |
|
| 243 |
+ |
|
| 244 |
+ if _, err := fmt.Sscanf(params, "%d", &devices.TransactionId); err != nil {
|
|
| 245 |
+ utils.Debugf("\n--->Err: %s\n", err)
|
|
| 246 |
+ return err |
|
| 247 |
+ } |
|
| 248 |
+ devices.NewTransactionId = devices.TransactionId |
|
| 249 |
+ |
|
| 250 |
+ jsonData, err := ioutil.ReadFile(devices.jsonFile()) |
|
| 251 |
+ if err != nil && !osIsNotExist(err) {
|
|
| 252 |
+ utils.Debugf("\n--->Err: %s\n", err)
|
|
| 253 |
+ return err |
|
| 254 |
+ } |
|
| 255 |
+ |
|
| 256 |
+ devices.MetaData.Devices = make(map[string]*DevInfo) |
|
| 257 |
+ if jsonData != nil {
|
|
| 258 |
+ if err := json.Unmarshal(jsonData, &devices.MetaData); err != nil {
|
|
| 259 |
+ utils.Debugf("\n--->Err: %s\n", err)
|
|
| 260 |
+ return err |
|
| 261 |
+ } |
|
| 262 |
+ } |
|
| 263 |
+ |
|
| 264 |
+ for hash, d := range devices.Devices {
|
|
| 265 |
+ d.Hash = hash |
|
| 266 |
+ d.devices = devices |
|
| 267 |
+ |
|
| 268 |
+ if d.DeviceId >= devices.nextFreeDevice {
|
|
| 269 |
+ devices.nextFreeDevice = d.DeviceId + 1 |
|
| 270 |
+ } |
|
| 271 |
+ |
|
| 272 |
+ // If the transaction id is larger than the actual one we lost the device due to some crash |
|
| 273 |
+ if d.TransactionId > devices.TransactionId {
|
|
| 274 |
+ utils.Debugf("Removing lost device %s with id %d", hash, d.TransactionId)
|
|
| 275 |
+ delete(devices.Devices, hash) |
|
| 276 |
+ } |
|
| 277 |
+ } |
|
| 278 |
+ return nil |
|
| 279 |
+} |
|
| 280 |
+ |
|
| 281 |
+func (devices *DeviceSet) setupBaseImage() error {
|
|
| 282 |
+ oldInfo := devices.Devices[""] |
|
| 283 |
+ if oldInfo != nil && oldInfo.Initialized {
|
|
| 284 |
+ return nil |
|
| 285 |
+ } |
|
| 286 |
+ |
|
| 287 |
+ if oldInfo != nil && !oldInfo.Initialized {
|
|
| 288 |
+ utils.Debugf("Removing uninitialized base image")
|
|
| 289 |
+ if err := devices.removeDevice(""); err != nil {
|
|
| 290 |
+ utils.Debugf("\n--->Err: %s\n", err)
|
|
| 291 |
+ return err |
|
| 292 |
+ } |
|
| 293 |
+ } |
|
| 294 |
+ |
|
| 295 |
+ utils.Debugf("Initializing base device-manager snapshot")
|
|
| 296 |
+ |
|
| 297 |
+ id := devices.allocateDeviceId() |
|
| 298 |
+ |
|
| 299 |
+ // Create initial device |
|
| 300 |
+ if err := createDevice(devices.getPoolDevName(), id); err != nil {
|
|
| 301 |
+ utils.Debugf("\n--->Err: %s\n", err)
|
|
| 302 |
+ return err |
|
| 303 |
+ } |
|
| 304 |
+ |
|
| 305 |
+ utils.Debugf("Registering base device (id %v) with FS size %v", id, DefaultBaseFsSize)
|
|
| 306 |
+ info, err := devices.registerDevice(id, "", DefaultBaseFsSize) |
|
| 307 |
+ if err != nil {
|
|
| 308 |
+ _ = deleteDevice(devices.getPoolDevName(), id) |
|
| 309 |
+ utils.Debugf("\n--->Err: %s\n", err)
|
|
| 310 |
+ return err |
|
| 311 |
+ } |
|
| 312 |
+ |
|
| 313 |
+ utils.Debugf("Creating filesystem on base device-manager snapshot")
|
|
| 314 |
+ |
|
| 315 |
+ if err = devices.activateDeviceIfNeeded(""); err != nil {
|
|
| 316 |
+ utils.Debugf("\n--->Err: %s\n", err)
|
|
| 317 |
+ return err |
|
| 318 |
+ } |
|
| 319 |
+ |
|
| 320 |
+ if err := devices.createFilesystem(info); err != nil {
|
|
| 321 |
+ utils.Debugf("\n--->Err: %s\n", err)
|
|
| 322 |
+ return err |
|
| 323 |
+ } |
|
| 324 |
+ |
|
| 325 |
+ info.Initialized = true |
|
| 326 |
+ if err = devices.saveMetadata(); err != nil {
|
|
| 327 |
+ info.Initialized = false |
|
| 328 |
+ utils.Debugf("\n--->Err: %s\n", err)
|
|
| 329 |
+ return err |
|
| 330 |
+ } |
|
| 331 |
+ |
|
| 332 |
+ return nil |
|
| 333 |
+} |
|
| 334 |
+ |
|
| 335 |
+func setCloseOnExec(name string) {
|
|
| 336 |
+ if fileInfos, _ := ioutil.ReadDir("/proc/self/fd"); fileInfos != nil {
|
|
| 337 |
+ for _, i := range fileInfos {
|
|
| 338 |
+ link, _ := osReadlink(filepath.Join("/proc/self/fd", i.Name()))
|
|
| 339 |
+ if link == name {
|
|
| 340 |
+ fd, err := strconv.Atoi(i.Name()) |
|
| 341 |
+ if err == nil {
|
|
| 342 |
+ sysCloseOnExec(fd) |
|
| 343 |
+ } |
|
| 344 |
+ } |
|
| 345 |
+ } |
|
| 346 |
+ } |
|
| 347 |
+} |
|
| 348 |
+ |
|
| 349 |
+func (devices *DeviceSet) log(level int, file string, line int, dmError int, message string) {
|
|
| 350 |
+ if level >= 7 {
|
|
| 351 |
+ return // Ignore _LOG_DEBUG |
|
| 352 |
+ } |
|
| 353 |
+ |
|
| 354 |
+ utils.Debugf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message)
|
|
| 355 |
+} |
|
| 356 |
+ |
|
| 357 |
+func major(device uint64) uint64 {
|
|
| 358 |
+ return (device >> 8) & 0xfff |
|
| 359 |
+} |
|
| 360 |
+ |
|
| 361 |
+func minor(device uint64) uint64 {
|
|
| 362 |
+ return (device & 0xff) | ((device >> 12) & 0xfff00) |
|
| 363 |
+} |
|
| 364 |
+ |
|
| 365 |
+func (devices *DeviceSet) ResizePool(size int64) error {
|
|
| 366 |
+ dirname := devices.loopbackDir() |
|
| 367 |
+ datafilename := path.Join(dirname, "data") |
|
| 368 |
+ metadatafilename := path.Join(dirname, "metadata") |
|
| 369 |
+ |
|
| 370 |
+ datafile, err := osOpenFile(datafilename, osORdWr, 0) |
|
| 371 |
+ if datafile == nil {
|
|
| 372 |
+ return err |
|
| 373 |
+ } |
|
| 374 |
+ defer datafile.Close() |
|
| 375 |
+ |
|
| 376 |
+ fi, err := datafile.Stat() |
|
| 377 |
+ if fi == nil {
|
|
| 378 |
+ return err |
|
| 379 |
+ } |
|
| 380 |
+ |
|
| 381 |
+ if fi.Size() > size {
|
|
| 382 |
+ return fmt.Errorf("Can't shrink file")
|
|
| 383 |
+ } |
|
| 384 |
+ |
|
| 385 |
+ dataloopback := FindLoopDeviceFor(&osFile{File: datafile})
|
|
| 386 |
+ if dataloopback == nil {
|
|
| 387 |
+ return fmt.Errorf("Unable to find loopback mount for: %s", datafilename)
|
|
| 388 |
+ } |
|
| 389 |
+ defer dataloopback.Close() |
|
| 390 |
+ |
|
| 391 |
+ metadatafile, err := osOpenFile(metadatafilename, osORdWr, 0) |
|
| 392 |
+ if metadatafile == nil {
|
|
| 393 |
+ return err |
|
| 394 |
+ } |
|
| 395 |
+ defer metadatafile.Close() |
|
| 396 |
+ |
|
| 397 |
+ metadataloopback := FindLoopDeviceFor(&osFile{File: metadatafile})
|
|
| 398 |
+ if metadataloopback == nil {
|
|
| 399 |
+ return fmt.Errorf("Unable to find loopback mount for: %s", metadatafilename)
|
|
| 400 |
+ } |
|
| 401 |
+ defer metadataloopback.Close() |
|
| 402 |
+ |
|
| 403 |
+ // Grow loopback file |
|
| 404 |
+ if err := datafile.Truncate(size); err != nil {
|
|
| 405 |
+ return fmt.Errorf("Unable to grow loopback file: %s", err)
|
|
| 406 |
+ } |
|
| 407 |
+ |
|
| 408 |
+ // Reload size for loopback device |
|
| 409 |
+ if err := LoopbackSetCapacity(dataloopback); err != nil {
|
|
| 410 |
+ return fmt.Errorf("Unable to update loopback capacity: %s", err)
|
|
| 411 |
+ } |
|
| 412 |
+ |
|
| 413 |
+ // Suspend the pool |
|
| 414 |
+ if err := suspendDevice(devices.getPoolName()); err != nil {
|
|
| 415 |
+ return fmt.Errorf("Unable to suspend pool: %s", err)
|
|
| 416 |
+ } |
|
| 417 |
+ |
|
| 418 |
+ // Reload with the new block sizes |
|
| 419 |
+ if err := reloadPool(devices.getPoolName(), dataloopback, metadataloopback); err != nil {
|
|
| 420 |
+ return fmt.Errorf("Unable to reload pool: %s", err)
|
|
| 421 |
+ } |
|
| 422 |
+ |
|
| 423 |
+ // Resume the pool |
|
| 424 |
+ if err := resumeDevice(devices.getPoolName()); err != nil {
|
|
| 425 |
+ return fmt.Errorf("Unable to resume pool: %s", err)
|
|
| 426 |
+ } |
|
| 427 |
+ |
|
| 428 |
+ return nil |
|
| 429 |
+} |
|
| 430 |
+ |
|
| 431 |
+func (devices *DeviceSet) initDevmapper(doInit bool) error {
|
|
| 432 |
+ logInit(devices) |
|
| 433 |
+ |
|
| 434 |
+ // Make sure the sparse images exist in <root>/devicemapper/data and |
|
| 435 |
+ // <root>/devicemapper/metadata |
|
| 436 |
+ |
|
| 437 |
+ hasData := devices.hasImage("data")
|
|
| 438 |
+ hasMetadata := devices.hasImage("metadata")
|
|
| 439 |
+ |
|
| 440 |
+ if !doInit && !hasData {
|
|
| 441 |
+ return fmt.Errorf("Looback data file not found %s")
|
|
| 442 |
+ } |
|
| 443 |
+ |
|
| 444 |
+ if !doInit && !hasMetadata {
|
|
| 445 |
+ return fmt.Errorf("Looback metadata file not found %s")
|
|
| 446 |
+ } |
|
| 447 |
+ |
|
| 448 |
+ createdLoopback := !hasData || !hasMetadata |
|
| 449 |
+ data, err := devices.ensureImage("data", DefaultDataLoopbackSize)
|
|
| 450 |
+ if err != nil {
|
|
| 451 |
+ utils.Debugf("Error device ensureImage (data): %s\n", err)
|
|
| 452 |
+ return err |
|
| 453 |
+ } |
|
| 454 |
+ metadata, err := devices.ensureImage("metadata", DefaultMetaDataLoopbackSize)
|
|
| 455 |
+ if err != nil {
|
|
| 456 |
+ utils.Debugf("Error device ensureImage (metadata): %s\n", err)
|
|
| 457 |
+ return err |
|
| 458 |
+ } |
|
| 459 |
+ |
|
| 460 |
+ // Set the device prefix from the device id and inode of the docker root dir |
|
| 461 |
+ |
|
| 462 |
+ st, err := osStat(devices.root) |
|
| 463 |
+ if err != nil {
|
|
| 464 |
+ return fmt.Errorf("Error looking up dir %s: %s", devices.root, err)
|
|
| 465 |
+ } |
|
| 466 |
+ sysSt := toSysStatT(st.Sys()) |
|
| 467 |
+ // "reg-" stands for "regular file". |
|
| 468 |
+ // In the future we might use "dev-" for "device file", etc. |
|
| 469 |
+ // docker-maj,min[-inode] stands for: |
|
| 470 |
+ // - Managed by docker |
|
| 471 |
+ // - The target of this device is at major <maj> and minor <min> |
|
| 472 |
+ // - If <inode> is defined, use that file inside the device as a loopback image. Otherwise use the device itself. |
|
| 473 |
+ devices.devicePrefix = fmt.Sprintf("docker-%d:%d-%d", major(sysSt.Dev), minor(sysSt.Dev), sysSt.Ino)
|
|
| 474 |
+ utils.Debugf("Generated prefix: %s", devices.devicePrefix)
|
|
| 475 |
+ |
|
| 476 |
+ // Check for the existence of the device <prefix>-pool |
|
| 477 |
+ utils.Debugf("Checking for existence of the pool '%s'", devices.getPoolName())
|
|
| 478 |
+ info, err := getInfo(devices.getPoolName()) |
|
| 479 |
+ if info == nil {
|
|
| 480 |
+ utils.Debugf("Error device getInfo: %s", err)
|
|
| 481 |
+ return err |
|
| 482 |
+ } |
|
| 483 |
+ |
|
| 484 |
+ // It seems libdevmapper opens this without O_CLOEXEC, and go exec will not close files |
|
| 485 |
+ // that are not Close-on-exec, and lxc-start will die if it inherits any unexpected files, |
|
| 486 |
+ // so we add this badhack to make sure it closes itself |
|
| 487 |
+ setCloseOnExec("/dev/mapper/control")
|
|
| 488 |
+ |
|
| 489 |
+ // If the pool doesn't exist, create it |
|
| 490 |
+ if info.Exists == 0 {
|
|
| 491 |
+ utils.Debugf("Pool doesn't exist. Creating it.")
|
|
| 492 |
+ |
|
| 493 |
+ dataFile, err := AttachLoopDevice(data) |
|
| 494 |
+ if err != nil {
|
|
| 495 |
+ utils.Debugf("\n--->Err: %s\n", err)
|
|
| 496 |
+ return err |
|
| 497 |
+ } |
|
| 498 |
+ defer dataFile.Close() |
|
| 499 |
+ |
|
| 500 |
+ metadataFile, err := AttachLoopDevice(metadata) |
|
| 501 |
+ if err != nil {
|
|
| 502 |
+ utils.Debugf("\n--->Err: %s\n", err)
|
|
| 503 |
+ return err |
|
| 504 |
+ } |
|
| 505 |
+ defer metadataFile.Close() |
|
| 506 |
+ |
|
| 507 |
+ if err := createPool(devices.getPoolName(), dataFile, metadataFile); err != nil {
|
|
| 508 |
+ utils.Debugf("\n--->Err: %s\n", err)
|
|
| 509 |
+ return err |
|
| 510 |
+ } |
|
| 511 |
+ } |
|
| 512 |
+ |
|
| 513 |
+ // If we didn't just create the data or metadata image, we need to |
|
| 514 |
+ // load the metadata from the existing file. |
|
| 515 |
+ if !createdLoopback {
|
|
| 516 |
+ if err = devices.loadMetaData(); err != nil {
|
|
| 517 |
+ utils.Debugf("\n--->Err: %s\n", err)
|
|
| 518 |
+ return err |
|
| 519 |
+ } |
|
| 520 |
+ } |
|
| 521 |
+ |
|
| 522 |
+ // Setup the base image |
|
| 523 |
+ if doInit {
|
|
| 524 |
+ if err := devices.setupBaseImage(); err != nil {
|
|
| 525 |
+ utils.Debugf("Error device setupBaseImage: %s\n", err)
|
|
| 526 |
+ return err |
|
| 527 |
+ } |
|
| 528 |
+ } |
|
| 529 |
+ |
|
| 530 |
+ return nil |
|
| 531 |
+} |
|
| 532 |
+ |
|
| 533 |
+func (devices *DeviceSet) AddDevice(hash, baseHash string) error {
|
|
| 534 |
+ devices.Lock() |
|
| 535 |
+ defer devices.Unlock() |
|
| 536 |
+ |
|
| 537 |
+ if devices.Devices[hash] != nil {
|
|
| 538 |
+ return fmt.Errorf("hash %s already exists", hash)
|
|
| 539 |
+ } |
|
| 540 |
+ |
|
| 541 |
+ baseInfo := devices.Devices[baseHash] |
|
| 542 |
+ if baseInfo == nil {
|
|
| 543 |
+ return fmt.Errorf("Error adding device for '%s': can't find device for parent '%s'", hash, baseHash)
|
|
| 544 |
+ } |
|
| 545 |
+ |
|
| 546 |
+ deviceId := devices.allocateDeviceId() |
|
| 547 |
+ |
|
| 548 |
+ if err := devices.createSnapDevice(devices.getPoolDevName(), deviceId, baseInfo.Name(), baseInfo.DeviceId); err != nil {
|
|
| 549 |
+ utils.Debugf("Error creating snap device: %s\n", err)
|
|
| 550 |
+ return err |
|
| 551 |
+ } |
|
| 552 |
+ |
|
| 553 |
+ if _, err := devices.registerDevice(deviceId, hash, baseInfo.Size); err != nil {
|
|
| 554 |
+ deleteDevice(devices.getPoolDevName(), deviceId) |
|
| 555 |
+ utils.Debugf("Error registering device: %s\n", err)
|
|
| 556 |
+ return err |
|
| 557 |
+ } |
|
| 558 |
+ return nil |
|
| 559 |
+} |
|
| 560 |
+ |
|
| 561 |
+func (devices *DeviceSet) removeDevice(hash string) error {
|
|
| 562 |
+ info := devices.Devices[hash] |
|
| 563 |
+ if info == nil {
|
|
| 564 |
+ return fmt.Errorf("hash %s doesn't exists", hash)
|
|
| 565 |
+ } |
|
| 566 |
+ |
|
| 567 |
+ devinfo, _ := getInfo(info.Name()) |
|
| 568 |
+ if devinfo != nil && devinfo.Exists != 0 {
|
|
| 569 |
+ if err := removeDevice(info.Name()); err != nil {
|
|
| 570 |
+ utils.Debugf("Error removing device: %s\n", err)
|
|
| 571 |
+ return err |
|
| 572 |
+ } |
|
| 573 |
+ } |
|
| 574 |
+ |
|
| 575 |
+ if info.Initialized {
|
|
| 576 |
+ info.Initialized = false |
|
| 577 |
+ if err := devices.saveMetadata(); err != nil {
|
|
| 578 |
+ utils.Debugf("Error saving meta data: %s\n", err)
|
|
| 579 |
+ return err |
|
| 580 |
+ } |
|
| 581 |
+ } |
|
| 582 |
+ |
|
| 583 |
+ if err := deleteDevice(devices.getPoolDevName(), info.DeviceId); err != nil {
|
|
| 584 |
+ utils.Debugf("Error deleting device: %s\n", err)
|
|
| 585 |
+ return err |
|
| 586 |
+ } |
|
| 587 |
+ |
|
| 588 |
+ devices.allocateTransactionId() |
|
| 589 |
+ delete(devices.Devices, info.Hash) |
|
| 590 |
+ |
|
| 591 |
+ if err := devices.saveMetadata(); err != nil {
|
|
| 592 |
+ devices.Devices[info.Hash] = info |
|
| 593 |
+ utils.Debugf("Error saving meta data: %s\n", err)
|
|
| 594 |
+ return err |
|
| 595 |
+ } |
|
| 596 |
+ |
|
| 597 |
+ return nil |
|
| 598 |
+} |
|
| 599 |
+ |
|
| 600 |
+func (devices *DeviceSet) RemoveDevice(hash string) error {
|
|
| 601 |
+ devices.Lock() |
|
| 602 |
+ defer devices.Unlock() |
|
| 603 |
+ |
|
| 604 |
+ return devices.removeDevice(hash) |
|
| 605 |
+} |
|
| 606 |
+ |
|
| 607 |
+func (devices *DeviceSet) deactivateDevice(hash string) error {
|
|
| 608 |
+ utils.Debugf("[devmapper] deactivateDevice(%s)", hash)
|
|
| 609 |
+ defer utils.Debugf("[devmapper] deactivateDevice END")
|
|
| 610 |
+ var devname string |
|
| 611 |
+ // FIXME: shouldn't we just register the pool into devices? |
|
| 612 |
+ devname, err := devices.byHash(hash) |
|
| 613 |
+ if err != nil {
|
|
| 614 |
+ return err |
|
| 615 |
+ } |
|
| 616 |
+ devinfo, err := getInfo(devname) |
|
| 617 |
+ if err != nil {
|
|
| 618 |
+ utils.Debugf("\n--->Err: %s\n", err)
|
|
| 619 |
+ return err |
|
| 620 |
+ } |
|
| 621 |
+ if devinfo.Exists != 0 {
|
|
| 622 |
+ if err := removeDevice(devname); err != nil {
|
|
| 623 |
+ utils.Debugf("\n--->Err: %s\n", err)
|
|
| 624 |
+ return err |
|
| 625 |
+ } |
|
| 626 |
+ if err := devices.waitRemove(hash); err != nil {
|
|
| 627 |
+ return err |
|
| 628 |
+ } |
|
| 629 |
+ } |
|
| 630 |
+ |
|
| 631 |
+ return nil |
|
| 632 |
+} |
|
| 633 |
+ |
|
| 634 |
+// waitRemove blocks until either: |
|
| 635 |
+// a) the device registered at <device_set_prefix>-<hash> is removed, |
|
| 636 |
+// or b) the 1 second timeout expires. |
|
| 637 |
+func (devices *DeviceSet) waitRemove(hash string) error {
|
|
| 638 |
+ utils.Debugf("[deviceset %s] waitRemove(%s)", devices.devicePrefix, hash)
|
|
| 639 |
+ defer utils.Debugf("[deviceset %s] waitRemove END", devices.devicePrefix, hash)
|
|
| 640 |
+ devname, err := devices.byHash(hash) |
|
| 641 |
+ if err != nil {
|
|
| 642 |
+ return err |
|
| 643 |
+ } |
|
| 644 |
+ i := 0 |
|
| 645 |
+ for ; i < 1000; i += 1 {
|
|
| 646 |
+ devinfo, err := getInfo(devname) |
|
| 647 |
+ if err != nil {
|
|
| 648 |
+ // If there is an error we assume the device doesn't exist. |
|
| 649 |
+ // The error might actually be something else, but we can't differentiate. |
|
| 650 |
+ return nil |
|
| 651 |
+ } |
|
| 652 |
+ if i%100 == 0 {
|
|
| 653 |
+ utils.Debugf("Waiting for removal of %s: exists=%d", devname, devinfo.Exists)
|
|
| 654 |
+ } |
|
| 655 |
+ if devinfo.Exists == 0 {
|
|
| 656 |
+ break |
|
| 657 |
+ } |
|
| 658 |
+ |
|
| 659 |
+ time.Sleep(1 * time.Millisecond) |
|
| 660 |
+ } |
|
| 661 |
+ if i == 1000 {
|
|
| 662 |
+ return fmt.Errorf("Timeout while waiting for device %s to be removed", devname)
|
|
| 663 |
+ } |
|
| 664 |
+ return nil |
|
| 665 |
+} |
|
| 666 |
+ |
|
| 667 |
+// waitClose blocks until either: |
|
| 668 |
+// a) the device registered at <device_set_prefix>-<hash> is closed, |
|
| 669 |
+// or b) the 1 second timeout expires. |
|
| 670 |
+func (devices *DeviceSet) waitClose(hash string) error {
|
|
| 671 |
+ devname, err := devices.byHash(hash) |
|
| 672 |
+ if err != nil {
|
|
| 673 |
+ return err |
|
| 674 |
+ } |
|
| 675 |
+ i := 0 |
|
| 676 |
+ for ; i < 1000; i += 1 {
|
|
| 677 |
+ devinfo, err := getInfo(devname) |
|
| 678 |
+ if err != nil {
|
|
| 679 |
+ return err |
|
| 680 |
+ } |
|
| 681 |
+ if i%100 == 0 {
|
|
| 682 |
+ utils.Debugf("Waiting for unmount of %s: opencount=%d", devname, devinfo.OpenCount)
|
|
| 683 |
+ } |
|
| 684 |
+ if devinfo.OpenCount == 0 {
|
|
| 685 |
+ break |
|
| 686 |
+ } |
|
| 687 |
+ time.Sleep(1 * time.Millisecond) |
|
| 688 |
+ } |
|
| 689 |
+ if i == 1000 {
|
|
| 690 |
+ return fmt.Errorf("Timeout while waiting for device %s to close", devname)
|
|
| 691 |
+ } |
|
| 692 |
+ return nil |
|
| 693 |
+} |
|
| 694 |
+ |
|
| 695 |
+// byHash is a hack to allow looking up the deviceset's pool by the hash "pool". |
|
| 696 |
+// FIXME: it seems probably cleaner to register the pool in devices.Devices, |
|
| 697 |
+// but I am afraid of arcane implications deep in the devicemapper code, |
|
| 698 |
+// so this will do. |
|
| 699 |
+func (devices *DeviceSet) byHash(hash string) (devname string, err error) {
|
|
| 700 |
+ if hash == "pool" {
|
|
| 701 |
+ return devices.getPoolDevName(), nil |
|
| 702 |
+ } |
|
| 703 |
+ info := devices.Devices[hash] |
|
| 704 |
+ if info == nil {
|
|
| 705 |
+ return "", fmt.Errorf("hash %s doesn't exists", hash)
|
|
| 706 |
+ } |
|
| 707 |
+ return info.Name(), nil |
|
| 708 |
+} |
|
| 709 |
+ |
|
| 710 |
+func (devices *DeviceSet) Shutdown() error {
|
|
| 711 |
+ devices.Lock() |
|
| 712 |
+ defer devices.Unlock() |
|
| 713 |
+ |
|
| 714 |
+ utils.Debugf("[deviceset %s] shutdown()", devices.devicePrefix)
|
|
| 715 |
+ utils.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root)
|
|
| 716 |
+ defer utils.Debugf("[deviceset %s] shutdown END", devices.devicePrefix)
|
|
| 717 |
+ |
|
| 718 |
+ for path, count := range devices.activeMounts {
|
|
| 719 |
+ for i := count; i > 0; i-- {
|
|
| 720 |
+ if err := sysUnmount(path, 0); err != nil {
|
|
| 721 |
+ utils.Debugf("Shutdown unmounting %s, error: %s\n", path, err)
|
|
| 722 |
+ } |
|
| 723 |
+ } |
|
| 724 |
+ delete(devices.activeMounts, path) |
|
| 725 |
+ } |
|
| 726 |
+ |
|
| 727 |
+ for _, d := range devices.Devices {
|
|
| 728 |
+ if err := devices.waitClose(d.Hash); err != nil {
|
|
| 729 |
+ utils.Errorf("Warning: error waiting for device %s to unmount: %s\n", d.Hash, err)
|
|
| 730 |
+ } |
|
| 731 |
+ if err := devices.deactivateDevice(d.Hash); err != nil {
|
|
| 732 |
+ utils.Debugf("Shutdown deactivate %s , error: %s\n", d.Hash, err)
|
|
| 733 |
+ } |
|
| 734 |
+ } |
|
| 735 |
+ |
|
| 736 |
+ pool := devices.getPoolDevName() |
|
| 737 |
+ if devinfo, err := getInfo(pool); err == nil && devinfo.Exists != 0 {
|
|
| 738 |
+ if err := devices.deactivateDevice("pool"); err != nil {
|
|
| 739 |
+ utils.Debugf("Shutdown deactivate %s , error: %s\n", pool, err)
|
|
| 740 |
+ } |
|
| 741 |
+ } |
|
| 742 |
+ |
|
| 743 |
+ return nil |
|
| 744 |
+} |
|
| 745 |
+ |
|
| 746 |
+func (devices *DeviceSet) MountDevice(hash, path string, readOnly bool) error {
|
|
| 747 |
+ devices.Lock() |
|
| 748 |
+ defer devices.Unlock() |
|
| 749 |
+ |
|
| 750 |
+ if err := devices.activateDeviceIfNeeded(hash); err != nil {
|
|
| 751 |
+ return fmt.Errorf("Error activating devmapper device for '%s': %s", hash, err)
|
|
| 752 |
+ } |
|
| 753 |
+ |
|
| 754 |
+ info := devices.Devices[hash] |
|
| 755 |
+ |
|
| 756 |
+ var flags uintptr = sysMsMgcVal |
|
| 757 |
+ |
|
| 758 |
+ if readOnly {
|
|
| 759 |
+ flags = flags | sysMsRdOnly |
|
| 760 |
+ } |
|
| 761 |
+ |
|
| 762 |
+ err := sysMount(info.DevName(), path, "ext4", flags, "discard") |
|
| 763 |
+ if err != nil && err == sysEInval {
|
|
| 764 |
+ err = sysMount(info.DevName(), path, "ext4", flags, "") |
|
| 765 |
+ } |
|
| 766 |
+ if err != nil {
|
|
| 767 |
+ return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), path, err)
|
|
| 768 |
+ } |
|
| 769 |
+ |
|
| 770 |
+ count := devices.activeMounts[path] |
|
| 771 |
+ devices.activeMounts[path] = count + 1 |
|
| 772 |
+ |
|
| 773 |
+ return devices.setInitialized(hash) |
|
| 774 |
+} |
|
| 775 |
+ |
|
| 776 |
+func (devices *DeviceSet) UnmountDevice(hash, path string, deactivate bool) error {
|
|
| 777 |
+ utils.Debugf("[devmapper] UnmountDevice(hash=%s path=%s)", hash, path)
|
|
| 778 |
+ defer utils.Debugf("[devmapper] UnmountDevice END")
|
|
| 779 |
+ devices.Lock() |
|
| 780 |
+ defer devices.Unlock() |
|
| 781 |
+ |
|
| 782 |
+ utils.Debugf("[devmapper] Unmount(%s)", path)
|
|
| 783 |
+ if err := sysUnmount(path, 0); err != nil {
|
|
| 784 |
+ utils.Debugf("\n--->Err: %s\n", err)
|
|
| 785 |
+ return err |
|
| 786 |
+ } |
|
| 787 |
+ utils.Debugf("[devmapper] Unmount done")
|
|
| 788 |
+ // Wait for the unmount to be effective, |
|
| 789 |
+ // by watching the value of Info.OpenCount for the device |
|
| 790 |
+ if err := devices.waitClose(hash); err != nil {
|
|
| 791 |
+ return err |
|
| 792 |
+ } |
|
| 793 |
+ |
|
| 794 |
+ if count := devices.activeMounts[path]; count > 1 {
|
|
| 795 |
+ devices.activeMounts[path] = count - 1 |
|
| 796 |
+ } else {
|
|
| 797 |
+ delete(devices.activeMounts, path) |
|
| 798 |
+ } |
|
| 799 |
+ |
|
| 800 |
+ if deactivate {
|
|
| 801 |
+ devices.deactivateDevice(hash) |
|
| 802 |
+ } |
|
| 803 |
+ |
|
| 804 |
+ return nil |
|
| 805 |
+} |
|
| 806 |
+ |
|
| 807 |
+func (devices *DeviceSet) HasDevice(hash string) bool {
|
|
| 808 |
+ devices.Lock() |
|
| 809 |
+ defer devices.Unlock() |
|
| 810 |
+ |
|
| 811 |
+ return devices.Devices[hash] != nil |
|
| 812 |
+} |
|
| 813 |
+ |
|
| 814 |
+func (devices *DeviceSet) HasInitializedDevice(hash string) bool {
|
|
| 815 |
+ devices.Lock() |
|
| 816 |
+ defer devices.Unlock() |
|
| 817 |
+ |
|
| 818 |
+ info := devices.Devices[hash] |
|
| 819 |
+ return info != nil && info.Initialized |
|
| 820 |
+} |
|
| 821 |
+ |
|
| 822 |
+func (devices *DeviceSet) HasActivatedDevice(hash string) bool {
|
|
| 823 |
+ devices.Lock() |
|
| 824 |
+ defer devices.Unlock() |
|
| 825 |
+ |
|
| 826 |
+ info := devices.Devices[hash] |
|
| 827 |
+ if info == nil {
|
|
| 828 |
+ return false |
|
| 829 |
+ } |
|
| 830 |
+ devinfo, _ := getInfo(info.Name()) |
|
| 831 |
+ return devinfo != nil && devinfo.Exists != 0 |
|
| 832 |
+} |
|
| 833 |
+ |
|
| 834 |
+func (devices *DeviceSet) setInitialized(hash string) error {
|
|
| 835 |
+ info := devices.Devices[hash] |
|
| 836 |
+ if info == nil {
|
|
| 837 |
+ return fmt.Errorf("Unknown device %s", hash)
|
|
| 838 |
+ } |
|
| 839 |
+ |
|
| 840 |
+ info.Initialized = true |
|
| 841 |
+ if err := devices.saveMetadata(); err != nil {
|
|
| 842 |
+ info.Initialized = false |
|
| 843 |
+ utils.Debugf("\n--->Err: %s\n", err)
|
|
| 844 |
+ return err |
|
| 845 |
+ } |
|
| 846 |
+ |
|
| 847 |
+ return nil |
|
| 848 |
+} |
|
| 849 |
+ |
|
| 850 |
+func (devices *DeviceSet) List() []string {
|
|
| 851 |
+ devices.Lock() |
|
| 852 |
+ defer devices.Unlock() |
|
| 853 |
+ |
|
| 854 |
+ ids := make([]string, len(devices.Devices)) |
|
| 855 |
+ i := 0 |
|
| 856 |
+ for k := range devices.Devices {
|
|
| 857 |
+ ids[i] = k |
|
| 858 |
+ i++ |
|
| 859 |
+ } |
|
| 860 |
+ return ids |
|
| 861 |
+} |
|
| 862 |
+ |
|
| 863 |
+func (devices *DeviceSet) deviceStatus(devName string) (sizeInSectors, mappedSectors, highestMappedSector uint64, err error) {
|
|
| 864 |
+ var params string |
|
| 865 |
+ _, sizeInSectors, _, params, err = getStatus(devName) |
|
| 866 |
+ if err != nil {
|
|
| 867 |
+ return |
|
| 868 |
+ } |
|
| 869 |
+ if _, err = fmt.Sscanf(params, "%d %d", &mappedSectors, &highestMappedSector); err == nil {
|
|
| 870 |
+ return |
|
| 871 |
+ } |
|
| 872 |
+ return |
|
| 873 |
+} |
|
| 874 |
+ |
|
| 875 |
+func (devices *DeviceSet) GetDeviceStatus(hash string) (*DevStatus, error) {
|
|
| 876 |
+ devices.Lock() |
|
| 877 |
+ defer devices.Unlock() |
|
| 878 |
+ |
|
| 879 |
+ info := devices.Devices[hash] |
|
| 880 |
+ if info == nil {
|
|
| 881 |
+ return nil, fmt.Errorf("No device %s", hash)
|
|
| 882 |
+ } |
|
| 883 |
+ |
|
| 884 |
+ status := &DevStatus{
|
|
| 885 |
+ DeviceId: info.DeviceId, |
|
| 886 |
+ Size: info.Size, |
|
| 887 |
+ TransactionId: info.TransactionId, |
|
| 888 |
+ } |
|
| 889 |
+ |
|
| 890 |
+ if err := devices.activateDeviceIfNeeded(hash); err != nil {
|
|
| 891 |
+ return nil, fmt.Errorf("Error activating devmapper device for '%s': %s", hash, err)
|
|
| 892 |
+ } |
|
| 893 |
+ |
|
| 894 |
+ if sizeInSectors, mappedSectors, highestMappedSector, err := devices.deviceStatus(info.DevName()); err != nil {
|
|
| 895 |
+ return nil, err |
|
| 896 |
+ } else {
|
|
| 897 |
+ status.SizeInSectors = sizeInSectors |
|
| 898 |
+ status.MappedSectors = mappedSectors |
|
| 899 |
+ status.HighestMappedSector = highestMappedSector |
|
| 900 |
+ } |
|
| 901 |
+ |
|
| 902 |
+ return status, nil |
|
| 903 |
+} |
|
| 904 |
+ |
|
| 905 |
+func (devices *DeviceSet) poolStatus() (totalSizeInSectors, transactionId, dataUsed, dataTotal, metadataUsed, metadataTotal uint64, err error) {
|
|
| 906 |
+ var params string |
|
| 907 |
+ if _, totalSizeInSectors, _, params, err = getStatus(devices.getPoolName()); err == nil {
|
|
| 908 |
+ _, err = fmt.Sscanf(params, "%d %d/%d %d/%d", &transactionId, &metadataUsed, &metadataTotal, &dataUsed, &dataTotal) |
|
| 909 |
+ } |
|
| 910 |
+ return |
|
| 911 |
+} |
|
| 912 |
+ |
|
| 913 |
+func (devices *DeviceSet) Status() *Status {
|
|
| 914 |
+ devices.Lock() |
|
| 915 |
+ defer devices.Unlock() |
|
| 916 |
+ |
|
| 917 |
+ status := &Status{}
|
|
| 918 |
+ |
|
| 919 |
+ status.PoolName = devices.getPoolName() |
|
| 920 |
+ status.DataLoopback = path.Join(devices.loopbackDir(), "data") |
|
| 921 |
+ status.MetadataLoopback = path.Join(devices.loopbackDir(), "metadata") |
|
| 922 |
+ |
|
| 923 |
+ totalSizeInSectors, _, dataUsed, dataTotal, metadataUsed, metadataTotal, err := devices.poolStatus() |
|
| 924 |
+ if err == nil {
|
|
| 925 |
+ // Convert from blocks to bytes |
|
| 926 |
+ blockSizeInSectors := totalSizeInSectors / dataTotal |
|
| 927 |
+ |
|
| 928 |
+ status.Data.Used = dataUsed * blockSizeInSectors * 512 |
|
| 929 |
+ status.Data.Total = dataTotal * blockSizeInSectors * 512 |
|
| 930 |
+ |
|
| 931 |
+ // metadata blocks are always 4k |
|
| 932 |
+ status.Metadata.Used = metadataUsed * 4096 |
|
| 933 |
+ status.Metadata.Total = metadataTotal * 4096 |
|
| 934 |
+ |
|
| 935 |
+ status.SectorSize = blockSizeInSectors * 512 |
|
| 936 |
+ } |
|
| 937 |
+ |
|
| 938 |
+ return status |
|
| 939 |
+} |
|
| 940 |
+ |
|
| 941 |
+func NewDeviceSet(root string, doInit bool) (*DeviceSet, error) {
|
|
| 942 |
+ SetDevDir("/dev")
|
|
| 943 |
+ |
|
| 944 |
+ devices := &DeviceSet{
|
|
| 945 |
+ root: root, |
|
| 946 |
+ MetaData: MetaData{Devices: make(map[string]*DevInfo)},
|
|
| 947 |
+ activeMounts: make(map[string]int), |
|
| 948 |
+ } |
|
| 949 |
+ |
|
| 950 |
+ if err := devices.initDevmapper(doInit); err != nil {
|
|
| 951 |
+ return nil, err |
|
| 952 |
+ } |
|
| 953 |
+ |
|
| 954 |
+ return devices, nil |
|
| 955 |
+} |
| 0 | 956 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,576 @@ |
| 0 |
+package devmapper |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "errors" |
|
| 4 |
+ "fmt" |
|
| 5 |
+ "github.com/dotcloud/docker/utils" |
|
| 6 |
+ "runtime" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+type DevmapperLogger interface {
|
|
| 10 |
+ log(level int, file string, line int, dmError int, message string) |
|
| 11 |
+} |
|
| 12 |
+ |
|
| 13 |
+const ( |
|
| 14 |
+ DeviceCreate TaskType = iota |
|
| 15 |
+ DeviceReload |
|
| 16 |
+ DeviceRemove |
|
| 17 |
+ DeviceRemoveAll |
|
| 18 |
+ DeviceSuspend |
|
| 19 |
+ DeviceResume |
|
| 20 |
+ DeviceInfo |
|
| 21 |
+ DeviceDeps |
|
| 22 |
+ DeviceRename |
|
| 23 |
+ DeviceVersion |
|
| 24 |
+ DeviceStatus |
|
| 25 |
+ DeviceTable |
|
| 26 |
+ DeviceWaitevent |
|
| 27 |
+ DeviceList |
|
| 28 |
+ DeviceClear |
|
| 29 |
+ DeviceMknodes |
|
| 30 |
+ DeviceListVersions |
|
| 31 |
+ DeviceTargetMsg |
|
| 32 |
+ DeviceSetGeometry |
|
| 33 |
+) |
|
| 34 |
+ |
|
| 35 |
+const ( |
|
| 36 |
+ AddNodeOnResume AddNodeType = iota |
|
| 37 |
+ AddNodeOnCreate |
|
| 38 |
+) |
|
| 39 |
+ |
|
| 40 |
+var ( |
|
| 41 |
+ ErrTaskRun = errors.New("dm_task_run failed")
|
|
| 42 |
+ ErrTaskSetName = errors.New("dm_task_set_name failed")
|
|
| 43 |
+ ErrTaskSetMessage = errors.New("dm_task_set_message failed")
|
|
| 44 |
+ ErrTaskSetAddNode = errors.New("dm_task_set_add_node failed")
|
|
| 45 |
+ ErrTaskSetRo = errors.New("dm_task_set_ro failed")
|
|
| 46 |
+ ErrTaskAddTarget = errors.New("dm_task_add_target failed")
|
|
| 47 |
+ ErrTaskSetSector = errors.New("dm_task_set_sector failed")
|
|
| 48 |
+ ErrTaskGetInfo = errors.New("dm_task_get_info failed")
|
|
| 49 |
+ ErrTaskSetCookie = errors.New("dm_task_set_cookie failed")
|
|
| 50 |
+ ErrNilCookie = errors.New("cookie ptr can't be nil")
|
|
| 51 |
+ ErrAttachLoopbackDevice = errors.New("loopback mounting failed")
|
|
| 52 |
+ ErrGetBlockSize = errors.New("Can't get block size")
|
|
| 53 |
+ ErrUdevWait = errors.New("wait on udev cookie failed")
|
|
| 54 |
+ ErrSetDevDir = errors.New("dm_set_dev_dir failed")
|
|
| 55 |
+ ErrGetLibraryVersion = errors.New("dm_get_library_version failed")
|
|
| 56 |
+ ErrCreateRemoveTask = errors.New("Can't create task of type DeviceRemove")
|
|
| 57 |
+ ErrRunRemoveDevice = errors.New("running removeDevice failed")
|
|
| 58 |
+ ErrInvalidAddNode = errors.New("Invalide AddNoce type")
|
|
| 59 |
+ ErrGetLoopbackBackingFile = errors.New("Unable to get loopback backing file")
|
|
| 60 |
+ ErrLoopbackSetCapacity = errors.New("Unable set loopback capacity")
|
|
| 61 |
+) |
|
| 62 |
+ |
|
| 63 |
+type ( |
|
| 64 |
+ Task struct {
|
|
| 65 |
+ unmanaged *CDmTask |
|
| 66 |
+ } |
|
| 67 |
+ Info struct {
|
|
| 68 |
+ Exists int |
|
| 69 |
+ Suspended int |
|
| 70 |
+ LiveTable int |
|
| 71 |
+ InactiveTable int |
|
| 72 |
+ OpenCount int32 |
|
| 73 |
+ EventNr uint32 |
|
| 74 |
+ Major uint32 |
|
| 75 |
+ Minor uint32 |
|
| 76 |
+ ReadOnly int |
|
| 77 |
+ TargetCount int32 |
|
| 78 |
+ } |
|
| 79 |
+ TaskType int |
|
| 80 |
+ AddNodeType int |
|
| 81 |
+) |
|
| 82 |
+ |
|
| 83 |
+func (t *Task) destroy() {
|
|
| 84 |
+ if t != nil {
|
|
| 85 |
+ DmTaskDestroy(t.unmanaged) |
|
| 86 |
+ runtime.SetFinalizer(t, nil) |
|
| 87 |
+ } |
|
| 88 |
+} |
|
| 89 |
+ |
|
| 90 |
+func TaskCreate(tasktype TaskType) *Task {
|
|
| 91 |
+ Ctask := DmTaskCreate(int(tasktype)) |
|
| 92 |
+ if Ctask == nil {
|
|
| 93 |
+ return nil |
|
| 94 |
+ } |
|
| 95 |
+ task := &Task{unmanaged: Ctask}
|
|
| 96 |
+ runtime.SetFinalizer(task, (*Task).destroy) |
|
| 97 |
+ return task |
|
| 98 |
+} |
|
| 99 |
+ |
|
| 100 |
+func (t *Task) Run() error {
|
|
| 101 |
+ if res := DmTaskRun(t.unmanaged); res != 1 {
|
|
| 102 |
+ return ErrTaskRun |
|
| 103 |
+ } |
|
| 104 |
+ return nil |
|
| 105 |
+} |
|
| 106 |
+ |
|
| 107 |
+func (t *Task) SetName(name string) error {
|
|
| 108 |
+ if res := DmTaskSetName(t.unmanaged, name); res != 1 {
|
|
| 109 |
+ return ErrTaskSetName |
|
| 110 |
+ } |
|
| 111 |
+ return nil |
|
| 112 |
+} |
|
| 113 |
+ |
|
| 114 |
+func (t *Task) SetMessage(message string) error {
|
|
| 115 |
+ if res := DmTaskSetMessage(t.unmanaged, message); res != 1 {
|
|
| 116 |
+ return ErrTaskSetMessage |
|
| 117 |
+ } |
|
| 118 |
+ return nil |
|
| 119 |
+} |
|
| 120 |
+ |
|
| 121 |
+func (t *Task) SetSector(sector uint64) error {
|
|
| 122 |
+ if res := DmTaskSetSector(t.unmanaged, sector); res != 1 {
|
|
| 123 |
+ return ErrTaskSetSector |
|
| 124 |
+ } |
|
| 125 |
+ return nil |
|
| 126 |
+} |
|
| 127 |
+ |
|
| 128 |
+func (t *Task) SetCookie(cookie *uint, flags uint16) error {
|
|
| 129 |
+ if cookie == nil {
|
|
| 130 |
+ return ErrNilCookie |
|
| 131 |
+ } |
|
| 132 |
+ if res := DmTaskSetCookie(t.unmanaged, cookie, flags); res != 1 {
|
|
| 133 |
+ return ErrTaskSetCookie |
|
| 134 |
+ } |
|
| 135 |
+ return nil |
|
| 136 |
+} |
|
| 137 |
+ |
|
| 138 |
+func (t *Task) SetAddNode(addNode AddNodeType) error {
|
|
| 139 |
+ if addNode != AddNodeOnResume && addNode != AddNodeOnCreate {
|
|
| 140 |
+ return ErrInvalidAddNode |
|
| 141 |
+ } |
|
| 142 |
+ if res := DmTaskSetAddNode(t.unmanaged, addNode); res != 1 {
|
|
| 143 |
+ return ErrTaskSetAddNode |
|
| 144 |
+ } |
|
| 145 |
+ return nil |
|
| 146 |
+} |
|
| 147 |
+ |
|
| 148 |
+func (t *Task) SetRo() error {
|
|
| 149 |
+ if res := DmTaskSetRo(t.unmanaged); res != 1 {
|
|
| 150 |
+ return ErrTaskSetRo |
|
| 151 |
+ } |
|
| 152 |
+ return nil |
|
| 153 |
+} |
|
| 154 |
+ |
|
| 155 |
+func (t *Task) AddTarget(start, size uint64, ttype, params string) error {
|
|
| 156 |
+ if res := DmTaskAddTarget(t.unmanaged, start, size, |
|
| 157 |
+ ttype, params); res != 1 {
|
|
| 158 |
+ return ErrTaskAddTarget |
|
| 159 |
+ } |
|
| 160 |
+ return nil |
|
| 161 |
+} |
|
| 162 |
+ |
|
| 163 |
+func (t *Task) GetInfo() (*Info, error) {
|
|
| 164 |
+ info := &Info{}
|
|
| 165 |
+ if res := DmTaskGetInfo(t.unmanaged, info); res != 1 {
|
|
| 166 |
+ return nil, ErrTaskGetInfo |
|
| 167 |
+ } |
|
| 168 |
+ return info, nil |
|
| 169 |
+} |
|
| 170 |
+ |
|
| 171 |
+func (t *Task) GetNextTarget(next uintptr) (nextPtr uintptr, start uint64, |
|
| 172 |
+ length uint64, targetType string, params string) {
|
|
| 173 |
+ |
|
| 174 |
+ return DmGetNextTarget(t.unmanaged, next, &start, &length, |
|
| 175 |
+ &targetType, ¶ms), |
|
| 176 |
+ start, length, targetType, params |
|
| 177 |
+} |
|
| 178 |
+ |
|
| 179 |
+func AttachLoopDevice(filename string) (*osFile, error) {
|
|
| 180 |
+ var fd int |
|
| 181 |
+ res := DmAttachLoopDevice(filename, &fd) |
|
| 182 |
+ if res == "" {
|
|
| 183 |
+ return nil, ErrAttachLoopbackDevice |
|
| 184 |
+ } |
|
| 185 |
+ return &osFile{File: osNewFile(uintptr(fd), res)}, nil
|
|
| 186 |
+} |
|
| 187 |
+ |
|
| 188 |
+func getLoopbackBackingFile(file *osFile) (uint64, uint64, error) {
|
|
| 189 |
+ dev, inode, err := DmGetLoopbackBackingFile(file.Fd()) |
|
| 190 |
+ if err != 0 {
|
|
| 191 |
+ return 0, 0, ErrGetLoopbackBackingFile |
|
| 192 |
+ } |
|
| 193 |
+ return dev, inode, nil |
|
| 194 |
+} |
|
| 195 |
+ |
|
| 196 |
+func LoopbackSetCapacity(file *osFile) error {
|
|
| 197 |
+ if err := DmLoopbackSetCapacity(file.Fd()); err != 0 {
|
|
| 198 |
+ return ErrLoopbackSetCapacity |
|
| 199 |
+ } |
|
| 200 |
+ return nil |
|
| 201 |
+} |
|
| 202 |
+ |
|
| 203 |
+func FindLoopDeviceFor(file *osFile) *osFile {
|
|
| 204 |
+ stat, err := file.Stat() |
|
| 205 |
+ if err != nil {
|
|
| 206 |
+ return nil |
|
| 207 |
+ } |
|
| 208 |
+ targetInode := stat.Sys().(*sysStatT).Ino |
|
| 209 |
+ targetDevice := stat.Sys().(*sysStatT).Dev |
|
| 210 |
+ |
|
| 211 |
+ for i := 0; true; i++ {
|
|
| 212 |
+ path := fmt.Sprintf("/dev/loop%d", i)
|
|
| 213 |
+ |
|
| 214 |
+ file, err := osOpenFile(path, osORdWr, 0) |
|
| 215 |
+ if err != nil {
|
|
| 216 |
+ if osIsNotExist(err) {
|
|
| 217 |
+ return nil |
|
| 218 |
+ } |
|
| 219 |
+ |
|
| 220 |
+ // Ignore all errors until the first not-exist |
|
| 221 |
+ // we want to continue looking for the file |
|
| 222 |
+ continue |
|
| 223 |
+ } |
|
| 224 |
+ |
|
| 225 |
+ dev, inode, err := getLoopbackBackingFile(&osFile{File: file})
|
|
| 226 |
+ if err == nil && dev == targetDevice && inode == targetInode {
|
|
| 227 |
+ return &osFile{File: file}
|
|
| 228 |
+ } |
|
| 229 |
+ |
|
| 230 |
+ file.Close() |
|
| 231 |
+ } |
|
| 232 |
+ |
|
| 233 |
+ return nil |
|
| 234 |
+} |
|
| 235 |
+ |
|
| 236 |
+func UdevWait(cookie uint) error {
|
|
| 237 |
+ if res := DmUdevWait(cookie); res != 1 {
|
|
| 238 |
+ utils.Debugf("Failed to wait on udev cookie %d", cookie)
|
|
| 239 |
+ return ErrUdevWait |
|
| 240 |
+ } |
|
| 241 |
+ return nil |
|
| 242 |
+} |
|
| 243 |
+ |
|
| 244 |
+func LogInitVerbose(level int) {
|
|
| 245 |
+ DmLogInitVerbose(level) |
|
| 246 |
+} |
|
| 247 |
+ |
|
| 248 |
+var dmLogger DevmapperLogger = nil |
|
| 249 |
+ |
|
| 250 |
+func logInit(logger DevmapperLogger) {
|
|
| 251 |
+ dmLogger = logger |
|
| 252 |
+ LogWithErrnoInit() |
|
| 253 |
+} |
|
| 254 |
+ |
|
| 255 |
+func SetDevDir(dir string) error {
|
|
| 256 |
+ if res := DmSetDevDir(dir); res != 1 {
|
|
| 257 |
+ utils.Debugf("Error dm_set_dev_dir")
|
|
| 258 |
+ return ErrSetDevDir |
|
| 259 |
+ } |
|
| 260 |
+ return nil |
|
| 261 |
+} |
|
| 262 |
+ |
|
| 263 |
+func GetLibraryVersion() (string, error) {
|
|
| 264 |
+ var version string |
|
| 265 |
+ if res := DmGetLibraryVersion(&version); res != 1 {
|
|
| 266 |
+ return "", ErrGetLibraryVersion |
|
| 267 |
+ } |
|
| 268 |
+ return version, nil |
|
| 269 |
+} |
|
| 270 |
+ |
|
| 271 |
+// Useful helper for cleanup |
|
| 272 |
+func RemoveDevice(name string) error {
|
|
| 273 |
+ task := TaskCreate(DeviceRemove) |
|
| 274 |
+ if task == nil {
|
|
| 275 |
+ return ErrCreateRemoveTask |
|
| 276 |
+ } |
|
| 277 |
+ if err := task.SetName(name); err != nil {
|
|
| 278 |
+ utils.Debugf("Can't set task name %s", name)
|
|
| 279 |
+ return err |
|
| 280 |
+ } |
|
| 281 |
+ if err := task.Run(); err != nil {
|
|
| 282 |
+ return ErrRunRemoveDevice |
|
| 283 |
+ } |
|
| 284 |
+ return nil |
|
| 285 |
+} |
|
| 286 |
+ |
|
| 287 |
+func GetBlockDeviceSize(file *osFile) (uint64, error) {
|
|
| 288 |
+ size, errno := DmGetBlockSize(file.Fd()) |
|
| 289 |
+ if size == -1 || errno != 0 {
|
|
| 290 |
+ return 0, ErrGetBlockSize |
|
| 291 |
+ } |
|
| 292 |
+ return uint64(size), nil |
|
| 293 |
+} |
|
| 294 |
+ |
|
| 295 |
+// This is the programmatic example of "dmsetup create" |
|
| 296 |
+func createPool(poolName string, dataFile, metadataFile *osFile) error {
|
|
| 297 |
+ task, err := createTask(DeviceCreate, poolName) |
|
| 298 |
+ if task == nil {
|
|
| 299 |
+ return err |
|
| 300 |
+ } |
|
| 301 |
+ |
|
| 302 |
+ size, err := GetBlockDeviceSize(dataFile) |
|
| 303 |
+ if err != nil {
|
|
| 304 |
+ return fmt.Errorf("Can't get data size")
|
|
| 305 |
+ } |
|
| 306 |
+ |
|
| 307 |
+ params := metadataFile.Name() + " " + dataFile.Name() + " 128 32768" |
|
| 308 |
+ if err := task.AddTarget(0, size/512, "thin-pool", params); err != nil {
|
|
| 309 |
+ return fmt.Errorf("Can't add target")
|
|
| 310 |
+ } |
|
| 311 |
+ |
|
| 312 |
+ var cookie uint = 0 |
|
| 313 |
+ if err := task.SetCookie(&cookie, 0); err != nil {
|
|
| 314 |
+ return fmt.Errorf("Can't set cookie")
|
|
| 315 |
+ } |
|
| 316 |
+ |
|
| 317 |
+ if err := task.Run(); err != nil {
|
|
| 318 |
+ return fmt.Errorf("Error running DeviceCreate (createPool)")
|
|
| 319 |
+ } |
|
| 320 |
+ |
|
| 321 |
+ UdevWait(cookie) |
|
| 322 |
+ |
|
| 323 |
+ return nil |
|
| 324 |
+} |
|
| 325 |
+ |
|
| 326 |
+func reloadPool(poolName string, dataFile, metadataFile *osFile) error {
|
|
| 327 |
+ task, err := createTask(DeviceReload, poolName) |
|
| 328 |
+ if task == nil {
|
|
| 329 |
+ return err |
|
| 330 |
+ } |
|
| 331 |
+ |
|
| 332 |
+ size, err := GetBlockDeviceSize(dataFile) |
|
| 333 |
+ if err != nil {
|
|
| 334 |
+ return fmt.Errorf("Can't get data size")
|
|
| 335 |
+ } |
|
| 336 |
+ |
|
| 337 |
+ params := metadataFile.Name() + " " + dataFile.Name() + " 128 32768" |
|
| 338 |
+ if err := task.AddTarget(0, size/512, "thin-pool", params); err != nil {
|
|
| 339 |
+ return fmt.Errorf("Can't add target")
|
|
| 340 |
+ } |
|
| 341 |
+ |
|
| 342 |
+ if err := task.Run(); err != nil {
|
|
| 343 |
+ return fmt.Errorf("Error running DeviceCreate")
|
|
| 344 |
+ } |
|
| 345 |
+ |
|
| 346 |
+ return nil |
|
| 347 |
+} |
|
| 348 |
+ |
|
| 349 |
+func createTask(t TaskType, name string) (*Task, error) {
|
|
| 350 |
+ task := TaskCreate(t) |
|
| 351 |
+ if task == nil {
|
|
| 352 |
+ return nil, fmt.Errorf("Can't create task of type %d", int(t))
|
|
| 353 |
+ } |
|
| 354 |
+ if err := task.SetName(name); err != nil {
|
|
| 355 |
+ return nil, fmt.Errorf("Can't set task name %s", name)
|
|
| 356 |
+ } |
|
| 357 |
+ return task, nil |
|
| 358 |
+} |
|
| 359 |
+ |
|
| 360 |
+func getInfo(name string) (*Info, error) {
|
|
| 361 |
+ task, err := createTask(DeviceInfo, name) |
|
| 362 |
+ if task == nil {
|
|
| 363 |
+ return nil, err |
|
| 364 |
+ } |
|
| 365 |
+ if err := task.Run(); err != nil {
|
|
| 366 |
+ return nil, err |
|
| 367 |
+ } |
|
| 368 |
+ return task.GetInfo() |
|
| 369 |
+} |
|
| 370 |
+ |
|
| 371 |
+func getStatus(name string) (uint64, uint64, string, string, error) {
|
|
| 372 |
+ task, err := createTask(DeviceStatus, name) |
|
| 373 |
+ if task == nil {
|
|
| 374 |
+ utils.Debugf("getStatus: Error createTask: %s", err)
|
|
| 375 |
+ return 0, 0, "", "", err |
|
| 376 |
+ } |
|
| 377 |
+ if err := task.Run(); err != nil {
|
|
| 378 |
+ utils.Debugf("getStatus: Error Run: %s", err)
|
|
| 379 |
+ return 0, 0, "", "", err |
|
| 380 |
+ } |
|
| 381 |
+ |
|
| 382 |
+ devinfo, err := task.GetInfo() |
|
| 383 |
+ if err != nil {
|
|
| 384 |
+ utils.Debugf("getStatus: Error GetInfo: %s", err)
|
|
| 385 |
+ return 0, 0, "", "", err |
|
| 386 |
+ } |
|
| 387 |
+ if devinfo.Exists == 0 {
|
|
| 388 |
+ utils.Debugf("getStatus: Non existing device %s", name)
|
|
| 389 |
+ return 0, 0, "", "", fmt.Errorf("Non existing device %s", name)
|
|
| 390 |
+ } |
|
| 391 |
+ |
|
| 392 |
+ _, start, length, targetType, params := task.GetNextTarget(0) |
|
| 393 |
+ return start, length, targetType, params, nil |
|
| 394 |
+} |
|
| 395 |
+ |
|
| 396 |
+func setTransactionId(poolName string, oldId uint64, newId uint64) error {
|
|
| 397 |
+ task, err := createTask(DeviceTargetMsg, poolName) |
|
| 398 |
+ if task == nil {
|
|
| 399 |
+ return err |
|
| 400 |
+ } |
|
| 401 |
+ |
|
| 402 |
+ if err := task.SetSector(0); err != nil {
|
|
| 403 |
+ return fmt.Errorf("Can't set sector")
|
|
| 404 |
+ } |
|
| 405 |
+ |
|
| 406 |
+ if err := task.SetMessage(fmt.Sprintf("set_transaction_id %d %d", oldId, newId)); err != nil {
|
|
| 407 |
+ return fmt.Errorf("Can't set message")
|
|
| 408 |
+ } |
|
| 409 |
+ |
|
| 410 |
+ if err := task.Run(); err != nil {
|
|
| 411 |
+ return fmt.Errorf("Error running setTransactionId")
|
|
| 412 |
+ } |
|
| 413 |
+ return nil |
|
| 414 |
+} |
|
| 415 |
+ |
|
| 416 |
+func suspendDevice(name string) error {
|
|
| 417 |
+ task, err := createTask(DeviceSuspend, name) |
|
| 418 |
+ if task == nil {
|
|
| 419 |
+ return err |
|
| 420 |
+ } |
|
| 421 |
+ if err := task.Run(); err != nil {
|
|
| 422 |
+ return fmt.Errorf("Error running DeviceSuspend")
|
|
| 423 |
+ } |
|
| 424 |
+ return nil |
|
| 425 |
+} |
|
| 426 |
+ |
|
| 427 |
+func resumeDevice(name string) error {
|
|
| 428 |
+ task, err := createTask(DeviceResume, name) |
|
| 429 |
+ if task == nil {
|
|
| 430 |
+ return err |
|
| 431 |
+ } |
|
| 432 |
+ |
|
| 433 |
+ var cookie uint = 0 |
|
| 434 |
+ if err := task.SetCookie(&cookie, 0); err != nil {
|
|
| 435 |
+ return fmt.Errorf("Can't set cookie")
|
|
| 436 |
+ } |
|
| 437 |
+ |
|
| 438 |
+ if err := task.Run(); err != nil {
|
|
| 439 |
+ return fmt.Errorf("Error running DeviceSuspend")
|
|
| 440 |
+ } |
|
| 441 |
+ |
|
| 442 |
+ UdevWait(cookie) |
|
| 443 |
+ |
|
| 444 |
+ return nil |
|
| 445 |
+} |
|
| 446 |
+ |
|
| 447 |
+func createDevice(poolName string, deviceId int) error {
|
|
| 448 |
+ utils.Debugf("[devmapper] createDevice(poolName=%v, deviceId=%v)", poolName, deviceId)
|
|
| 449 |
+ task, err := createTask(DeviceTargetMsg, poolName) |
|
| 450 |
+ if task == nil {
|
|
| 451 |
+ return err |
|
| 452 |
+ } |
|
| 453 |
+ |
|
| 454 |
+ if err := task.SetSector(0); err != nil {
|
|
| 455 |
+ return fmt.Errorf("Can't set sector")
|
|
| 456 |
+ } |
|
| 457 |
+ |
|
| 458 |
+ if err := task.SetMessage(fmt.Sprintf("create_thin %d", deviceId)); err != nil {
|
|
| 459 |
+ return fmt.Errorf("Can't set message")
|
|
| 460 |
+ } |
|
| 461 |
+ |
|
| 462 |
+ if err := task.Run(); err != nil {
|
|
| 463 |
+ return fmt.Errorf("Error running createDevice")
|
|
| 464 |
+ } |
|
| 465 |
+ return nil |
|
| 466 |
+} |
|
| 467 |
+ |
|
| 468 |
+func deleteDevice(poolName string, deviceId int) error {
|
|
| 469 |
+ task, err := createTask(DeviceTargetMsg, poolName) |
|
| 470 |
+ if task == nil {
|
|
| 471 |
+ return err |
|
| 472 |
+ } |
|
| 473 |
+ |
|
| 474 |
+ if err := task.SetSector(0); err != nil {
|
|
| 475 |
+ return fmt.Errorf("Can't set sector")
|
|
| 476 |
+ } |
|
| 477 |
+ |
|
| 478 |
+ if err := task.SetMessage(fmt.Sprintf("delete %d", deviceId)); err != nil {
|
|
| 479 |
+ return fmt.Errorf("Can't set message")
|
|
| 480 |
+ } |
|
| 481 |
+ |
|
| 482 |
+ if err := task.Run(); err != nil {
|
|
| 483 |
+ return fmt.Errorf("Error running deleteDevice")
|
|
| 484 |
+ } |
|
| 485 |
+ return nil |
|
| 486 |
+} |
|
| 487 |
+ |
|
| 488 |
+func removeDevice(name string) error {
|
|
| 489 |
+ utils.Debugf("[devmapper] removeDevice START")
|
|
| 490 |
+ defer utils.Debugf("[devmapper] removeDevice END")
|
|
| 491 |
+ task, err := createTask(DeviceRemove, name) |
|
| 492 |
+ if task == nil {
|
|
| 493 |
+ return err |
|
| 494 |
+ } |
|
| 495 |
+ if err = task.Run(); err != nil {
|
|
| 496 |
+ return fmt.Errorf("Error running removeDevice")
|
|
| 497 |
+ } |
|
| 498 |
+ return nil |
|
| 499 |
+} |
|
| 500 |
+ |
|
| 501 |
+func activateDevice(poolName string, name string, deviceId int, size uint64) error {
|
|
| 502 |
+ task, err := createTask(DeviceCreate, name) |
|
| 503 |
+ if task == nil {
|
|
| 504 |
+ return err |
|
| 505 |
+ } |
|
| 506 |
+ |
|
| 507 |
+ params := fmt.Sprintf("%s %d", poolName, deviceId)
|
|
| 508 |
+ if err := task.AddTarget(0, size/512, "thin", params); err != nil {
|
|
| 509 |
+ return fmt.Errorf("Can't add target")
|
|
| 510 |
+ } |
|
| 511 |
+ if err := task.SetAddNode(AddNodeOnCreate); err != nil {
|
|
| 512 |
+ return fmt.Errorf("Can't add node")
|
|
| 513 |
+ } |
|
| 514 |
+ |
|
| 515 |
+ var cookie uint = 0 |
|
| 516 |
+ if err := task.SetCookie(&cookie, 0); err != nil {
|
|
| 517 |
+ return fmt.Errorf("Can't set cookie")
|
|
| 518 |
+ } |
|
| 519 |
+ |
|
| 520 |
+ if err := task.Run(); err != nil {
|
|
| 521 |
+ return fmt.Errorf("Error running DeviceCreate (activateDevice)")
|
|
| 522 |
+ } |
|
| 523 |
+ |
|
| 524 |
+ UdevWait(cookie) |
|
| 525 |
+ |
|
| 526 |
+ return nil |
|
| 527 |
+} |
|
| 528 |
+ |
|
| 529 |
+func (devices *DeviceSet) createSnapDevice(poolName string, deviceId int, baseName string, baseDeviceId int) error {
|
|
| 530 |
+ devinfo, _ := getInfo(baseName) |
|
| 531 |
+ doSuspend := devinfo != nil && devinfo.Exists != 0 |
|
| 532 |
+ |
|
| 533 |
+ if doSuspend {
|
|
| 534 |
+ if err := suspendDevice(baseName); err != nil {
|
|
| 535 |
+ return err |
|
| 536 |
+ } |
|
| 537 |
+ } |
|
| 538 |
+ |
|
| 539 |
+ task, err := createTask(DeviceTargetMsg, poolName) |
|
| 540 |
+ if task == nil {
|
|
| 541 |
+ if doSuspend {
|
|
| 542 |
+ resumeDevice(baseName) |
|
| 543 |
+ } |
|
| 544 |
+ return err |
|
| 545 |
+ } |
|
| 546 |
+ |
|
| 547 |
+ if err := task.SetSector(0); err != nil {
|
|
| 548 |
+ if doSuspend {
|
|
| 549 |
+ resumeDevice(baseName) |
|
| 550 |
+ } |
|
| 551 |
+ return fmt.Errorf("Can't set sector")
|
|
| 552 |
+ } |
|
| 553 |
+ |
|
| 554 |
+ if err := task.SetMessage(fmt.Sprintf("create_snap %d %d", deviceId, baseDeviceId)); err != nil {
|
|
| 555 |
+ if doSuspend {
|
|
| 556 |
+ resumeDevice(baseName) |
|
| 557 |
+ } |
|
| 558 |
+ return fmt.Errorf("Can't set message")
|
|
| 559 |
+ } |
|
| 560 |
+ |
|
| 561 |
+ if err := task.Run(); err != nil {
|
|
| 562 |
+ if doSuspend {
|
|
| 563 |
+ resumeDevice(baseName) |
|
| 564 |
+ } |
|
| 565 |
+ return fmt.Errorf("Error running DeviceCreate (createSnapDevice)")
|
|
| 566 |
+ } |
|
| 567 |
+ |
|
| 568 |
+ if doSuspend {
|
|
| 569 |
+ if err := resumeDevice(baseName); err != nil {
|
|
| 570 |
+ return err |
|
| 571 |
+ } |
|
| 572 |
+ } |
|
| 573 |
+ |
|
| 574 |
+ return nil |
|
| 575 |
+} |
| 0 | 576 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,106 @@ |
| 0 |
+package devmapper |
|
| 1 |
+ |
|
| 2 |
+// Definition of struct dm_task and sub structures (from lvm2) |
|
| 3 |
+// |
|
| 4 |
+// struct dm_ioctl {
|
|
| 5 |
+// /* |
|
| 6 |
+// * The version number is made up of three parts: |
|
| 7 |
+// * major - no backward or forward compatibility, |
|
| 8 |
+// * minor - only backwards compatible, |
|
| 9 |
+// * patch - both backwards and forwards compatible. |
|
| 10 |
+// * |
|
| 11 |
+// * All clients of the ioctl interface should fill in the |
|
| 12 |
+// * version number of the interface that they were |
|
| 13 |
+// * compiled with. |
|
| 14 |
+// * |
|
| 15 |
+// * All recognised ioctl commands (ie. those that don't |
|
| 16 |
+// * return -ENOTTY) fill out this field, even if the |
|
| 17 |
+// * command failed. |
|
| 18 |
+// */ |
|
| 19 |
+// uint32_t version[3]; /* in/out */ |
|
| 20 |
+// uint32_t data_size; /* total size of data passed in |
|
| 21 |
+// * including this struct */ |
|
| 22 |
+ |
|
| 23 |
+// uint32_t data_start; /* offset to start of data |
|
| 24 |
+// * relative to start of this struct */ |
|
| 25 |
+ |
|
| 26 |
+// uint32_t target_count; /* in/out */ |
|
| 27 |
+// int32_t open_count; /* out */ |
|
| 28 |
+// uint32_t flags; /* in/out */ |
|
| 29 |
+ |
|
| 30 |
+// /* |
|
| 31 |
+// * event_nr holds either the event number (input and output) or the |
|
| 32 |
+// * udev cookie value (input only). |
|
| 33 |
+// * The DM_DEV_WAIT ioctl takes an event number as input. |
|
| 34 |
+// * The DM_SUSPEND, DM_DEV_REMOVE and DM_DEV_RENAME ioctls |
|
| 35 |
+// * use the field as a cookie to return in the DM_COOKIE |
|
| 36 |
+// * variable with the uevents they issue. |
|
| 37 |
+// * For output, the ioctls return the event number, not the cookie. |
|
| 38 |
+// */ |
|
| 39 |
+// uint32_t event_nr; /* in/out */ |
|
| 40 |
+// uint32_t padding; |
|
| 41 |
+ |
|
| 42 |
+// uint64_t dev; /* in/out */ |
|
| 43 |
+ |
|
| 44 |
+// char name[DM_NAME_LEN]; /* device name */ |
|
| 45 |
+// char uuid[DM_UUID_LEN]; /* unique identifier for |
|
| 46 |
+// * the block device */ |
|
| 47 |
+// char data[7]; /* padding or data */ |
|
| 48 |
+// }; |
|
| 49 |
+ |
|
| 50 |
+// struct target {
|
|
| 51 |
+// uint64_t start; |
|
| 52 |
+// uint64_t length; |
|
| 53 |
+// char *type; |
|
| 54 |
+// char *params; |
|
| 55 |
+ |
|
| 56 |
+// struct target *next; |
|
| 57 |
+// }; |
|
| 58 |
+ |
|
| 59 |
+// typedef enum {
|
|
| 60 |
+// DM_ADD_NODE_ON_RESUME, /* add /dev/mapper node with dmsetup resume */ |
|
| 61 |
+// DM_ADD_NODE_ON_CREATE /* add /dev/mapper node with dmsetup create */ |
|
| 62 |
+// } dm_add_node_t; |
|
| 63 |
+ |
|
| 64 |
+// struct dm_task {
|
|
| 65 |
+// int type; |
|
| 66 |
+// char *dev_name; |
|
| 67 |
+// char *mangled_dev_name; |
|
| 68 |
+ |
|
| 69 |
+// struct target *head, *tail; |
|
| 70 |
+ |
|
| 71 |
+// int read_only; |
|
| 72 |
+// uint32_t event_nr; |
|
| 73 |
+// int major; |
|
| 74 |
+// int minor; |
|
| 75 |
+// int allow_default_major_fallback; |
|
| 76 |
+// uid_t uid; |
|
| 77 |
+// gid_t gid; |
|
| 78 |
+// mode_t mode; |
|
| 79 |
+// uint32_t read_ahead; |
|
| 80 |
+// uint32_t read_ahead_flags; |
|
| 81 |
+// union {
|
|
| 82 |
+// struct dm_ioctl *v4; |
|
| 83 |
+// } dmi; |
|
| 84 |
+// char *newname; |
|
| 85 |
+// char *message; |
|
| 86 |
+// char *geometry; |
|
| 87 |
+// uint64_t sector; |
|
| 88 |
+// int no_flush; |
|
| 89 |
+// int no_open_count; |
|
| 90 |
+// int skip_lockfs; |
|
| 91 |
+// int query_inactive_table; |
|
| 92 |
+// int suppress_identical_reload; |
|
| 93 |
+// dm_add_node_t add_node; |
|
| 94 |
+// uint64_t existing_table_size; |
|
| 95 |
+// int cookie_set; |
|
| 96 |
+// int new_uuid; |
|
| 97 |
+// int secure_data; |
|
| 98 |
+// int retry_remove; |
|
| 99 |
+// int enable_checks; |
|
| 100 |
+// int expected_errno; |
|
| 101 |
+ |
|
| 102 |
+// char *uuid; |
|
| 103 |
+// char *mangled_uuid; |
|
| 104 |
+// }; |
|
| 105 |
+// |
| 0 | 106 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,13 @@ |
| 0 |
+package devmapper |
|
| 1 |
+ |
|
| 2 |
+import "C" |
|
| 3 |
+ |
|
| 4 |
+// Due to the way cgo works this has to be in a separate file, as devmapper.go has |
|
| 5 |
+// definitions in the cgo block, which is incompatible with using "//export" |
|
| 6 |
+ |
|
| 7 |
+//export DevmapperLogCallback |
|
| 8 |
+func DevmapperLogCallback(level C.int, file *C.char, line C.int, dm_errno_or_class C.int, message *C.char) {
|
|
| 9 |
+ if dmLogger != nil {
|
|
| 10 |
+ dmLogger.log(int(level), C.GoString(file), int(line), int(dm_errno_or_class), C.GoString(message)) |
|
| 11 |
+ } |
|
| 12 |
+} |
| 0 | 13 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,285 @@ |
| 0 |
+package devmapper |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "testing" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+func TestTaskCreate(t *testing.T) {
|
|
| 7 |
+ t.Skip("FIXME: not a unit test")
|
|
| 8 |
+ // Test success |
|
| 9 |
+ taskCreate(t, DeviceInfo) |
|
| 10 |
+ |
|
| 11 |
+ // Test Failure |
|
| 12 |
+ DmTaskCreate = dmTaskCreateFail |
|
| 13 |
+ defer func() { DmTaskCreate = dmTaskCreateFct }()
|
|
| 14 |
+ if task := TaskCreate(-1); task != nil {
|
|
| 15 |
+ t.Fatalf("An error should have occured while creating an invalid task.")
|
|
| 16 |
+ } |
|
| 17 |
+} |
|
| 18 |
+ |
|
| 19 |
+func TestTaskRun(t *testing.T) {
|
|
| 20 |
+ t.Skip("FIXME: not a unit test")
|
|
| 21 |
+ task := taskCreate(t, DeviceInfo) |
|
| 22 |
+ |
|
| 23 |
+ // Test success |
|
| 24 |
+ // Perform the RUN |
|
| 25 |
+ if err := task.Run(); err != nil {
|
|
| 26 |
+ t.Fatal(err) |
|
| 27 |
+ } |
|
| 28 |
+ // Make sure we don't have error with GetInfo |
|
| 29 |
+ if _, err := task.GetInfo(); err != nil {
|
|
| 30 |
+ t.Fatal(err) |
|
| 31 |
+ } |
|
| 32 |
+ |
|
| 33 |
+ // Test failure |
|
| 34 |
+ DmTaskRun = dmTaskRunFail |
|
| 35 |
+ defer func() { DmTaskRun = dmTaskRunFct }()
|
|
| 36 |
+ |
|
| 37 |
+ task = taskCreate(t, DeviceInfo) |
|
| 38 |
+ // Perform the RUN |
|
| 39 |
+ if err := task.Run(); err != ErrTaskRun {
|
|
| 40 |
+ t.Fatalf("An error should have occured while running task.")
|
|
| 41 |
+ } |
|
| 42 |
+ // Make sure GetInfo also fails |
|
| 43 |
+ if _, err := task.GetInfo(); err != ErrTaskGetInfo {
|
|
| 44 |
+ t.Fatalf("GetInfo should fail if task.Run() failed.")
|
|
| 45 |
+ } |
|
| 46 |
+} |
|
| 47 |
+ |
|
| 48 |
+func TestTaskSetName(t *testing.T) {
|
|
| 49 |
+ t.Skip("FIXME: not a unit test")
|
|
| 50 |
+ task := taskCreate(t, DeviceInfo) |
|
| 51 |
+ |
|
| 52 |
+ // Test success |
|
| 53 |
+ if err := task.SetName("test"); err != nil {
|
|
| 54 |
+ t.Fatal(err) |
|
| 55 |
+ } |
|
| 56 |
+ |
|
| 57 |
+ // Test failure |
|
| 58 |
+ DmTaskSetName = dmTaskSetNameFail |
|
| 59 |
+ defer func() { DmTaskSetName = dmTaskSetNameFct }()
|
|
| 60 |
+ |
|
| 61 |
+ if err := task.SetName("test"); err != ErrTaskSetName {
|
|
| 62 |
+ t.Fatalf("An error should have occured while runnign SetName.")
|
|
| 63 |
+ } |
|
| 64 |
+} |
|
| 65 |
+ |
|
| 66 |
+func TestTaskSetMessage(t *testing.T) {
|
|
| 67 |
+ t.Skip("FIXME: not a unit test")
|
|
| 68 |
+ task := taskCreate(t, DeviceInfo) |
|
| 69 |
+ |
|
| 70 |
+ // Test success |
|
| 71 |
+ if err := task.SetMessage("test"); err != nil {
|
|
| 72 |
+ t.Fatal(err) |
|
| 73 |
+ } |
|
| 74 |
+ |
|
| 75 |
+ // Test failure |
|
| 76 |
+ DmTaskSetMessage = dmTaskSetMessageFail |
|
| 77 |
+ defer func() { DmTaskSetMessage = dmTaskSetMessageFct }()
|
|
| 78 |
+ |
|
| 79 |
+ if err := task.SetMessage("test"); err != ErrTaskSetMessage {
|
|
| 80 |
+ t.Fatalf("An error should have occured while runnign SetMessage.")
|
|
| 81 |
+ } |
|
| 82 |
+} |
|
| 83 |
+ |
|
| 84 |
+func TestTaskSetSector(t *testing.T) {
|
|
| 85 |
+ t.Skip("FIXME: not a unit test")
|
|
| 86 |
+ task := taskCreate(t, DeviceInfo) |
|
| 87 |
+ |
|
| 88 |
+ // Test success |
|
| 89 |
+ if err := task.SetSector(128); err != nil {
|
|
| 90 |
+ t.Fatal(err) |
|
| 91 |
+ } |
|
| 92 |
+ |
|
| 93 |
+ DmTaskSetSector = dmTaskSetSectorFail |
|
| 94 |
+ defer func() { DmTaskSetSector = dmTaskSetSectorFct }()
|
|
| 95 |
+ |
|
| 96 |
+ // Test failure |
|
| 97 |
+ if err := task.SetSector(0); err != ErrTaskSetSector {
|
|
| 98 |
+ t.Fatalf("An error should have occured while running SetSector.")
|
|
| 99 |
+ } |
|
| 100 |
+} |
|
| 101 |
+ |
|
| 102 |
+func TestTaskSetCookie(t *testing.T) {
|
|
| 103 |
+ t.Skip("FIXME: not a unit test")
|
|
| 104 |
+ var ( |
|
| 105 |
+ cookie uint = 0 |
|
| 106 |
+ task = taskCreate(t, DeviceInfo) |
|
| 107 |
+ ) |
|
| 108 |
+ |
|
| 109 |
+ // Test success |
|
| 110 |
+ if err := task.SetCookie(&cookie, 0); err != nil {
|
|
| 111 |
+ t.Fatal(err) |
|
| 112 |
+ } |
|
| 113 |
+ |
|
| 114 |
+ // Test failure |
|
| 115 |
+ if err := task.SetCookie(nil, 0); err != ErrNilCookie {
|
|
| 116 |
+ t.Fatalf("An error should have occured while running SetCookie with nil cookie.")
|
|
| 117 |
+ } |
|
| 118 |
+ |
|
| 119 |
+ DmTaskSetCookie = dmTaskSetCookieFail |
|
| 120 |
+ defer func() { DmTaskSetCookie = dmTaskSetCookieFct }()
|
|
| 121 |
+ |
|
| 122 |
+ if err := task.SetCookie(&cookie, 0); err != ErrTaskSetCookie {
|
|
| 123 |
+ t.Fatalf("An error should have occured while running SetCookie.")
|
|
| 124 |
+ } |
|
| 125 |
+} |
|
| 126 |
+ |
|
| 127 |
+func TestTaskSetAddNode(t *testing.T) {
|
|
| 128 |
+ t.Skip("FIXME: not a unit test")
|
|
| 129 |
+ task := taskCreate(t, DeviceInfo) |
|
| 130 |
+ |
|
| 131 |
+ // Test success |
|
| 132 |
+ if err := task.SetAddNode(0); err != nil {
|
|
| 133 |
+ t.Fatal(err) |
|
| 134 |
+ } |
|
| 135 |
+ |
|
| 136 |
+ // Test failure |
|
| 137 |
+ if err := task.SetAddNode(-1); err != ErrInvalidAddNode {
|
|
| 138 |
+ t.Fatalf("An error should have occured running SetAddNode with wrong node.")
|
|
| 139 |
+ } |
|
| 140 |
+ |
|
| 141 |
+ DmTaskSetAddNode = dmTaskSetAddNodeFail |
|
| 142 |
+ defer func() { DmTaskSetAddNode = dmTaskSetAddNodeFct }()
|
|
| 143 |
+ |
|
| 144 |
+ if err := task.SetAddNode(0); err != ErrTaskSetAddNode {
|
|
| 145 |
+ t.Fatalf("An error should have occured running SetAddNode.")
|
|
| 146 |
+ } |
|
| 147 |
+} |
|
| 148 |
+ |
|
| 149 |
+func TestTaskSetRo(t *testing.T) {
|
|
| 150 |
+ t.Skip("FIXME: not a unit test")
|
|
| 151 |
+ task := taskCreate(t, DeviceInfo) |
|
| 152 |
+ |
|
| 153 |
+ // Test success |
|
| 154 |
+ if err := task.SetRo(); err != nil {
|
|
| 155 |
+ t.Fatal(err) |
|
| 156 |
+ } |
|
| 157 |
+ |
|
| 158 |
+ // Test failure |
|
| 159 |
+ DmTaskSetRo = dmTaskSetRoFail |
|
| 160 |
+ defer func() { DmTaskSetRo = dmTaskSetRoFct }()
|
|
| 161 |
+ |
|
| 162 |
+ if err := task.SetRo(); err != ErrTaskSetRo {
|
|
| 163 |
+ t.Fatalf("An error should have occured running SetRo.")
|
|
| 164 |
+ } |
|
| 165 |
+} |
|
| 166 |
+ |
|
| 167 |
+func TestTaskAddTarget(t *testing.T) {
|
|
| 168 |
+ t.Skip("FIXME: not a unit test")
|
|
| 169 |
+ task := taskCreate(t, DeviceInfo) |
|
| 170 |
+ |
|
| 171 |
+ // Test success |
|
| 172 |
+ if err := task.AddTarget(0, 128, "thinp", ""); err != nil {
|
|
| 173 |
+ t.Fatal(err) |
|
| 174 |
+ } |
|
| 175 |
+ |
|
| 176 |
+ // Test failure |
|
| 177 |
+ DmTaskAddTarget = dmTaskAddTargetFail |
|
| 178 |
+ defer func() { DmTaskAddTarget = dmTaskAddTargetFct }()
|
|
| 179 |
+ |
|
| 180 |
+ if err := task.AddTarget(0, 128, "thinp", ""); err != ErrTaskAddTarget {
|
|
| 181 |
+ t.Fatalf("An error should have occured running AddTarget.")
|
|
| 182 |
+ } |
|
| 183 |
+} |
|
| 184 |
+ |
|
| 185 |
+// func TestTaskGetInfo(t *testing.T) {
|
|
| 186 |
+// task := taskCreate(t, DeviceInfo) |
|
| 187 |
+ |
|
| 188 |
+// // Test success |
|
| 189 |
+// if _, err := task.GetInfo(); err != nil {
|
|
| 190 |
+// t.Fatal(err) |
|
| 191 |
+// } |
|
| 192 |
+ |
|
| 193 |
+// // Test failure |
|
| 194 |
+// DmTaskGetInfo = dmTaskGetInfoFail |
|
| 195 |
+// defer func() { DmTaskGetInfo = dmTaskGetInfoFct }()
|
|
| 196 |
+ |
|
| 197 |
+// if _, err := task.GetInfo(); err != ErrTaskGetInfo {
|
|
| 198 |
+// t.Fatalf("An error should have occured running GetInfo.")
|
|
| 199 |
+// } |
|
| 200 |
+// } |
|
| 201 |
+ |
|
| 202 |
+// func TestTaskGetNextTarget(t *testing.T) {
|
|
| 203 |
+// task := taskCreate(t, DeviceInfo) |
|
| 204 |
+ |
|
| 205 |
+// if next, _, _, _, _ := task.GetNextTarget(0); next == 0 {
|
|
| 206 |
+// t.Fatalf("The next target should not be 0.")
|
|
| 207 |
+// } |
|
| 208 |
+// } |
|
| 209 |
+ |
|
| 210 |
+/// Utils |
|
| 211 |
+func taskCreate(t *testing.T, taskType TaskType) *Task {
|
|
| 212 |
+ task := TaskCreate(taskType) |
|
| 213 |
+ if task == nil {
|
|
| 214 |
+ t.Fatalf("Error creating task")
|
|
| 215 |
+ } |
|
| 216 |
+ return task |
|
| 217 |
+} |
|
| 218 |
+ |
|
| 219 |
+/// Failure function replacement |
|
| 220 |
+func dmTaskCreateFail(t int) *CDmTask {
|
|
| 221 |
+ return nil |
|
| 222 |
+} |
|
| 223 |
+ |
|
| 224 |
+func dmTaskRunFail(task *CDmTask) int {
|
|
| 225 |
+ return -1 |
|
| 226 |
+} |
|
| 227 |
+ |
|
| 228 |
+func dmTaskSetNameFail(task *CDmTask, name string) int {
|
|
| 229 |
+ return -1 |
|
| 230 |
+} |
|
| 231 |
+ |
|
| 232 |
+func dmTaskSetMessageFail(task *CDmTask, message string) int {
|
|
| 233 |
+ return -1 |
|
| 234 |
+} |
|
| 235 |
+ |
|
| 236 |
+func dmTaskSetSectorFail(task *CDmTask, sector uint64) int {
|
|
| 237 |
+ return -1 |
|
| 238 |
+} |
|
| 239 |
+ |
|
| 240 |
+func dmTaskSetCookieFail(task *CDmTask, cookie *uint, flags uint16) int {
|
|
| 241 |
+ return -1 |
|
| 242 |
+} |
|
| 243 |
+ |
|
| 244 |
+func dmTaskSetAddNodeFail(task *CDmTask, addNode AddNodeType) int {
|
|
| 245 |
+ return -1 |
|
| 246 |
+} |
|
| 247 |
+ |
|
| 248 |
+func dmTaskSetRoFail(task *CDmTask) int {
|
|
| 249 |
+ return -1 |
|
| 250 |
+} |
|
| 251 |
+ |
|
| 252 |
+func dmTaskAddTargetFail(task *CDmTask, |
|
| 253 |
+ start, size uint64, ttype, params string) int {
|
|
| 254 |
+ return -1 |
|
| 255 |
+} |
|
| 256 |
+ |
|
| 257 |
+func dmTaskGetInfoFail(task *CDmTask, info *Info) int {
|
|
| 258 |
+ return -1 |
|
| 259 |
+} |
|
| 260 |
+ |
|
| 261 |
+func dmGetNextTargetFail(task *CDmTask, next uintptr, start, length *uint64, |
|
| 262 |
+ target, params *string) uintptr {
|
|
| 263 |
+ return 0 |
|
| 264 |
+} |
|
| 265 |
+ |
|
| 266 |
+func dmAttachLoopDeviceFail(filename string, fd *int) string {
|
|
| 267 |
+ return "" |
|
| 268 |
+} |
|
| 269 |
+ |
|
| 270 |
+func sysGetBlockSizeFail(fd uintptr, size *uint64) sysErrno {
|
|
| 271 |
+ return 1 |
|
| 272 |
+} |
|
| 273 |
+ |
|
| 274 |
+func dmUdevWaitFail(cookie uint) int {
|
|
| 275 |
+ return -1 |
|
| 276 |
+} |
|
| 277 |
+ |
|
| 278 |
+func dmSetDevDirFail(dir string) int {
|
|
| 279 |
+ return -1 |
|
| 280 |
+} |
|
| 281 |
+ |
|
| 282 |
+func dmGetLibraryVersionFail(version *string) int {
|
|
| 283 |
+ return -1 |
|
| 284 |
+} |
| 0 | 285 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,340 @@ |
| 0 |
+package devmapper |
|
| 1 |
+ |
|
| 2 |
+/* |
|
| 3 |
+#cgo LDFLAGS: -L. -ldevmapper |
|
| 4 |
+#include <stdio.h> |
|
| 5 |
+#include <stdlib.h> |
|
| 6 |
+#include <unistd.h> |
|
| 7 |
+#include <libdevmapper.h> |
|
| 8 |
+#include <linux/loop.h> |
|
| 9 |
+#include <sys/types.h> |
|
| 10 |
+#include <sys/stat.h> |
|
| 11 |
+#include <fcntl.h> |
|
| 12 |
+#include <sys/ioctl.h> |
|
| 13 |
+#include <linux/fs.h> |
|
| 14 |
+#include <errno.h> |
|
| 15 |
+ |
|
| 16 |
+#ifndef LOOP_CTL_GET_FREE |
|
| 17 |
+#define LOOP_CTL_GET_FREE 0x4C82 |
|
| 18 |
+#endif |
|
| 19 |
+ |
|
| 20 |
+// FIXME: this could easily be rewritten in go |
|
| 21 |
+char* attach_loop_device(const char *filename, int *loop_fd_out) |
|
| 22 |
+{
|
|
| 23 |
+ struct loop_info64 loopinfo = {0};
|
|
| 24 |
+ struct stat st; |
|
| 25 |
+ char buf[64]; |
|
| 26 |
+ int i, loop_fd, fd, start_index; |
|
| 27 |
+ char* loopname; |
|
| 28 |
+ |
|
| 29 |
+ |
|
| 30 |
+ *loop_fd_out = -1; |
|
| 31 |
+ |
|
| 32 |
+ start_index = 0; |
|
| 33 |
+ fd = open("/dev/loop-control", O_RDONLY);
|
|
| 34 |
+ if (fd >= 0) {
|
|
| 35 |
+ start_index = ioctl(fd, LOOP_CTL_GET_FREE); |
|
| 36 |
+ close(fd); |
|
| 37 |
+ |
|
| 38 |
+ if (start_index < 0) |
|
| 39 |
+ start_index = 0; |
|
| 40 |
+ } |
|
| 41 |
+ |
|
| 42 |
+ fd = open(filename, O_RDWR); |
|
| 43 |
+ if (fd < 0) {
|
|
| 44 |
+ perror("open");
|
|
| 45 |
+ return NULL; |
|
| 46 |
+ } |
|
| 47 |
+ |
|
| 48 |
+ loop_fd = -1; |
|
| 49 |
+ for (i = start_index ; loop_fd < 0 ; i++ ) {
|
|
| 50 |
+ if (sprintf(buf, "/dev/loop%d", i) < 0) {
|
|
| 51 |
+ close(fd); |
|
| 52 |
+ return NULL; |
|
| 53 |
+ } |
|
| 54 |
+ |
|
| 55 |
+ if (stat(buf, &st)) {
|
|
| 56 |
+ if (!S_ISBLK(st.st_mode)) {
|
|
| 57 |
+ fprintf(stderr, "[error] Loopback device %s is not a block device.\n", buf); |
|
| 58 |
+ } else if (errno == ENOENT) {
|
|
| 59 |
+ fprintf(stderr, "[error] There are no more loopback device available.\n"); |
|
| 60 |
+ } else {
|
|
| 61 |
+ fprintf(stderr, "[error] Unkown error trying to stat the loopback device %s (errno: %d).\n", buf, errno); |
|
| 62 |
+ } |
|
| 63 |
+ close(fd); |
|
| 64 |
+ return NULL; |
|
| 65 |
+ } |
|
| 66 |
+ |
|
| 67 |
+ loop_fd = open(buf, O_RDWR); |
|
| 68 |
+ if (loop_fd < 0 && errno == ENOENT) {
|
|
| 69 |
+ fprintf(stderr, "[error] The loopback device %s does not exists.\n", buf); |
|
| 70 |
+ close(fd); |
|
| 71 |
+ return NULL; |
|
| 72 |
+ } else if (loop_fd < 0) {
|
|
| 73 |
+ fprintf(stderr, "[error] Unkown error openning the loopback device %s. (errno: %d)\n", buf, errno); |
|
| 74 |
+ continue; |
|
| 75 |
+ } |
|
| 76 |
+ |
|
| 77 |
+ if (ioctl(loop_fd, LOOP_SET_FD, (void *)(size_t)fd) < 0) {
|
|
| 78 |
+ int errsv = errno; |
|
| 79 |
+ close(loop_fd); |
|
| 80 |
+ loop_fd = -1; |
|
| 81 |
+ if (errsv != EBUSY) {
|
|
| 82 |
+ close(fd); |
|
| 83 |
+ fprintf(stderr, "cannot set up loopback device %s: %s", buf, strerror(errsv)); |
|
| 84 |
+ return NULL; |
|
| 85 |
+ } |
|
| 86 |
+ continue; |
|
| 87 |
+ } |
|
| 88 |
+ |
|
| 89 |
+ close(fd); |
|
| 90 |
+ |
|
| 91 |
+ strncpy((char*)loopinfo.lo_file_name, buf, LO_NAME_SIZE); |
|
| 92 |
+ loopinfo.lo_offset = 0; |
|
| 93 |
+ loopinfo.lo_flags = LO_FLAGS_AUTOCLEAR; |
|
| 94 |
+ |
|
| 95 |
+ if (ioctl(loop_fd, LOOP_SET_STATUS64, &loopinfo) < 0) {
|
|
| 96 |
+ perror("ioctl LOOP_SET_STATUS64");
|
|
| 97 |
+ if (ioctl(loop_fd, LOOP_CLR_FD, 0) < 0) {
|
|
| 98 |
+ perror("ioctl LOOP_CLR_FD");
|
|
| 99 |
+ } |
|
| 100 |
+ close(loop_fd); |
|
| 101 |
+ fprintf (stderr, "cannot set up loopback device info"); |
|
| 102 |
+ return (NULL); |
|
| 103 |
+ } |
|
| 104 |
+ |
|
| 105 |
+ loopname = strdup(buf); |
|
| 106 |
+ if (loopname == NULL) {
|
|
| 107 |
+ close(loop_fd); |
|
| 108 |
+ return (NULL); |
|
| 109 |
+ } |
|
| 110 |
+ |
|
| 111 |
+ *loop_fd_out = loop_fd; |
|
| 112 |
+ return (loopname); |
|
| 113 |
+ } |
|
| 114 |
+ |
|
| 115 |
+ return (NULL); |
|
| 116 |
+} |
|
| 117 |
+ |
|
| 118 |
+extern void DevmapperLogCallback(int level, char *file, int line, int dm_errno_or_class, char *str); |
|
| 119 |
+ |
|
| 120 |
+static void log_cb(int level, const char *file, int line, |
|
| 121 |
+ int dm_errno_or_class, const char *f, ...) |
|
| 122 |
+{
|
|
| 123 |
+ char buffer[256]; |
|
| 124 |
+ va_list ap; |
|
| 125 |
+ |
|
| 126 |
+ va_start(ap, f); |
|
| 127 |
+ vsnprintf(buffer, 256, f, ap); |
|
| 128 |
+ va_end(ap); |
|
| 129 |
+ |
|
| 130 |
+ DevmapperLogCallback(level, (char *)file, line, dm_errno_or_class, buffer); |
|
| 131 |
+} |
|
| 132 |
+ |
|
| 133 |
+static void log_with_errno_init() |
|
| 134 |
+{
|
|
| 135 |
+ dm_log_with_errno_init(log_cb); |
|
| 136 |
+} |
|
| 137 |
+ |
|
| 138 |
+*/ |
|
| 139 |
+import "C" |
|
| 140 |
+ |
|
| 141 |
+import ( |
|
| 142 |
+ "unsafe" |
|
| 143 |
+) |
|
| 144 |
+ |
|
| 145 |
+type ( |
|
| 146 |
+ CDmTask C.struct_dm_task |
|
| 147 |
+) |
|
| 148 |
+ |
|
| 149 |
+var ( |
|
| 150 |
+ DmAttachLoopDevice = dmAttachLoopDeviceFct |
|
| 151 |
+ DmGetBlockSize = dmGetBlockSizeFct |
|
| 152 |
+ DmGetLibraryVersion = dmGetLibraryVersionFct |
|
| 153 |
+ DmGetNextTarget = dmGetNextTargetFct |
|
| 154 |
+ DmLogInitVerbose = dmLogInitVerboseFct |
|
| 155 |
+ DmSetDevDir = dmSetDevDirFct |
|
| 156 |
+ DmTaskAddTarget = dmTaskAddTargetFct |
|
| 157 |
+ DmTaskCreate = dmTaskCreateFct |
|
| 158 |
+ DmTaskDestroy = dmTaskDestroyFct |
|
| 159 |
+ DmTaskGetInfo = dmTaskGetInfoFct |
|
| 160 |
+ DmTaskRun = dmTaskRunFct |
|
| 161 |
+ DmTaskSetAddNode = dmTaskSetAddNodeFct |
|
| 162 |
+ DmTaskSetCookie = dmTaskSetCookieFct |
|
| 163 |
+ DmTaskSetMessage = dmTaskSetMessageFct |
|
| 164 |
+ DmTaskSetName = dmTaskSetNameFct |
|
| 165 |
+ DmTaskSetRo = dmTaskSetRoFct |
|
| 166 |
+ DmTaskSetSector = dmTaskSetSectorFct |
|
| 167 |
+ DmUdevWait = dmUdevWaitFct |
|
| 168 |
+ GetBlockSize = getBlockSizeFct |
|
| 169 |
+ LogWithErrnoInit = logWithErrnoInitFct |
|
| 170 |
+ DmGetLoopbackBackingFile = dmGetLoopbackBackingFileFct |
|
| 171 |
+ DmLoopbackSetCapacity = dmLoopbackSetCapacityFct |
|
| 172 |
+) |
|
| 173 |
+ |
|
| 174 |
+func free(p *C.char) {
|
|
| 175 |
+ C.free(unsafe.Pointer(p)) |
|
| 176 |
+} |
|
| 177 |
+ |
|
| 178 |
+func dmTaskDestroyFct(task *CDmTask) {
|
|
| 179 |
+ C.dm_task_destroy((*C.struct_dm_task)(task)) |
|
| 180 |
+} |
|
| 181 |
+ |
|
| 182 |
+func dmTaskCreateFct(taskType int) *CDmTask {
|
|
| 183 |
+ return (*CDmTask)(C.dm_task_create(C.int(taskType))) |
|
| 184 |
+} |
|
| 185 |
+ |
|
| 186 |
+func dmTaskRunFct(task *CDmTask) int {
|
|
| 187 |
+ return int(C.dm_task_run((*C.struct_dm_task)(task))) |
|
| 188 |
+} |
|
| 189 |
+ |
|
| 190 |
+func dmTaskSetNameFct(task *CDmTask, name string) int {
|
|
| 191 |
+ Cname := C.CString(name) |
|
| 192 |
+ defer free(Cname) |
|
| 193 |
+ |
|
| 194 |
+ return int(C.dm_task_set_name((*C.struct_dm_task)(task), |
|
| 195 |
+ Cname)) |
|
| 196 |
+} |
|
| 197 |
+ |
|
| 198 |
+func dmTaskSetMessageFct(task *CDmTask, message string) int {
|
|
| 199 |
+ Cmessage := C.CString(message) |
|
| 200 |
+ defer free(Cmessage) |
|
| 201 |
+ |
|
| 202 |
+ return int(C.dm_task_set_message((*C.struct_dm_task)(task), |
|
| 203 |
+ Cmessage)) |
|
| 204 |
+} |
|
| 205 |
+ |
|
| 206 |
+func dmTaskSetSectorFct(task *CDmTask, sector uint64) int {
|
|
| 207 |
+ return int(C.dm_task_set_sector((*C.struct_dm_task)(task), |
|
| 208 |
+ C.uint64_t(sector))) |
|
| 209 |
+} |
|
| 210 |
+ |
|
| 211 |
+func dmTaskSetCookieFct(task *CDmTask, cookie *uint, flags uint16) int {
|
|
| 212 |
+ cCookie := C.uint32_t(*cookie) |
|
| 213 |
+ defer func() {
|
|
| 214 |
+ *cookie = uint(cCookie) |
|
| 215 |
+ }() |
|
| 216 |
+ return int(C.dm_task_set_cookie((*C.struct_dm_task)(task), &cCookie, |
|
| 217 |
+ C.uint16_t(flags))) |
|
| 218 |
+} |
|
| 219 |
+ |
|
| 220 |
+func dmTaskSetAddNodeFct(task *CDmTask, addNode AddNodeType) int {
|
|
| 221 |
+ return int(C.dm_task_set_add_node((*C.struct_dm_task)(task), |
|
| 222 |
+ C.dm_add_node_t(addNode))) |
|
| 223 |
+} |
|
| 224 |
+ |
|
| 225 |
+func dmTaskSetRoFct(task *CDmTask) int {
|
|
| 226 |
+ return int(C.dm_task_set_ro((*C.struct_dm_task)(task))) |
|
| 227 |
+} |
|
| 228 |
+ |
|
| 229 |
+func dmTaskAddTargetFct(task *CDmTask, |
|
| 230 |
+ start, size uint64, ttype, params string) int {
|
|
| 231 |
+ |
|
| 232 |
+ Cttype := C.CString(ttype) |
|
| 233 |
+ defer free(Cttype) |
|
| 234 |
+ |
|
| 235 |
+ Cparams := C.CString(params) |
|
| 236 |
+ defer free(Cparams) |
|
| 237 |
+ |
|
| 238 |
+ return int(C.dm_task_add_target((*C.struct_dm_task)(task), |
|
| 239 |
+ C.uint64_t(start), C.uint64_t(size), Cttype, Cparams)) |
|
| 240 |
+} |
|
| 241 |
+ |
|
| 242 |
+func dmGetLoopbackBackingFileFct(fd uintptr) (uint64, uint64, sysErrno) {
|
|
| 243 |
+ var lo64 C.struct_loop_info64 |
|
| 244 |
+ _, _, err := sysSyscall(sysSysIoctl, fd, C.LOOP_GET_STATUS64, |
|
| 245 |
+ uintptr(unsafe.Pointer(&lo64))) |
|
| 246 |
+ return uint64(lo64.lo_device), uint64(lo64.lo_inode), sysErrno(err) |
|
| 247 |
+} |
|
| 248 |
+ |
|
| 249 |
+func dmLoopbackSetCapacityFct(fd uintptr) sysErrno {
|
|
| 250 |
+ _, _, err := sysSyscall(sysSysIoctl, fd, C.LOOP_SET_CAPACITY, 0) |
|
| 251 |
+ return sysErrno(err) |
|
| 252 |
+} |
|
| 253 |
+ |
|
| 254 |
+func dmGetBlockSizeFct(fd uintptr) (int64, sysErrno) {
|
|
| 255 |
+ var size int64 |
|
| 256 |
+ _, _, err := sysSyscall(sysSysIoctl, fd, C.BLKGETSIZE64, uintptr(unsafe.Pointer(&size))) |
|
| 257 |
+ return size, sysErrno(err) |
|
| 258 |
+} |
|
| 259 |
+ |
|
| 260 |
+func dmTaskGetInfoFct(task *CDmTask, info *Info) int {
|
|
| 261 |
+ Cinfo := C.struct_dm_info{}
|
|
| 262 |
+ defer func() {
|
|
| 263 |
+ info.Exists = int(Cinfo.exists) |
|
| 264 |
+ info.Suspended = int(Cinfo.suspended) |
|
| 265 |
+ info.LiveTable = int(Cinfo.live_table) |
|
| 266 |
+ info.InactiveTable = int(Cinfo.inactive_table) |
|
| 267 |
+ info.OpenCount = int32(Cinfo.open_count) |
|
| 268 |
+ info.EventNr = uint32(Cinfo.event_nr) |
|
| 269 |
+ info.Major = uint32(Cinfo.major) |
|
| 270 |
+ info.Minor = uint32(Cinfo.minor) |
|
| 271 |
+ info.ReadOnly = int(Cinfo.read_only) |
|
| 272 |
+ info.TargetCount = int32(Cinfo.target_count) |
|
| 273 |
+ }() |
|
| 274 |
+ return int(C.dm_task_get_info((*C.struct_dm_task)(task), &Cinfo)) |
|
| 275 |
+} |
|
| 276 |
+ |
|
| 277 |
+func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64, target, params *string) uintptr {
|
|
| 278 |
+ var ( |
|
| 279 |
+ Cstart, Clength C.uint64_t |
|
| 280 |
+ CtargetType, Cparams *C.char |
|
| 281 |
+ ) |
|
| 282 |
+ defer func() {
|
|
| 283 |
+ *start = uint64(Cstart) |
|
| 284 |
+ *length = uint64(Clength) |
|
| 285 |
+ *target = C.GoString(CtargetType) |
|
| 286 |
+ *params = C.GoString(Cparams) |
|
| 287 |
+ }() |
|
| 288 |
+ |
|
| 289 |
+ nextp := C.dm_get_next_target((*C.struct_dm_task)(task), |
|
| 290 |
+ unsafe.Pointer(next), &Cstart, &Clength, &CtargetType, &Cparams) |
|
| 291 |
+ return uintptr(nextp) |
|
| 292 |
+} |
|
| 293 |
+ |
|
| 294 |
+func dmAttachLoopDeviceFct(filename string, fd *int) string {
|
|
| 295 |
+ cFilename := C.CString(filename) |
|
| 296 |
+ defer free(cFilename) |
|
| 297 |
+ |
|
| 298 |
+ var cFd C.int |
|
| 299 |
+ defer func() {
|
|
| 300 |
+ *fd = int(cFd) |
|
| 301 |
+ }() |
|
| 302 |
+ |
|
| 303 |
+ ret := C.attach_loop_device(cFilename, &cFd) |
|
| 304 |
+ defer free(ret) |
|
| 305 |
+ return C.GoString(ret) |
|
| 306 |
+} |
|
| 307 |
+ |
|
| 308 |
+func getBlockSizeFct(fd uintptr, size *uint64) sysErrno {
|
|
| 309 |
+ _, _, err := sysSyscall(sysSysIoctl, fd, C.BLKGETSIZE64, uintptr(unsafe.Pointer(&size))) |
|
| 310 |
+ return sysErrno(err) |
|
| 311 |
+} |
|
| 312 |
+ |
|
| 313 |
+func dmUdevWaitFct(cookie uint) int {
|
|
| 314 |
+ return int(C.dm_udev_wait(C.uint32_t(cookie))) |
|
| 315 |
+} |
|
| 316 |
+ |
|
| 317 |
+func dmLogInitVerboseFct(level int) {
|
|
| 318 |
+ C.dm_log_init_verbose(C.int(level)) |
|
| 319 |
+} |
|
| 320 |
+ |
|
| 321 |
+func logWithErrnoInitFct() {
|
|
| 322 |
+ C.log_with_errno_init() |
|
| 323 |
+} |
|
| 324 |
+ |
|
| 325 |
+func dmSetDevDirFct(dir string) int {
|
|
| 326 |
+ Cdir := C.CString(dir) |
|
| 327 |
+ defer free(Cdir) |
|
| 328 |
+ |
|
| 329 |
+ return int(C.dm_set_dev_dir(Cdir)) |
|
| 330 |
+} |
|
| 331 |
+ |
|
| 332 |
+func dmGetLibraryVersionFct(version *string) int {
|
|
| 333 |
+ buffer := C.CString(string(make([]byte, 128))) |
|
| 334 |
+ defer free(buffer) |
|
| 335 |
+ defer func() {
|
|
| 336 |
+ *version = C.GoString(buffer) |
|
| 337 |
+ }() |
|
| 338 |
+ return int(C.dm_get_library_version(buffer, 128)) |
|
| 339 |
+} |
| 0 | 340 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,126 @@ |
| 0 |
+package devmapper |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "github.com/dotcloud/docker/graphdriver" |
|
| 5 |
+ "io/ioutil" |
|
| 6 |
+ "path" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+func init() {
|
|
| 10 |
+ graphdriver.Register("devicemapper", Init)
|
|
| 11 |
+} |
|
| 12 |
+ |
|
| 13 |
+// Placeholder interfaces, to be replaced |
|
| 14 |
+// at integration. |
|
| 15 |
+ |
|
| 16 |
+// End of placeholder interfaces. |
|
| 17 |
+ |
|
| 18 |
+type Driver struct {
|
|
| 19 |
+ *DeviceSet |
|
| 20 |
+ home string |
|
| 21 |
+} |
|
| 22 |
+ |
|
| 23 |
+var Init = func(home string) (graphdriver.Driver, error) {
|
|
| 24 |
+ deviceSet, err := NewDeviceSet(home, true) |
|
| 25 |
+ if err != nil {
|
|
| 26 |
+ return nil, err |
|
| 27 |
+ } |
|
| 28 |
+ d := &Driver{
|
|
| 29 |
+ DeviceSet: deviceSet, |
|
| 30 |
+ home: home, |
|
| 31 |
+ } |
|
| 32 |
+ return d, nil |
|
| 33 |
+} |
|
| 34 |
+ |
|
| 35 |
+func (d *Driver) String() string {
|
|
| 36 |
+ return "devicemapper" |
|
| 37 |
+} |
|
| 38 |
+ |
|
| 39 |
+func (d *Driver) Status() [][2]string {
|
|
| 40 |
+ s := d.DeviceSet.Status() |
|
| 41 |
+ |
|
| 42 |
+ status := [][2]string{
|
|
| 43 |
+ {"Pool Name", s.PoolName},
|
|
| 44 |
+ {"Data file", s.DataLoopback},
|
|
| 45 |
+ {"Metadata file", s.MetadataLoopback},
|
|
| 46 |
+ {"Data Space Used", fmt.Sprintf("%.1f Mb", float64(s.Data.Used)/(1024*1024))},
|
|
| 47 |
+ {"Data Space Total", fmt.Sprintf("%.1f Mb", float64(s.Data.Total)/(1024*1024))},
|
|
| 48 |
+ {"Metadata Space Used", fmt.Sprintf("%.1f Mb", float64(s.Metadata.Used)/(1024*1024))},
|
|
| 49 |
+ {"Metadata Space Total", fmt.Sprintf("%.1f Mb", float64(s.Metadata.Total)/(1024*1024))},
|
|
| 50 |
+ } |
|
| 51 |
+ return status |
|
| 52 |
+} |
|
| 53 |
+ |
|
| 54 |
+func (d *Driver) Cleanup() error {
|
|
| 55 |
+ return d.DeviceSet.Shutdown() |
|
| 56 |
+} |
|
| 57 |
+ |
|
| 58 |
+func (d *Driver) Create(id, parent string) error {
|
|
| 59 |
+ if err := d.DeviceSet.AddDevice(id, parent); err != nil {
|
|
| 60 |
+ return err |
|
| 61 |
+ } |
|
| 62 |
+ |
|
| 63 |
+ mp := path.Join(d.home, "mnt", id) |
|
| 64 |
+ if err := d.mount(id, mp); err != nil {
|
|
| 65 |
+ return err |
|
| 66 |
+ } |
|
| 67 |
+ |
|
| 68 |
+ if err := osMkdirAll(path.Join(mp, "rootfs"), 0755); err != nil && !osIsExist(err) {
|
|
| 69 |
+ return err |
|
| 70 |
+ } |
|
| 71 |
+ |
|
| 72 |
+ // Create an "id" file with the container/image id in it to help reconscruct this in case |
|
| 73 |
+ // of later problems |
|
| 74 |
+ if err := ioutil.WriteFile(path.Join(mp, "id"), []byte(id), 0600); err != nil {
|
|
| 75 |
+ return err |
|
| 76 |
+ } |
|
| 77 |
+ |
|
| 78 |
+ return nil |
|
| 79 |
+} |
|
| 80 |
+ |
|
| 81 |
+func (d *Driver) Remove(id string) error {
|
|
| 82 |
+ mp := path.Join(d.home, "mnt", id) |
|
| 83 |
+ if err := d.unmount(id, mp); err != nil {
|
|
| 84 |
+ return err |
|
| 85 |
+ } |
|
| 86 |
+ return d.DeviceSet.RemoveDevice(id) |
|
| 87 |
+} |
|
| 88 |
+ |
|
| 89 |
+func (d *Driver) Get(id string) (string, error) {
|
|
| 90 |
+ mp := path.Join(d.home, "mnt", id) |
|
| 91 |
+ if err := d.mount(id, mp); err != nil {
|
|
| 92 |
+ return "", err |
|
| 93 |
+ } |
|
| 94 |
+ return path.Join(mp, "rootfs"), nil |
|
| 95 |
+} |
|
| 96 |
+ |
|
| 97 |
+func (d *Driver) mount(id, mountPoint string) error {
|
|
| 98 |
+ // Create the target directories if they don't exist |
|
| 99 |
+ if err := osMkdirAll(mountPoint, 0755); err != nil && !osIsExist(err) {
|
|
| 100 |
+ return err |
|
| 101 |
+ } |
|
| 102 |
+ // If mountpoint is already mounted, do nothing |
|
| 103 |
+ if mounted, err := Mounted(mountPoint); err != nil {
|
|
| 104 |
+ return fmt.Errorf("Error checking mountpoint: %s", err)
|
|
| 105 |
+ } else if mounted {
|
|
| 106 |
+ return nil |
|
| 107 |
+ } |
|
| 108 |
+ // Mount the device |
|
| 109 |
+ return d.DeviceSet.MountDevice(id, mountPoint, false) |
|
| 110 |
+} |
|
| 111 |
+ |
|
| 112 |
+func (d *Driver) unmount(id, mountPoint string) error {
|
|
| 113 |
+ // If mountpoint is not mounted, do nothing |
|
| 114 |
+ if mounted, err := Mounted(mountPoint); err != nil {
|
|
| 115 |
+ return fmt.Errorf("Error checking mountpoint: %s", err)
|
|
| 116 |
+ } else if !mounted {
|
|
| 117 |
+ return nil |
|
| 118 |
+ } |
|
| 119 |
+ // Unmount the device |
|
| 120 |
+ return d.DeviceSet.UnmountDevice(id, mountPoint, true) |
|
| 121 |
+} |
|
| 122 |
+ |
|
| 123 |
+func (d *Driver) Exists(id string) bool {
|
|
| 124 |
+ return d.Devices[id] != nil |
|
| 125 |
+} |
| 0 | 126 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,872 @@ |
| 0 |
+package devmapper |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "github.com/dotcloud/docker/graphdriver" |
|
| 5 |
+ "io/ioutil" |
|
| 6 |
+ "path" |
|
| 7 |
+ "runtime" |
|
| 8 |
+ "strings" |
|
| 9 |
+ "syscall" |
|
| 10 |
+ "testing" |
|
| 11 |
+) |
|
| 12 |
+ |
|
| 13 |
+func init() {
|
|
| 14 |
+ // Reduce the size the the base fs and loopback for the tests |
|
| 15 |
+ DefaultDataLoopbackSize = 300 * 1024 * 1024 |
|
| 16 |
+ DefaultMetaDataLoopbackSize = 200 * 1024 * 1024 |
|
| 17 |
+ DefaultBaseFsSize = 300 * 1024 * 1024 |
|
| 18 |
+} |
|
| 19 |
+ |
|
| 20 |
+// denyAllDevmapper mocks all calls to libdevmapper in the unit tests, and denies them by default |
|
| 21 |
+func denyAllDevmapper() {
|
|
| 22 |
+ // Hijack all calls to libdevmapper with default panics. |
|
| 23 |
+ // Authorized calls are selectively hijacked in each tests. |
|
| 24 |
+ DmTaskCreate = func(t int) *CDmTask {
|
|
| 25 |
+ panic("DmTaskCreate: this method should not be called here")
|
|
| 26 |
+ } |
|
| 27 |
+ DmTaskRun = func(task *CDmTask) int {
|
|
| 28 |
+ panic("DmTaskRun: this method should not be called here")
|
|
| 29 |
+ } |
|
| 30 |
+ DmTaskSetName = func(task *CDmTask, name string) int {
|
|
| 31 |
+ panic("DmTaskSetName: this method should not be called here")
|
|
| 32 |
+ } |
|
| 33 |
+ DmTaskSetMessage = func(task *CDmTask, message string) int {
|
|
| 34 |
+ panic("DmTaskSetMessage: this method should not be called here")
|
|
| 35 |
+ } |
|
| 36 |
+ DmTaskSetSector = func(task *CDmTask, sector uint64) int {
|
|
| 37 |
+ panic("DmTaskSetSector: this method should not be called here")
|
|
| 38 |
+ } |
|
| 39 |
+ DmTaskSetCookie = func(task *CDmTask, cookie *uint, flags uint16) int {
|
|
| 40 |
+ panic("DmTaskSetCookie: this method should not be called here")
|
|
| 41 |
+ } |
|
| 42 |
+ DmTaskSetAddNode = func(task *CDmTask, addNode AddNodeType) int {
|
|
| 43 |
+ panic("DmTaskSetAddNode: this method should not be called here")
|
|
| 44 |
+ } |
|
| 45 |
+ DmTaskSetRo = func(task *CDmTask) int {
|
|
| 46 |
+ panic("DmTaskSetRo: this method should not be called here")
|
|
| 47 |
+ } |
|
| 48 |
+ DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int {
|
|
| 49 |
+ panic("DmTaskAddTarget: this method should not be called here")
|
|
| 50 |
+ } |
|
| 51 |
+ DmTaskGetInfo = func(task *CDmTask, info *Info) int {
|
|
| 52 |
+ panic("DmTaskGetInfo: this method should not be called here")
|
|
| 53 |
+ } |
|
| 54 |
+ DmGetNextTarget = func(task *CDmTask, next uintptr, start, length *uint64, target, params *string) uintptr {
|
|
| 55 |
+ panic("DmGetNextTarget: this method should not be called here")
|
|
| 56 |
+ } |
|
| 57 |
+ DmAttachLoopDevice = func(filename string, fd *int) string {
|
|
| 58 |
+ panic("DmAttachLoopDevice: this method should not be called here")
|
|
| 59 |
+ } |
|
| 60 |
+ DmGetBlockSize = func(fd uintptr) (int64, sysErrno) {
|
|
| 61 |
+ panic("DmGetBlockSize: this method should not be called here")
|
|
| 62 |
+ } |
|
| 63 |
+ DmUdevWait = func(cookie uint) int {
|
|
| 64 |
+ panic("DmUdevWait: this method should not be called here")
|
|
| 65 |
+ } |
|
| 66 |
+ DmSetDevDir = func(dir string) int {
|
|
| 67 |
+ panic("DmSetDevDir: this method should not be called here")
|
|
| 68 |
+ } |
|
| 69 |
+ DmGetLibraryVersion = func(version *string) int {
|
|
| 70 |
+ panic("DmGetLibraryVersion: this method should not be called here")
|
|
| 71 |
+ } |
|
| 72 |
+ DmLogInitVerbose = func(level int) {
|
|
| 73 |
+ panic("DmLogInitVerbose: this method should not be called here")
|
|
| 74 |
+ } |
|
| 75 |
+ DmTaskDestroy = func(task *CDmTask) {
|
|
| 76 |
+ panic("DmTaskDestroy: this method should not be called here")
|
|
| 77 |
+ } |
|
| 78 |
+ GetBlockSize = func(fd uintptr, size *uint64) sysErrno {
|
|
| 79 |
+ panic("GetBlockSize: this method should not be called here")
|
|
| 80 |
+ } |
|
| 81 |
+ LogWithErrnoInit = func() {
|
|
| 82 |
+ panic("LogWithErrnoInit: this method should not be called here")
|
|
| 83 |
+ } |
|
| 84 |
+} |
|
| 85 |
+ |
|
| 86 |
+func denyAllSyscall() {
|
|
| 87 |
+ sysMount = func(source, target, fstype string, flags uintptr, data string) (err error) {
|
|
| 88 |
+ panic("sysMount: this method should not be called here")
|
|
| 89 |
+ } |
|
| 90 |
+ sysUnmount = func(target string, flags int) (err error) {
|
|
| 91 |
+ panic("sysUnmount: this method should not be called here")
|
|
| 92 |
+ } |
|
| 93 |
+ sysCloseOnExec = func(fd int) {
|
|
| 94 |
+ panic("sysCloseOnExec: this method should not be called here")
|
|
| 95 |
+ } |
|
| 96 |
+ sysSyscall = func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
|
|
| 97 |
+ panic("sysSyscall: this method should not be called here")
|
|
| 98 |
+ } |
|
| 99 |
+ // Not a syscall, but forbidding it here anyway |
|
| 100 |
+ Mounted = func(mnt string) (bool, error) {
|
|
| 101 |
+ panic("devmapper.Mounted: this method should not be called here")
|
|
| 102 |
+ } |
|
| 103 |
+ // osOpenFile = os.OpenFile |
|
| 104 |
+ // osNewFile = os.NewFile |
|
| 105 |
+ // osCreate = os.Create |
|
| 106 |
+ // osStat = os.Stat |
|
| 107 |
+ // osIsNotExist = os.IsNotExist |
|
| 108 |
+ // osIsExist = os.IsExist |
|
| 109 |
+ // osMkdirAll = os.MkdirAll |
|
| 110 |
+ // osRemoveAll = os.RemoveAll |
|
| 111 |
+ // osRename = os.Rename |
|
| 112 |
+ // osReadlink = os.Readlink |
|
| 113 |
+ |
|
| 114 |
+ // execRun = func(name string, args ...string) error {
|
|
| 115 |
+ // return exec.Command(name, args...).Run() |
|
| 116 |
+ // } |
|
| 117 |
+} |
|
| 118 |
+ |
|
| 119 |
+func mkTestDirectory(t *testing.T) string {
|
|
| 120 |
+ dir, err := ioutil.TempDir("", "docker-test-devmapper-")
|
|
| 121 |
+ if err != nil {
|
|
| 122 |
+ t.Fatal(err) |
|
| 123 |
+ } |
|
| 124 |
+ return dir |
|
| 125 |
+} |
|
| 126 |
+ |
|
| 127 |
+func newDriver(t *testing.T) *Driver {
|
|
| 128 |
+ home := mkTestDirectory(t) |
|
| 129 |
+ d, err := Init(home) |
|
| 130 |
+ if err != nil {
|
|
| 131 |
+ t.Fatal(err) |
|
| 132 |
+ } |
|
| 133 |
+ return d.(*Driver) |
|
| 134 |
+} |
|
| 135 |
+ |
|
| 136 |
+func cleanup(d *Driver) {
|
|
| 137 |
+ d.Cleanup() |
|
| 138 |
+ osRemoveAll(d.home) |
|
| 139 |
+} |
|
| 140 |
+ |
|
| 141 |
+type Set map[string]bool |
|
| 142 |
+ |
|
| 143 |
+func (r Set) Assert(t *testing.T, names ...string) {
|
|
| 144 |
+ for _, key := range names {
|
|
| 145 |
+ if _, exists := r[key]; !exists {
|
|
| 146 |
+ t.Fatalf("Key not set: %s", key)
|
|
| 147 |
+ } |
|
| 148 |
+ delete(r, key) |
|
| 149 |
+ } |
|
| 150 |
+ if len(r) != 0 {
|
|
| 151 |
+ t.Fatalf("Unexpected keys: %v", r)
|
|
| 152 |
+ } |
|
| 153 |
+} |
|
| 154 |
+ |
|
| 155 |
+func TestInit(t *testing.T) {
|
|
| 156 |
+ var ( |
|
| 157 |
+ calls = make(Set) |
|
| 158 |
+ devicesAttached = make(Set) |
|
| 159 |
+ taskMessages = make(Set) |
|
| 160 |
+ taskTypes = make(Set) |
|
| 161 |
+ home = mkTestDirectory(t) |
|
| 162 |
+ ) |
|
| 163 |
+ defer osRemoveAll(home) |
|
| 164 |
+ |
|
| 165 |
+ func() {
|
|
| 166 |
+ denyAllDevmapper() |
|
| 167 |
+ DmSetDevDir = func(dir string) int {
|
|
| 168 |
+ calls["DmSetDevDir"] = true |
|
| 169 |
+ expectedDir := "/dev" |
|
| 170 |
+ if dir != expectedDir {
|
|
| 171 |
+ t.Fatalf("Wrong libdevmapper call\nExpected: DmSetDevDir(%v)\nReceived: DmSetDevDir(%v)\n", expectedDir, dir)
|
|
| 172 |
+ } |
|
| 173 |
+ return 0 |
|
| 174 |
+ } |
|
| 175 |
+ LogWithErrnoInit = func() {
|
|
| 176 |
+ calls["DmLogWithErrnoInit"] = true |
|
| 177 |
+ } |
|
| 178 |
+ var task1 CDmTask |
|
| 179 |
+ DmTaskCreate = func(taskType int) *CDmTask {
|
|
| 180 |
+ calls["DmTaskCreate"] = true |
|
| 181 |
+ taskTypes[fmt.Sprintf("%d", taskType)] = true
|
|
| 182 |
+ return &task1 |
|
| 183 |
+ } |
|
| 184 |
+ DmTaskSetName = func(task *CDmTask, name string) int {
|
|
| 185 |
+ calls["DmTaskSetName"] = true |
|
| 186 |
+ expectedTask := &task1 |
|
| 187 |
+ if task != expectedTask {
|
|
| 188 |
+ t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetName(%v)\nReceived: DmTaskSetName(%v)\n", expectedTask, task)
|
|
| 189 |
+ } |
|
| 190 |
+ // FIXME: use Set.AssertRegexp() |
|
| 191 |
+ if !strings.HasPrefix(name, "docker-") && !strings.HasPrefix(name, "/dev/mapper/docker-") || |
|
| 192 |
+ !strings.HasSuffix(name, "-pool") && !strings.HasSuffix(name, "-base") {
|
|
| 193 |
+ t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetName(%v)\nReceived: DmTaskSetName(%v)\n", "docker-...-pool", name)
|
|
| 194 |
+ } |
|
| 195 |
+ return 1 |
|
| 196 |
+ } |
|
| 197 |
+ DmTaskRun = func(task *CDmTask) int {
|
|
| 198 |
+ calls["DmTaskRun"] = true |
|
| 199 |
+ expectedTask := &task1 |
|
| 200 |
+ if task != expectedTask {
|
|
| 201 |
+ t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskRun(%v)\nReceived: DmTaskRun(%v)\n", expectedTask, task)
|
|
| 202 |
+ } |
|
| 203 |
+ return 1 |
|
| 204 |
+ } |
|
| 205 |
+ DmTaskGetInfo = func(task *CDmTask, info *Info) int {
|
|
| 206 |
+ calls["DmTaskGetInfo"] = true |
|
| 207 |
+ expectedTask := &task1 |
|
| 208 |
+ if task != expectedTask {
|
|
| 209 |
+ t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskGetInfo(%v)\nReceived: DmTaskGetInfo(%v)\n", expectedTask, task)
|
|
| 210 |
+ } |
|
| 211 |
+ // This will crash if info is not dereferenceable |
|
| 212 |
+ info.Exists = 0 |
|
| 213 |
+ return 1 |
|
| 214 |
+ } |
|
| 215 |
+ DmTaskSetSector = func(task *CDmTask, sector uint64) int {
|
|
| 216 |
+ calls["DmTaskSetSector"] = true |
|
| 217 |
+ expectedTask := &task1 |
|
| 218 |
+ if task != expectedTask {
|
|
| 219 |
+ t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetSector(%v)\nReceived: DmTaskSetSector(%v)\n", expectedTask, task)
|
|
| 220 |
+ } |
|
| 221 |
+ if expectedSector := uint64(0); sector != expectedSector {
|
|
| 222 |
+ t.Fatalf("Wrong libdevmapper call to DmTaskSetSector\nExpected: %v\nReceived: %v\n", expectedSector, sector)
|
|
| 223 |
+ } |
|
| 224 |
+ return 1 |
|
| 225 |
+ } |
|
| 226 |
+ DmTaskSetMessage = func(task *CDmTask, message string) int {
|
|
| 227 |
+ calls["DmTaskSetMessage"] = true |
|
| 228 |
+ expectedTask := &task1 |
|
| 229 |
+ if task != expectedTask {
|
|
| 230 |
+ t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetSector(%v)\nReceived: DmTaskSetSector(%v)\n", expectedTask, task)
|
|
| 231 |
+ } |
|
| 232 |
+ taskMessages[message] = true |
|
| 233 |
+ return 1 |
|
| 234 |
+ } |
|
| 235 |
+ var ( |
|
| 236 |
+ fakeDataLoop = "/dev/loop42" |
|
| 237 |
+ fakeMetadataLoop = "/dev/loop43" |
|
| 238 |
+ fakeDataLoopFd = 42 |
|
| 239 |
+ fakeMetadataLoopFd = 43 |
|
| 240 |
+ ) |
|
| 241 |
+ var attachCount int |
|
| 242 |
+ DmAttachLoopDevice = func(filename string, fd *int) string {
|
|
| 243 |
+ calls["DmAttachLoopDevice"] = true |
|
| 244 |
+ if _, exists := devicesAttached[filename]; exists {
|
|
| 245 |
+ t.Fatalf("Already attached %s", filename)
|
|
| 246 |
+ } |
|
| 247 |
+ devicesAttached[filename] = true |
|
| 248 |
+ // This will crash if fd is not dereferenceable |
|
| 249 |
+ if attachCount == 0 {
|
|
| 250 |
+ attachCount++ |
|
| 251 |
+ *fd = fakeDataLoopFd |
|
| 252 |
+ return fakeDataLoop |
|
| 253 |
+ } else {
|
|
| 254 |
+ *fd = fakeMetadataLoopFd |
|
| 255 |
+ return fakeMetadataLoop |
|
| 256 |
+ } |
|
| 257 |
+ } |
|
| 258 |
+ DmTaskDestroy = func(task *CDmTask) {
|
|
| 259 |
+ calls["DmTaskDestroy"] = true |
|
| 260 |
+ expectedTask := &task1 |
|
| 261 |
+ if task != expectedTask {
|
|
| 262 |
+ t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskDestroy(%v)\nReceived: DmTaskDestroy(%v)\n", expectedTask, task)
|
|
| 263 |
+ } |
|
| 264 |
+ } |
|
| 265 |
+ fakeBlockSize := int64(4242 * 512) |
|
| 266 |
+ DmGetBlockSize = func(fd uintptr) (int64, sysErrno) {
|
|
| 267 |
+ calls["DmGetBlockSize"] = true |
|
| 268 |
+ if expectedFd := uintptr(42); fd != expectedFd {
|
|
| 269 |
+ t.Fatalf("Wrong libdevmapper call\nExpected: DmGetBlockSize(%v)\nReceived: DmGetBlockSize(%v)\n", expectedFd, fd)
|
|
| 270 |
+ } |
|
| 271 |
+ return fakeBlockSize, 0 |
|
| 272 |
+ } |
|
| 273 |
+ DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int {
|
|
| 274 |
+ calls["DmTaskSetTarget"] = true |
|
| 275 |
+ expectedTask := &task1 |
|
| 276 |
+ if task != expectedTask {
|
|
| 277 |
+ t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskDestroy(%v)\nReceived: DmTaskDestroy(%v)\n", expectedTask, task)
|
|
| 278 |
+ } |
|
| 279 |
+ if start != 0 {
|
|
| 280 |
+ t.Fatalf("Wrong start: %d != %d", start, 0)
|
|
| 281 |
+ } |
|
| 282 |
+ if ttype != "thin" && ttype != "thin-pool" {
|
|
| 283 |
+ t.Fatalf("Wrong ttype: %s", ttype)
|
|
| 284 |
+ } |
|
| 285 |
+ // Quick smoke test |
|
| 286 |
+ if params == "" {
|
|
| 287 |
+ t.Fatalf("Params should not be empty")
|
|
| 288 |
+ } |
|
| 289 |
+ return 1 |
|
| 290 |
+ } |
|
| 291 |
+ fakeCookie := uint(4321) |
|
| 292 |
+ DmTaskSetCookie = func(task *CDmTask, cookie *uint, flags uint16) int {
|
|
| 293 |
+ calls["DmTaskSetCookie"] = true |
|
| 294 |
+ expectedTask := &task1 |
|
| 295 |
+ if task != expectedTask {
|
|
| 296 |
+ t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskDestroy(%v)\nReceived: DmTaskDestroy(%v)\n", expectedTask, task)
|
|
| 297 |
+ } |
|
| 298 |
+ if flags != 0 {
|
|
| 299 |
+ t.Fatalf("Cookie flags should be 0 (not %x)", flags)
|
|
| 300 |
+ } |
|
| 301 |
+ *cookie = fakeCookie |
|
| 302 |
+ return 1 |
|
| 303 |
+ } |
|
| 304 |
+ DmUdevWait = func(cookie uint) int {
|
|
| 305 |
+ calls["DmUdevWait"] = true |
|
| 306 |
+ if cookie != fakeCookie {
|
|
| 307 |
+ t.Fatalf("Wrong cookie: %d != %d", cookie, fakeCookie)
|
|
| 308 |
+ } |
|
| 309 |
+ return 1 |
|
| 310 |
+ } |
|
| 311 |
+ DmTaskSetAddNode = func(task *CDmTask, addNode AddNodeType) int {
|
|
| 312 |
+ if addNode != AddNodeOnCreate {
|
|
| 313 |
+ t.Fatalf("Wrong AddNoteType: %v (expected %v)", addNode, AddNodeOnCreate)
|
|
| 314 |
+ } |
|
| 315 |
+ calls["DmTaskSetAddNode"] = true |
|
| 316 |
+ return 1 |
|
| 317 |
+ } |
|
| 318 |
+ execRun = func(name string, args ...string) error {
|
|
| 319 |
+ calls["execRun"] = true |
|
| 320 |
+ if name != "mkfs.ext4" {
|
|
| 321 |
+ t.Fatalf("Expected %s to be executed, not %s", "mkfs.ext4", name)
|
|
| 322 |
+ } |
|
| 323 |
+ return nil |
|
| 324 |
+ } |
|
| 325 |
+ driver, err := Init(home) |
|
| 326 |
+ if err != nil {
|
|
| 327 |
+ t.Fatal(err) |
|
| 328 |
+ } |
|
| 329 |
+ defer func() {
|
|
| 330 |
+ if err := driver.Cleanup(); err != nil {
|
|
| 331 |
+ t.Fatal(err) |
|
| 332 |
+ } |
|
| 333 |
+ }() |
|
| 334 |
+ }() |
|
| 335 |
+ // Put all tests in a funciton to make sure the garbage collection will |
|
| 336 |
+ // occur. |
|
| 337 |
+ |
|
| 338 |
+ // Call GC to cleanup runtime.Finalizers |
|
| 339 |
+ runtime.GC() |
|
| 340 |
+ |
|
| 341 |
+ calls.Assert(t, |
|
| 342 |
+ "DmSetDevDir", |
|
| 343 |
+ "DmLogWithErrnoInit", |
|
| 344 |
+ "DmTaskSetName", |
|
| 345 |
+ "DmTaskRun", |
|
| 346 |
+ "DmTaskGetInfo", |
|
| 347 |
+ "DmAttachLoopDevice", |
|
| 348 |
+ "DmTaskDestroy", |
|
| 349 |
+ "execRun", |
|
| 350 |
+ "DmTaskCreate", |
|
| 351 |
+ "DmGetBlockSize", |
|
| 352 |
+ "DmTaskSetTarget", |
|
| 353 |
+ "DmTaskSetCookie", |
|
| 354 |
+ "DmUdevWait", |
|
| 355 |
+ "DmTaskSetSector", |
|
| 356 |
+ "DmTaskSetMessage", |
|
| 357 |
+ "DmTaskSetAddNode", |
|
| 358 |
+ ) |
|
| 359 |
+ devicesAttached.Assert(t, path.Join(home, "devicemapper", "data"), path.Join(home, "devicemapper", "metadata")) |
|
| 360 |
+ taskTypes.Assert(t, "0", "6", "17") |
|
| 361 |
+ taskMessages.Assert(t, "create_thin 0", "set_transaction_id 0 1") |
|
| 362 |
+} |
|
| 363 |
+ |
|
| 364 |
+func fakeInit() func(home string) (graphdriver.Driver, error) {
|
|
| 365 |
+ oldInit := Init |
|
| 366 |
+ Init = func(home string) (graphdriver.Driver, error) {
|
|
| 367 |
+ return &Driver{
|
|
| 368 |
+ home: home, |
|
| 369 |
+ }, nil |
|
| 370 |
+ } |
|
| 371 |
+ return oldInit |
|
| 372 |
+} |
|
| 373 |
+ |
|
| 374 |
+func restoreInit(init func(home string) (graphdriver.Driver, error)) {
|
|
| 375 |
+ Init = init |
|
| 376 |
+} |
|
| 377 |
+ |
|
| 378 |
+func mockAllDevmapper(calls Set) {
|
|
| 379 |
+ DmSetDevDir = func(dir string) int {
|
|
| 380 |
+ calls["DmSetDevDir"] = true |
|
| 381 |
+ return 0 |
|
| 382 |
+ } |
|
| 383 |
+ LogWithErrnoInit = func() {
|
|
| 384 |
+ calls["DmLogWithErrnoInit"] = true |
|
| 385 |
+ } |
|
| 386 |
+ DmTaskCreate = func(taskType int) *CDmTask {
|
|
| 387 |
+ calls["DmTaskCreate"] = true |
|
| 388 |
+ return &CDmTask{}
|
|
| 389 |
+ } |
|
| 390 |
+ DmTaskSetName = func(task *CDmTask, name string) int {
|
|
| 391 |
+ calls["DmTaskSetName"] = true |
|
| 392 |
+ return 1 |
|
| 393 |
+ } |
|
| 394 |
+ DmTaskRun = func(task *CDmTask) int {
|
|
| 395 |
+ calls["DmTaskRun"] = true |
|
| 396 |
+ return 1 |
|
| 397 |
+ } |
|
| 398 |
+ DmTaskGetInfo = func(task *CDmTask, info *Info) int {
|
|
| 399 |
+ calls["DmTaskGetInfo"] = true |
|
| 400 |
+ return 1 |
|
| 401 |
+ } |
|
| 402 |
+ DmTaskSetSector = func(task *CDmTask, sector uint64) int {
|
|
| 403 |
+ calls["DmTaskSetSector"] = true |
|
| 404 |
+ return 1 |
|
| 405 |
+ } |
|
| 406 |
+ DmTaskSetMessage = func(task *CDmTask, message string) int {
|
|
| 407 |
+ calls["DmTaskSetMessage"] = true |
|
| 408 |
+ return 1 |
|
| 409 |
+ } |
|
| 410 |
+ DmAttachLoopDevice = func(filename string, fd *int) string {
|
|
| 411 |
+ calls["DmAttachLoopDevice"] = true |
|
| 412 |
+ return "/dev/loop42" |
|
| 413 |
+ } |
|
| 414 |
+ DmTaskDestroy = func(task *CDmTask) {
|
|
| 415 |
+ calls["DmTaskDestroy"] = true |
|
| 416 |
+ } |
|
| 417 |
+ DmGetBlockSize = func(fd uintptr) (int64, sysErrno) {
|
|
| 418 |
+ calls["DmGetBlockSize"] = true |
|
| 419 |
+ return int64(4242 * 512), 0 |
|
| 420 |
+ } |
|
| 421 |
+ DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int {
|
|
| 422 |
+ calls["DmTaskSetTarget"] = true |
|
| 423 |
+ return 1 |
|
| 424 |
+ } |
|
| 425 |
+ DmTaskSetCookie = func(task *CDmTask, cookie *uint, flags uint16) int {
|
|
| 426 |
+ calls["DmTaskSetCookie"] = true |
|
| 427 |
+ return 1 |
|
| 428 |
+ } |
|
| 429 |
+ DmUdevWait = func(cookie uint) int {
|
|
| 430 |
+ calls["DmUdevWait"] = true |
|
| 431 |
+ return 1 |
|
| 432 |
+ } |
|
| 433 |
+ DmTaskSetAddNode = func(task *CDmTask, addNode AddNodeType) int {
|
|
| 434 |
+ calls["DmTaskSetAddNode"] = true |
|
| 435 |
+ return 1 |
|
| 436 |
+ } |
|
| 437 |
+ execRun = func(name string, args ...string) error {
|
|
| 438 |
+ calls["execRun"] = true |
|
| 439 |
+ return nil |
|
| 440 |
+ } |
|
| 441 |
+} |
|
| 442 |
+ |
|
| 443 |
+func TestDriverName(t *testing.T) {
|
|
| 444 |
+ denyAllDevmapper() |
|
| 445 |
+ defer denyAllDevmapper() |
|
| 446 |
+ |
|
| 447 |
+ oldInit := fakeInit() |
|
| 448 |
+ defer restoreInit(oldInit) |
|
| 449 |
+ |
|
| 450 |
+ d := newDriver(t) |
|
| 451 |
+ if d.String() != "devicemapper" {
|
|
| 452 |
+ t.Fatalf("Expected driver name to be devicemapper got %s", d.String())
|
|
| 453 |
+ } |
|
| 454 |
+} |
|
| 455 |
+ |
|
| 456 |
+func TestDriverCreate(t *testing.T) {
|
|
| 457 |
+ denyAllDevmapper() |
|
| 458 |
+ denyAllSyscall() |
|
| 459 |
+ defer denyAllSyscall() |
|
| 460 |
+ defer denyAllDevmapper() |
|
| 461 |
+ |
|
| 462 |
+ calls := make(Set) |
|
| 463 |
+ mockAllDevmapper(calls) |
|
| 464 |
+ |
|
| 465 |
+ sysMount = func(source, target, fstype string, flags uintptr, data string) (err error) {
|
|
| 466 |
+ calls["sysMount"] = true |
|
| 467 |
+ // FIXME: compare the exact source and target strings (inodes + devname) |
|
| 468 |
+ if expectedSource := "/dev/mapper/docker-"; !strings.HasPrefix(source, expectedSource) {
|
|
| 469 |
+ t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedSource, source)
|
|
| 470 |
+ } |
|
| 471 |
+ if expectedTarget := "/tmp/docker-test-devmapper-"; !strings.HasPrefix(target, expectedTarget) {
|
|
| 472 |
+ t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedTarget, target)
|
|
| 473 |
+ } |
|
| 474 |
+ if expectedFstype := "ext4"; fstype != expectedFstype {
|
|
| 475 |
+ t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFstype, fstype)
|
|
| 476 |
+ } |
|
| 477 |
+ if expectedFlags := uintptr(3236757504); flags != expectedFlags {
|
|
| 478 |
+ t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFlags, flags)
|
|
| 479 |
+ } |
|
| 480 |
+ return nil |
|
| 481 |
+ } |
|
| 482 |
+ |
|
| 483 |
+ Mounted = func(mnt string) (bool, error) {
|
|
| 484 |
+ calls["Mounted"] = true |
|
| 485 |
+ if !strings.HasPrefix(mnt, "/tmp/docker-test-devmapper-") || !strings.HasSuffix(mnt, "/mnt/1") {
|
|
| 486 |
+ t.Fatalf("Wrong mounted call\nExpected: Mounted(%v)\nReceived: Mounted(%v)\n", "/tmp/docker-test-devmapper-.../mnt/1", mnt)
|
|
| 487 |
+ } |
|
| 488 |
+ return false, nil |
|
| 489 |
+ } |
|
| 490 |
+ |
|
| 491 |
+ func() {
|
|
| 492 |
+ d := newDriver(t) |
|
| 493 |
+ |
|
| 494 |
+ calls.Assert(t, |
|
| 495 |
+ "DmSetDevDir", |
|
| 496 |
+ "DmLogWithErrnoInit", |
|
| 497 |
+ "DmTaskSetName", |
|
| 498 |
+ "DmTaskRun", |
|
| 499 |
+ "DmTaskGetInfo", |
|
| 500 |
+ "DmAttachLoopDevice", |
|
| 501 |
+ "execRun", |
|
| 502 |
+ "DmTaskCreate", |
|
| 503 |
+ "DmGetBlockSize", |
|
| 504 |
+ "DmTaskSetTarget", |
|
| 505 |
+ "DmTaskSetCookie", |
|
| 506 |
+ "DmUdevWait", |
|
| 507 |
+ "DmTaskSetSector", |
|
| 508 |
+ "DmTaskSetMessage", |
|
| 509 |
+ "DmTaskSetAddNode", |
|
| 510 |
+ ) |
|
| 511 |
+ |
|
| 512 |
+ if err := d.Create("1", ""); err != nil {
|
|
| 513 |
+ t.Fatal(err) |
|
| 514 |
+ } |
|
| 515 |
+ calls.Assert(t, |
|
| 516 |
+ "DmTaskCreate", |
|
| 517 |
+ "DmTaskGetInfo", |
|
| 518 |
+ "sysMount", |
|
| 519 |
+ "Mounted", |
|
| 520 |
+ "DmTaskRun", |
|
| 521 |
+ "DmTaskSetTarget", |
|
| 522 |
+ "DmTaskSetSector", |
|
| 523 |
+ "DmTaskSetCookie", |
|
| 524 |
+ "DmUdevWait", |
|
| 525 |
+ "DmTaskSetName", |
|
| 526 |
+ "DmTaskSetMessage", |
|
| 527 |
+ "DmTaskSetAddNode", |
|
| 528 |
+ ) |
|
| 529 |
+ |
|
| 530 |
+ }() |
|
| 531 |
+ |
|
| 532 |
+ runtime.GC() |
|
| 533 |
+ |
|
| 534 |
+ calls.Assert(t, |
|
| 535 |
+ "DmTaskDestroy", |
|
| 536 |
+ ) |
|
| 537 |
+} |
|
| 538 |
+ |
|
| 539 |
+func TestDriverRemove(t *testing.T) {
|
|
| 540 |
+ denyAllDevmapper() |
|
| 541 |
+ denyAllSyscall() |
|
| 542 |
+ defer denyAllSyscall() |
|
| 543 |
+ defer denyAllDevmapper() |
|
| 544 |
+ |
|
| 545 |
+ calls := make(Set) |
|
| 546 |
+ mockAllDevmapper(calls) |
|
| 547 |
+ |
|
| 548 |
+ sysMount = func(source, target, fstype string, flags uintptr, data string) (err error) {
|
|
| 549 |
+ calls["sysMount"] = true |
|
| 550 |
+ // FIXME: compare the exact source and target strings (inodes + devname) |
|
| 551 |
+ if expectedSource := "/dev/mapper/docker-"; !strings.HasPrefix(source, expectedSource) {
|
|
| 552 |
+ t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedSource, source)
|
|
| 553 |
+ } |
|
| 554 |
+ if expectedTarget := "/tmp/docker-test-devmapper-"; !strings.HasPrefix(target, expectedTarget) {
|
|
| 555 |
+ t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedTarget, target)
|
|
| 556 |
+ } |
|
| 557 |
+ if expectedFstype := "ext4"; fstype != expectedFstype {
|
|
| 558 |
+ t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFstype, fstype)
|
|
| 559 |
+ } |
|
| 560 |
+ if expectedFlags := uintptr(3236757504); flags != expectedFlags {
|
|
| 561 |
+ t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFlags, flags)
|
|
| 562 |
+ } |
|
| 563 |
+ return nil |
|
| 564 |
+ } |
|
| 565 |
+ sysUnmount = func(target string, flags int) (err error) {
|
|
| 566 |
+ calls["sysUnmount"] = true |
|
| 567 |
+ // FIXME: compare the exact source and target strings (inodes + devname) |
|
| 568 |
+ if expectedTarget := "/tmp/docker-test-devmapper-"; !strings.HasPrefix(target, expectedTarget) {
|
|
| 569 |
+ t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedTarget, target)
|
|
| 570 |
+ } |
|
| 571 |
+ if expectedFlags := 0; flags != expectedFlags {
|
|
| 572 |
+ t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFlags, flags)
|
|
| 573 |
+ } |
|
| 574 |
+ return nil |
|
| 575 |
+ } |
|
| 576 |
+ Mounted = func(mnt string) (bool, error) {
|
|
| 577 |
+ calls["Mounted"] = true |
|
| 578 |
+ return false, nil |
|
| 579 |
+ } |
|
| 580 |
+ |
|
| 581 |
+ func() {
|
|
| 582 |
+ d := newDriver(t) |
|
| 583 |
+ |
|
| 584 |
+ calls.Assert(t, |
|
| 585 |
+ "DmSetDevDir", |
|
| 586 |
+ "DmLogWithErrnoInit", |
|
| 587 |
+ "DmTaskSetName", |
|
| 588 |
+ "DmTaskRun", |
|
| 589 |
+ "DmTaskGetInfo", |
|
| 590 |
+ "DmAttachLoopDevice", |
|
| 591 |
+ "execRun", |
|
| 592 |
+ "DmTaskCreate", |
|
| 593 |
+ "DmGetBlockSize", |
|
| 594 |
+ "DmTaskSetTarget", |
|
| 595 |
+ "DmTaskSetCookie", |
|
| 596 |
+ "DmUdevWait", |
|
| 597 |
+ "DmTaskSetSector", |
|
| 598 |
+ "DmTaskSetMessage", |
|
| 599 |
+ "DmTaskSetAddNode", |
|
| 600 |
+ ) |
|
| 601 |
+ |
|
| 602 |
+ if err := d.Create("1", ""); err != nil {
|
|
| 603 |
+ t.Fatal(err) |
|
| 604 |
+ } |
|
| 605 |
+ |
|
| 606 |
+ calls.Assert(t, |
|
| 607 |
+ "DmTaskCreate", |
|
| 608 |
+ "DmTaskGetInfo", |
|
| 609 |
+ "sysMount", |
|
| 610 |
+ "Mounted", |
|
| 611 |
+ "DmTaskRun", |
|
| 612 |
+ "DmTaskSetTarget", |
|
| 613 |
+ "DmTaskSetSector", |
|
| 614 |
+ "DmTaskSetCookie", |
|
| 615 |
+ "DmUdevWait", |
|
| 616 |
+ "DmTaskSetName", |
|
| 617 |
+ "DmTaskSetMessage", |
|
| 618 |
+ "DmTaskSetAddNode", |
|
| 619 |
+ ) |
|
| 620 |
+ |
|
| 621 |
+ Mounted = func(mnt string) (bool, error) {
|
|
| 622 |
+ calls["Mounted"] = true |
|
| 623 |
+ return true, nil |
|
| 624 |
+ } |
|
| 625 |
+ |
|
| 626 |
+ if err := d.Remove("1"); err != nil {
|
|
| 627 |
+ t.Fatal(err) |
|
| 628 |
+ } |
|
| 629 |
+ |
|
| 630 |
+ calls.Assert(t, |
|
| 631 |
+ "DmTaskRun", |
|
| 632 |
+ "DmTaskSetSector", |
|
| 633 |
+ "DmTaskSetName", |
|
| 634 |
+ "DmTaskSetMessage", |
|
| 635 |
+ "DmTaskCreate", |
|
| 636 |
+ "DmTaskGetInfo", |
|
| 637 |
+ "Mounted", |
|
| 638 |
+ "sysUnmount", |
|
| 639 |
+ ) |
|
| 640 |
+ }() |
|
| 641 |
+ runtime.GC() |
|
| 642 |
+ |
|
| 643 |
+ calls.Assert(t, |
|
| 644 |
+ "DmTaskDestroy", |
|
| 645 |
+ ) |
|
| 646 |
+} |
|
| 647 |
+ |
|
| 648 |
+func TestCleanup(t *testing.T) {
|
|
| 649 |
+ t.Skip("FIXME: not a unit test")
|
|
| 650 |
+ t.Skip("Unimplemented")
|
|
| 651 |
+ d := newDriver(t) |
|
| 652 |
+ defer osRemoveAll(d.home) |
|
| 653 |
+ |
|
| 654 |
+ mountPoints := make([]string, 2) |
|
| 655 |
+ |
|
| 656 |
+ if err := d.Create("1", ""); err != nil {
|
|
| 657 |
+ t.Fatal(err) |
|
| 658 |
+ } |
|
| 659 |
+ // Mount the id |
|
| 660 |
+ p, err := d.Get("1")
|
|
| 661 |
+ if err != nil {
|
|
| 662 |
+ t.Fatal(err) |
|
| 663 |
+ } |
|
| 664 |
+ mountPoints[0] = p |
|
| 665 |
+ |
|
| 666 |
+ if err := d.Create("2", "1"); err != nil {
|
|
| 667 |
+ t.Fatal(err) |
|
| 668 |
+ } |
|
| 669 |
+ |
|
| 670 |
+ p, err = d.Get("2")
|
|
| 671 |
+ if err != nil {
|
|
| 672 |
+ t.Fatal(err) |
|
| 673 |
+ } |
|
| 674 |
+ mountPoints[1] = p |
|
| 675 |
+ |
|
| 676 |
+ // Ensure that all the mount points are currently mounted |
|
| 677 |
+ for _, p := range mountPoints {
|
|
| 678 |
+ if mounted, err := Mounted(p); err != nil {
|
|
| 679 |
+ t.Fatal(err) |
|
| 680 |
+ } else if !mounted {
|
|
| 681 |
+ t.Fatalf("Expected %s to be mounted", p)
|
|
| 682 |
+ } |
|
| 683 |
+ } |
|
| 684 |
+ |
|
| 685 |
+ // Ensure that devices are active |
|
| 686 |
+ for _, p := range []string{"1", "2"} {
|
|
| 687 |
+ if !d.HasActivatedDevice(p) {
|
|
| 688 |
+ t.Fatalf("Expected %s to have an active device", p)
|
|
| 689 |
+ } |
|
| 690 |
+ } |
|
| 691 |
+ |
|
| 692 |
+ if err := d.Cleanup(); err != nil {
|
|
| 693 |
+ t.Fatal(err) |
|
| 694 |
+ } |
|
| 695 |
+ |
|
| 696 |
+ // Ensure that all the mount points are no longer mounted |
|
| 697 |
+ for _, p := range mountPoints {
|
|
| 698 |
+ if mounted, err := Mounted(p); err != nil {
|
|
| 699 |
+ t.Fatal(err) |
|
| 700 |
+ } else if mounted {
|
|
| 701 |
+ t.Fatalf("Expected %s to not be mounted", p)
|
|
| 702 |
+ } |
|
| 703 |
+ } |
|
| 704 |
+ |
|
| 705 |
+ // Ensure that devices are no longer activated |
|
| 706 |
+ for _, p := range []string{"1", "2"} {
|
|
| 707 |
+ if d.HasActivatedDevice(p) {
|
|
| 708 |
+ t.Fatalf("Expected %s not be an active device", p)
|
|
| 709 |
+ } |
|
| 710 |
+ } |
|
| 711 |
+} |
|
| 712 |
+ |
|
| 713 |
+func TestNotMounted(t *testing.T) {
|
|
| 714 |
+ t.Skip("FIXME: not a unit test")
|
|
| 715 |
+ t.Skip("Not implemented")
|
|
| 716 |
+ d := newDriver(t) |
|
| 717 |
+ defer cleanup(d) |
|
| 718 |
+ |
|
| 719 |
+ if err := d.Create("1", ""); err != nil {
|
|
| 720 |
+ t.Fatal(err) |
|
| 721 |
+ } |
|
| 722 |
+ |
|
| 723 |
+ mounted, err := Mounted(path.Join(d.home, "mnt", "1")) |
|
| 724 |
+ if err != nil {
|
|
| 725 |
+ t.Fatal(err) |
|
| 726 |
+ } |
|
| 727 |
+ if mounted {
|
|
| 728 |
+ t.Fatal("Id 1 should not be mounted")
|
|
| 729 |
+ } |
|
| 730 |
+} |
|
| 731 |
+ |
|
| 732 |
+func TestMounted(t *testing.T) {
|
|
| 733 |
+ t.Skip("FIXME: not a unit test")
|
|
| 734 |
+ d := newDriver(t) |
|
| 735 |
+ defer cleanup(d) |
|
| 736 |
+ |
|
| 737 |
+ if err := d.Create("1", ""); err != nil {
|
|
| 738 |
+ t.Fatal(err) |
|
| 739 |
+ } |
|
| 740 |
+ if _, err := d.Get("1"); err != nil {
|
|
| 741 |
+ t.Fatal(err) |
|
| 742 |
+ } |
|
| 743 |
+ |
|
| 744 |
+ mounted, err := Mounted(path.Join(d.home, "mnt", "1")) |
|
| 745 |
+ if err != nil {
|
|
| 746 |
+ t.Fatal(err) |
|
| 747 |
+ } |
|
| 748 |
+ if !mounted {
|
|
| 749 |
+ t.Fatal("Id 1 should be mounted")
|
|
| 750 |
+ } |
|
| 751 |
+} |
|
| 752 |
+ |
|
| 753 |
+func TestInitCleanedDriver(t *testing.T) {
|
|
| 754 |
+ t.Skip("FIXME: not a unit test")
|
|
| 755 |
+ d := newDriver(t) |
|
| 756 |
+ |
|
| 757 |
+ if err := d.Create("1", ""); err != nil {
|
|
| 758 |
+ t.Fatal(err) |
|
| 759 |
+ } |
|
| 760 |
+ if _, err := d.Get("1"); err != nil {
|
|
| 761 |
+ t.Fatal(err) |
|
| 762 |
+ } |
|
| 763 |
+ |
|
| 764 |
+ if err := d.Cleanup(); err != nil {
|
|
| 765 |
+ t.Fatal(err) |
|
| 766 |
+ } |
|
| 767 |
+ |
|
| 768 |
+ driver, err := Init(d.home) |
|
| 769 |
+ if err != nil {
|
|
| 770 |
+ t.Fatal(err) |
|
| 771 |
+ } |
|
| 772 |
+ d = driver.(*Driver) |
|
| 773 |
+ defer cleanup(d) |
|
| 774 |
+ |
|
| 775 |
+ if _, err := d.Get("1"); err != nil {
|
|
| 776 |
+ t.Fatal(err) |
|
| 777 |
+ } |
|
| 778 |
+} |
|
| 779 |
+ |
|
| 780 |
+func TestMountMountedDriver(t *testing.T) {
|
|
| 781 |
+ t.Skip("FIXME: not a unit test")
|
|
| 782 |
+ d := newDriver(t) |
|
| 783 |
+ defer cleanup(d) |
|
| 784 |
+ |
|
| 785 |
+ if err := d.Create("1", ""); err != nil {
|
|
| 786 |
+ t.Fatal(err) |
|
| 787 |
+ } |
|
| 788 |
+ |
|
| 789 |
+ // Perform get on same id to ensure that it will |
|
| 790 |
+ // not be mounted twice |
|
| 791 |
+ if _, err := d.Get("1"); err != nil {
|
|
| 792 |
+ t.Fatal(err) |
|
| 793 |
+ } |
|
| 794 |
+ if _, err := d.Get("1"); err != nil {
|
|
| 795 |
+ t.Fatal(err) |
|
| 796 |
+ } |
|
| 797 |
+} |
|
| 798 |
+ |
|
| 799 |
+func TestGetReturnsValidDevice(t *testing.T) {
|
|
| 800 |
+ t.Skip("FIXME: not a unit test")
|
|
| 801 |
+ d := newDriver(t) |
|
| 802 |
+ defer cleanup(d) |
|
| 803 |
+ |
|
| 804 |
+ if err := d.Create("1", ""); err != nil {
|
|
| 805 |
+ t.Fatal(err) |
|
| 806 |
+ } |
|
| 807 |
+ |
|
| 808 |
+ if !d.HasDevice("1") {
|
|
| 809 |
+ t.Fatalf("Expected id 1 to be in device set")
|
|
| 810 |
+ } |
|
| 811 |
+ |
|
| 812 |
+ if _, err := d.Get("1"); err != nil {
|
|
| 813 |
+ t.Fatal(err) |
|
| 814 |
+ } |
|
| 815 |
+ |
|
| 816 |
+ if !d.HasActivatedDevice("1") {
|
|
| 817 |
+ t.Fatalf("Expected id 1 to be activated")
|
|
| 818 |
+ } |
|
| 819 |
+ |
|
| 820 |
+ if !d.HasInitializedDevice("1") {
|
|
| 821 |
+ t.Fatalf("Expected id 1 to be initialized")
|
|
| 822 |
+ } |
|
| 823 |
+} |
|
| 824 |
+ |
|
| 825 |
+func TestDriverGetSize(t *testing.T) {
|
|
| 826 |
+ t.Skip("FIXME: not a unit test")
|
|
| 827 |
+ t.Skipf("Size is currently not implemented")
|
|
| 828 |
+ |
|
| 829 |
+ d := newDriver(t) |
|
| 830 |
+ defer cleanup(d) |
|
| 831 |
+ |
|
| 832 |
+ if err := d.Create("1", ""); err != nil {
|
|
| 833 |
+ t.Fatal(err) |
|
| 834 |
+ } |
|
| 835 |
+ |
|
| 836 |
+ mountPoint, err := d.Get("1")
|
|
| 837 |
+ if err != nil {
|
|
| 838 |
+ t.Fatal(err) |
|
| 839 |
+ } |
|
| 840 |
+ |
|
| 841 |
+ size := int64(1024) |
|
| 842 |
+ |
|
| 843 |
+ f, err := osCreate(path.Join(mountPoint, "test_file")) |
|
| 844 |
+ if err != nil {
|
|
| 845 |
+ t.Fatal(err) |
|
| 846 |
+ } |
|
| 847 |
+ if err := f.Truncate(size); err != nil {
|
|
| 848 |
+ t.Fatal(err) |
|
| 849 |
+ } |
|
| 850 |
+ f.Close() |
|
| 851 |
+ |
|
| 852 |
+ // diffSize, err := d.DiffSize("1")
|
|
| 853 |
+ // if err != nil {
|
|
| 854 |
+ // t.Fatal(err) |
|
| 855 |
+ // } |
|
| 856 |
+ // if diffSize != size {
|
|
| 857 |
+ // t.Fatalf("Expected size %d got %d", size, diffSize)
|
|
| 858 |
+ // } |
|
| 859 |
+} |
|
| 860 |
+ |
|
| 861 |
+func assertMap(t *testing.T, m map[string]bool, keys ...string) {
|
|
| 862 |
+ for _, key := range keys {
|
|
| 863 |
+ if _, exists := m[key]; !exists {
|
|
| 864 |
+ t.Fatalf("Key not set: %s", key)
|
|
| 865 |
+ } |
|
| 866 |
+ delete(m, key) |
|
| 867 |
+ } |
|
| 868 |
+ if len(m) != 0 {
|
|
| 869 |
+ t.Fatalf("Unexpected keys: %v", m)
|
|
| 870 |
+ } |
|
| 871 |
+} |
| 0 | 872 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,25 @@ |
| 0 |
+package devmapper |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "path/filepath" |
|
| 4 |
+) |
|
| 5 |
+ |
|
| 6 |
+// FIXME: this is copy-pasted from the aufs driver. |
|
| 7 |
+// It should be moved into the core. |
|
| 8 |
+ |
|
| 9 |
+var Mounted = func(mountpoint string) (bool, error) {
|
|
| 10 |
+ mntpoint, err := osStat(mountpoint) |
|
| 11 |
+ if err != nil {
|
|
| 12 |
+ if osIsNotExist(err) {
|
|
| 13 |
+ return false, nil |
|
| 14 |
+ } |
|
| 15 |
+ return false, err |
|
| 16 |
+ } |
|
| 17 |
+ parent, err := osStat(filepath.Join(mountpoint, "..")) |
|
| 18 |
+ if err != nil {
|
|
| 19 |
+ return false, err |
|
| 20 |
+ } |
|
| 21 |
+ mntpointSt := toSysStatT(mntpoint.Sys()) |
|
| 22 |
+ parentSt := toSysStatT(parent.Sys()) |
|
| 23 |
+ return mntpointSt.Dev != parentSt.Dev, nil |
|
| 24 |
+} |
| 0 | 25 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,50 @@ |
| 0 |
+package devmapper |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "os" |
|
| 4 |
+ "os/exec" |
|
| 5 |
+ "syscall" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+type ( |
|
| 9 |
+ sysStatT syscall.Stat_t |
|
| 10 |
+ sysErrno syscall.Errno |
|
| 11 |
+ |
|
| 12 |
+ osFile struct{ *os.File }
|
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+var ( |
|
| 16 |
+ sysMount = syscall.Mount |
|
| 17 |
+ sysUnmount = syscall.Unmount |
|
| 18 |
+ sysCloseOnExec = syscall.CloseOnExec |
|
| 19 |
+ sysSyscall = syscall.Syscall |
|
| 20 |
+ |
|
| 21 |
+ osOpenFile = os.OpenFile |
|
| 22 |
+ osNewFile = os.NewFile |
|
| 23 |
+ osCreate = os.Create |
|
| 24 |
+ osStat = os.Stat |
|
| 25 |
+ osIsNotExist = os.IsNotExist |
|
| 26 |
+ osIsExist = os.IsExist |
|
| 27 |
+ osMkdirAll = os.MkdirAll |
|
| 28 |
+ osRemoveAll = os.RemoveAll |
|
| 29 |
+ osRename = os.Rename |
|
| 30 |
+ osReadlink = os.Readlink |
|
| 31 |
+ |
|
| 32 |
+ execRun = func(name string, args ...string) error {
|
|
| 33 |
+ return exec.Command(name, args...).Run() |
|
| 34 |
+ } |
|
| 35 |
+) |
|
| 36 |
+ |
|
| 37 |
+const ( |
|
| 38 |
+ sysMsMgcVal = syscall.MS_MGC_VAL |
|
| 39 |
+ sysMsRdOnly = syscall.MS_RDONLY |
|
| 40 |
+ sysEInval = syscall.EINVAL |
|
| 41 |
+ sysSysIoctl = syscall.SYS_IOCTL |
|
| 42 |
+ |
|
| 43 |
+ osORdWr = os.O_RDWR |
|
| 44 |
+ osOCreate = os.O_CREATE |
|
| 45 |
+) |
|
| 46 |
+ |
|
| 47 |
+func toSysStatT(i interface{}) *sysStatT {
|
|
| 48 |
+ return (*sysStatT)(i.(*syscall.Stat_t)) |
|
| 49 |
+} |
| 0 | 50 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,90 @@ |
| 0 |
+package graphdriver |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "github.com/dotcloud/docker/archive" |
|
| 5 |
+ "github.com/dotcloud/docker/utils" |
|
| 6 |
+ "os" |
|
| 7 |
+ "path" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+type InitFunc func(root string) (Driver, error) |
|
| 11 |
+ |
|
| 12 |
+type Driver interface {
|
|
| 13 |
+ String() string |
|
| 14 |
+ |
|
| 15 |
+ Create(id, parent string) error |
|
| 16 |
+ Remove(id string) error |
|
| 17 |
+ |
|
| 18 |
+ Get(id string) (dir string, err error) |
|
| 19 |
+ Exists(id string) bool |
|
| 20 |
+ |
|
| 21 |
+ Status() [][2]string |
|
| 22 |
+ |
|
| 23 |
+ Cleanup() error |
|
| 24 |
+} |
|
| 25 |
+ |
|
| 26 |
+type Differ interface {
|
|
| 27 |
+ Diff(id string) (archive.Archive, error) |
|
| 28 |
+ Changes(id string) ([]archive.Change, error) |
|
| 29 |
+ ApplyDiff(id string, diff archive.Archive) error |
|
| 30 |
+ DiffSize(id string) (bytes int64, err error) |
|
| 31 |
+} |
|
| 32 |
+ |
|
| 33 |
+var ( |
|
| 34 |
+ DefaultDriver string |
|
| 35 |
+ // All registred drivers |
|
| 36 |
+ drivers map[string]InitFunc |
|
| 37 |
+ // Slice of drivers that should be used in an order |
|
| 38 |
+ priority = []string{
|
|
| 39 |
+ "aufs", |
|
| 40 |
+ "devicemapper", |
|
| 41 |
+ "vfs", |
|
| 42 |
+ } |
|
| 43 |
+) |
|
| 44 |
+ |
|
| 45 |
+func init() {
|
|
| 46 |
+ drivers = make(map[string]InitFunc) |
|
| 47 |
+} |
|
| 48 |
+ |
|
| 49 |
+func Register(name string, initFunc InitFunc) error {
|
|
| 50 |
+ if _, exists := drivers[name]; exists {
|
|
| 51 |
+ return fmt.Errorf("Name already registered %s", name)
|
|
| 52 |
+ } |
|
| 53 |
+ drivers[name] = initFunc |
|
| 54 |
+ |
|
| 55 |
+ return nil |
|
| 56 |
+} |
|
| 57 |
+ |
|
| 58 |
+func GetDriver(name, home string) (Driver, error) {
|
|
| 59 |
+ if initFunc, exists := drivers[name]; exists {
|
|
| 60 |
+ return initFunc(path.Join(home, name)) |
|
| 61 |
+ } |
|
| 62 |
+ return nil, fmt.Errorf("No such driver: %s", name)
|
|
| 63 |
+} |
|
| 64 |
+ |
|
| 65 |
+func New(root string) (driver Driver, err error) {
|
|
| 66 |
+ for _, name := range []string{os.Getenv("DOCKER_DRIVER"), DefaultDriver} {
|
|
| 67 |
+ if name != "" {
|
|
| 68 |
+ return GetDriver(name, root) |
|
| 69 |
+ } |
|
| 70 |
+ } |
|
| 71 |
+ |
|
| 72 |
+ // Check for priority drivers first |
|
| 73 |
+ for _, name := range priority {
|
|
| 74 |
+ if driver, err = GetDriver(name, root); err != nil {
|
|
| 75 |
+ utils.Debugf("Error loading driver %s: %s", name, err)
|
|
| 76 |
+ continue |
|
| 77 |
+ } |
|
| 78 |
+ return driver, nil |
|
| 79 |
+ } |
|
| 80 |
+ |
|
| 81 |
+ // Check all registered drivers if no priority driver is found |
|
| 82 |
+ for _, initFunc := range drivers {
|
|
| 83 |
+ if driver, err = initFunc(root); err != nil {
|
|
| 84 |
+ continue |
|
| 85 |
+ } |
|
| 86 |
+ return driver, nil |
|
| 87 |
+ } |
|
| 88 |
+ return nil, err |
|
| 89 |
+} |
| 0 | 90 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,91 @@ |
| 0 |
+package vfs |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "github.com/dotcloud/docker/graphdriver" |
|
| 5 |
+ "os" |
|
| 6 |
+ "os/exec" |
|
| 7 |
+ "path" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+func init() {
|
|
| 11 |
+ graphdriver.Register("vfs", Init)
|
|
| 12 |
+} |
|
| 13 |
+ |
|
| 14 |
+func Init(home string) (graphdriver.Driver, error) {
|
|
| 15 |
+ d := &Driver{
|
|
| 16 |
+ home: home, |
|
| 17 |
+ } |
|
| 18 |
+ return d, nil |
|
| 19 |
+} |
|
| 20 |
+ |
|
| 21 |
+type Driver struct {
|
|
| 22 |
+ home string |
|
| 23 |
+} |
|
| 24 |
+ |
|
| 25 |
+func (d *Driver) String() string {
|
|
| 26 |
+ return "vfs" |
|
| 27 |
+} |
|
| 28 |
+ |
|
| 29 |
+func (d *Driver) Status() [][2]string {
|
|
| 30 |
+ return nil |
|
| 31 |
+} |
|
| 32 |
+ |
|
| 33 |
+func (d *Driver) Cleanup() error {
|
|
| 34 |
+ return nil |
|
| 35 |
+} |
|
| 36 |
+ |
|
| 37 |
+func copyDir(src, dst string) error {
|
|
| 38 |
+ cmd := exec.Command("cp", "-aT", "--reflink=auto", src, dst)
|
|
| 39 |
+ if err := cmd.Run(); err != nil {
|
|
| 40 |
+ return err |
|
| 41 |
+ } |
|
| 42 |
+ return nil |
|
| 43 |
+} |
|
| 44 |
+ |
|
| 45 |
+func (d *Driver) Create(id string, parent string) error {
|
|
| 46 |
+ dir := d.dir(id) |
|
| 47 |
+ if err := os.MkdirAll(path.Dir(dir), 0700); err != nil {
|
|
| 48 |
+ return err |
|
| 49 |
+ } |
|
| 50 |
+ if err := os.Mkdir(dir, 0700); err != nil {
|
|
| 51 |
+ return err |
|
| 52 |
+ } |
|
| 53 |
+ if parent == "" {
|
|
| 54 |
+ return nil |
|
| 55 |
+ } |
|
| 56 |
+ parentDir, err := d.Get(parent) |
|
| 57 |
+ if err != nil {
|
|
| 58 |
+ return fmt.Errorf("%s: %s", parent, err)
|
|
| 59 |
+ } |
|
| 60 |
+ if err := copyDir(parentDir, dir); err != nil {
|
|
| 61 |
+ return err |
|
| 62 |
+ } |
|
| 63 |
+ return nil |
|
| 64 |
+} |
|
| 65 |
+ |
|
| 66 |
+func (d *Driver) dir(id string) string {
|
|
| 67 |
+ return path.Join(d.home, "dir", path.Base(id)) |
|
| 68 |
+} |
|
| 69 |
+ |
|
| 70 |
+func (d *Driver) Remove(id string) error {
|
|
| 71 |
+ if _, err := os.Stat(d.dir(id)); err != nil {
|
|
| 72 |
+ return err |
|
| 73 |
+ } |
|
| 74 |
+ return os.RemoveAll(d.dir(id)) |
|
| 75 |
+} |
|
| 76 |
+ |
|
| 77 |
+func (d *Driver) Get(id string) (string, error) {
|
|
| 78 |
+ dir := d.dir(id) |
|
| 79 |
+ if st, err := os.Stat(dir); err != nil {
|
|
| 80 |
+ return "", err |
|
| 81 |
+ } else if !st.IsDir() {
|
|
| 82 |
+ return "", fmt.Errorf("%s: not a directory", dir)
|
|
| 83 |
+ } |
|
| 84 |
+ return dir, nil |
|
| 85 |
+} |
|
| 86 |
+ |
|
| 87 |
+func (d *Driver) Exists(id string) bool {
|
|
| 88 |
+ _, err := os.Stat(d.dir(id)) |
|
| 89 |
+ return err == nil |
|
| 90 |
+} |
| ... | ... |
@@ -6,17 +6,14 @@ import ( |
| 6 | 6 |
"encoding/json" |
| 7 | 7 |
"fmt" |
| 8 | 8 |
"github.com/dotcloud/docker/archive" |
| 9 |
+ "github.com/dotcloud/docker/graphdriver" |
|
| 9 | 10 |
"github.com/dotcloud/docker/utils" |
| 10 | 11 |
"io" |
| 11 | 12 |
"io/ioutil" |
| 12 |
- "log" |
|
| 13 | 13 |
"os" |
| 14 |
- "os/exec" |
|
| 15 | 14 |
"path" |
| 16 |
- "path/filepath" |
|
| 17 | 15 |
"strconv" |
| 18 | 16 |
"strings" |
| 19 |
- "syscall" |
|
| 20 | 17 |
"time" |
| 21 | 18 |
) |
| 22 | 19 |
|
| ... | ... |
@@ -62,39 +59,56 @@ func LoadImage(root string) (*Image, error) {
|
| 62 | 62 |
img.Size = int64(size) |
| 63 | 63 |
} |
| 64 | 64 |
|
| 65 |
- // Check that the filesystem layer exists |
|
| 66 |
- if stat, err := os.Stat(layerPath(root)); err != nil {
|
|
| 67 |
- if os.IsNotExist(err) {
|
|
| 68 |
- return nil, fmt.Errorf("Couldn't load image %s: no filesystem layer", img.ID)
|
|
| 69 |
- } |
|
| 70 |
- return nil, err |
|
| 71 |
- } else if !stat.IsDir() {
|
|
| 72 |
- return nil, fmt.Errorf("Couldn't load image %s: %s is not a directory", img.ID, layerPath(root))
|
|
| 73 |
- } |
|
| 74 | 65 |
return img, nil |
| 75 | 66 |
} |
| 76 | 67 |
|
| 77 |
-func StoreImage(img *Image, jsonData []byte, layerData archive.Archive, root string) error {
|
|
| 78 |
- // Check that root doesn't already exist |
|
| 79 |
- if _, err := os.Stat(root); err == nil {
|
|
| 80 |
- return fmt.Errorf("Image %s already exists", img.ID)
|
|
| 81 |
- } else if !os.IsNotExist(err) {
|
|
| 82 |
- return err |
|
| 83 |
- } |
|
| 68 |
+func StoreImage(img *Image, jsonData []byte, layerData archive.Archive, root, layer string) error {
|
|
| 84 | 69 |
// Store the layer |
| 85 |
- layer := layerPath(root) |
|
| 70 |
+ var ( |
|
| 71 |
+ size int64 |
|
| 72 |
+ err error |
|
| 73 |
+ driver = img.graph.driver |
|
| 74 |
+ ) |
|
| 86 | 75 |
if err := os.MkdirAll(layer, 0755); err != nil {
|
| 87 | 76 |
return err |
| 88 | 77 |
} |
| 89 | 78 |
|
| 90 | 79 |
// If layerData is not nil, unpack it into the new layer |
| 91 | 80 |
if layerData != nil {
|
| 92 |
- start := time.Now() |
|
| 93 |
- utils.Debugf("Start untar layer")
|
|
| 94 |
- if err := archive.Untar(layerData, layer); err != nil {
|
|
| 95 |
- return err |
|
| 81 |
+ if differ, ok := driver.(graphdriver.Differ); ok {
|
|
| 82 |
+ if err := differ.ApplyDiff(img.ID, layerData); err != nil {
|
|
| 83 |
+ return err |
|
| 84 |
+ } |
|
| 85 |
+ |
|
| 86 |
+ if size, err = differ.DiffSize(img.ID); err != nil {
|
|
| 87 |
+ return err |
|
| 88 |
+ } |
|
| 89 |
+ } else {
|
|
| 90 |
+ start := time.Now() |
|
| 91 |
+ utils.Debugf("Start untar layer")
|
|
| 92 |
+ if err := archive.ApplyLayer(layer, layerData); err != nil {
|
|
| 93 |
+ return err |
|
| 94 |
+ } |
|
| 95 |
+ utils.Debugf("Untar time: %vs", time.Now().Sub(start).Seconds())
|
|
| 96 |
+ |
|
| 97 |
+ if img.Parent == "" {
|
|
| 98 |
+ if size, err = utils.TreeSize(layer); err != nil {
|
|
| 99 |
+ return err |
|
| 100 |
+ } |
|
| 101 |
+ } else {
|
|
| 102 |
+ parent, err := driver.Get(img.Parent) |
|
| 103 |
+ if err != nil {
|
|
| 104 |
+ return err |
|
| 105 |
+ } |
|
| 106 |
+ changes, err := archive.ChangesDirs(layer, parent) |
|
| 107 |
+ if err != nil {
|
|
| 108 |
+ return err |
|
| 109 |
+ } |
|
| 110 |
+ if size = archive.ChangesSize(layer, changes); err != nil {
|
|
| 111 |
+ return err |
|
| 112 |
+ } |
|
| 113 |
+ } |
|
| 96 | 114 |
} |
| 97 |
- utils.Debugf("Untar time: %vs", time.Now().Sub(start).Seconds())
|
|
| 98 | 115 |
} |
| 99 | 116 |
|
| 100 | 117 |
// If raw json is provided, then use it |
| ... | ... |
@@ -102,117 +116,60 @@ func StoreImage(img *Image, jsonData []byte, layerData archive.Archive, root str |
| 102 | 102 |
return ioutil.WriteFile(jsonPath(root), jsonData, 0600) |
| 103 | 103 |
} |
| 104 | 104 |
// Otherwise, unmarshal the image |
| 105 |
- jsonData, err := json.Marshal(img) |
|
| 106 |
- if err != nil {
|
|
| 105 |
+ if jsonData, err = json.Marshal(img); err != nil {
|
|
| 107 | 106 |
return err |
| 108 | 107 |
} |
| 109 | 108 |
if err := ioutil.WriteFile(jsonPath(root), jsonData, 0600); err != nil {
|
| 110 | 109 |
return err |
| 111 | 110 |
} |
| 112 | 111 |
|
| 113 |
- return StoreSize(img, root) |
|
| 114 |
-} |
|
| 115 |
- |
|
| 116 |
-func StoreSize(img *Image, root string) error {
|
|
| 117 |
- layer := layerPath(root) |
|
| 118 |
- data := make(map[uint64]bool) |
|
| 119 |
- |
|
| 120 |
- var totalSize int64 |
|
| 121 |
- filepath.Walk(layer, func(path string, fileInfo os.FileInfo, err error) error {
|
|
| 122 |
- size := fileInfo.Size() |
|
| 123 |
- if size == 0 {
|
|
| 124 |
- return nil |
|
| 125 |
- } |
|
| 126 |
- |
|
| 127 |
- inode := fileInfo.Sys().(*syscall.Stat_t).Ino |
|
| 128 |
- if _, entryExists := data[inode]; entryExists {
|
|
| 129 |
- return nil |
|
| 130 |
- } |
|
| 131 |
- data[inode] = false |
|
| 132 |
- |
|
| 133 |
- totalSize += size |
|
| 134 |
- return nil |
|
| 135 |
- }) |
|
| 136 |
- img.Size = totalSize |
|
| 137 |
- |
|
| 138 |
- if err := ioutil.WriteFile(path.Join(root, "layersize"), []byte(strconv.Itoa(int(totalSize))), 0600); err != nil {
|
|
| 139 |
- return nil |
|
| 112 |
+ img.Size = size |
|
| 113 |
+ if err := img.SaveSize(root); err != nil {
|
|
| 114 |
+ return err |
|
| 140 | 115 |
} |
| 141 | 116 |
|
| 142 | 117 |
return nil |
| 143 | 118 |
} |
| 144 | 119 |
|
| 145 |
-func layerPath(root string) string {
|
|
| 146 |
- return path.Join(root, "layer") |
|
| 120 |
+// SaveSize stores the current `size` value of `img` in the directory `root`. |
|
| 121 |
+func (img *Image) SaveSize(root string) error {
|
|
| 122 |
+ if err := ioutil.WriteFile(path.Join(root, "layersize"), []byte(strconv.Itoa(int(img.Size))), 0600); err != nil {
|
|
| 123 |
+ return fmt.Errorf("Error storing image size in %s/layersize: %s", root, err)
|
|
| 124 |
+ } |
|
| 125 |
+ return nil |
|
| 147 | 126 |
} |
| 148 | 127 |
|
| 149 | 128 |
func jsonPath(root string) string {
|
| 150 | 129 |
return path.Join(root, "json") |
| 151 | 130 |
} |
| 152 | 131 |
|
| 153 |
-func MountAUFS(ro []string, rw string, target string) error {
|
|
| 154 |
- // FIXME: Now mount the layers |
|
| 155 |
- rwBranch := fmt.Sprintf("%v=rw", rw)
|
|
| 156 |
- roBranches := "" |
|
| 157 |
- for _, layer := range ro {
|
|
| 158 |
- roBranches += fmt.Sprintf("%v=ro+wh:", layer)
|
|
| 159 |
- } |
|
| 160 |
- branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches)
|
|
| 161 |
- |
|
| 162 |
- branches += ",xino=/dev/shm/aufs.xino" |
|
| 163 |
- |
|
| 164 |
- //if error, try to load aufs kernel module |
|
| 165 |
- if err := mount("none", target, "aufs", 0, branches); err != nil {
|
|
| 166 |
- log.Printf("Kernel does not support AUFS, trying to load the AUFS module with modprobe...")
|
|
| 167 |
- if err := exec.Command("modprobe", "aufs").Run(); err != nil {
|
|
| 168 |
- return fmt.Errorf("Unable to load the AUFS module")
|
|
| 169 |
- } |
|
| 170 |
- log.Printf("...module loaded.")
|
|
| 171 |
- if err := mount("none", target, "aufs", 0, branches); err != nil {
|
|
| 172 |
- return fmt.Errorf("Unable to mount using aufs")
|
|
| 173 |
- } |
|
| 174 |
- } |
|
| 175 |
- return nil |
|
| 176 |
-} |
|
| 177 |
- |
|
| 178 | 132 |
// TarLayer returns a tar archive of the image's filesystem layer. |
| 179 |
-func (img *Image) TarLayer(compression archive.Compression) (archive.Archive, error) {
|
|
| 180 |
- layerPath, err := img.layer() |
|
| 181 |
- if err != nil {
|
|
| 182 |
- return nil, err |
|
| 183 |
- } |
|
| 184 |
- return archive.Tar(layerPath, compression) |
|
| 185 |
-} |
|
| 186 |
- |
|
| 187 |
-func (img *Image) Mount(root, rw string) error {
|
|
| 188 |
- if mounted, err := Mounted(root); err != nil {
|
|
| 189 |
- return err |
|
| 190 |
- } else if mounted {
|
|
| 191 |
- return fmt.Errorf("%s is already mounted", root)
|
|
| 192 |
- } |
|
| 193 |
- layers, err := img.layers() |
|
| 194 |
- if err != nil {
|
|
| 195 |
- return err |
|
| 196 |
- } |
|
| 197 |
- // Create the target directories if they don't exist |
|
| 198 |
- if err := os.Mkdir(root, 0755); err != nil && !os.IsExist(err) {
|
|
| 199 |
- return err |
|
| 200 |
- } |
|
| 201 |
- if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) {
|
|
| 202 |
- return err |
|
| 133 |
+func (img *Image) TarLayer() (archive.Archive, error) {
|
|
| 134 |
+ if img.graph == nil {
|
|
| 135 |
+ return nil, fmt.Errorf("Can't load storage driver for unregistered image %s", img.ID)
|
|
| 203 | 136 |
} |
| 204 |
- if err := MountAUFS(layers, rw, root); err != nil {
|
|
| 205 |
- return err |
|
| 137 |
+ driver := img.graph.driver |
|
| 138 |
+ if differ, ok := driver.(graphdriver.Differ); ok {
|
|
| 139 |
+ return differ.Diff(img.ID) |
|
| 206 | 140 |
} |
| 207 |
- return nil |
|
| 208 |
-} |
|
| 209 | 141 |
|
| 210 |
-func (img *Image) Changes(rw string) ([]Change, error) {
|
|
| 211 |
- layers, err := img.layers() |
|
| 142 |
+ imgFs, err := driver.Get(img.ID) |
|
| 212 | 143 |
if err != nil {
|
| 213 | 144 |
return nil, err |
| 214 | 145 |
} |
| 215 |
- return Changes(layers, rw) |
|
| 146 |
+ if img.Parent == "" {
|
|
| 147 |
+ return archive.Tar(imgFs, archive.Uncompressed) |
|
| 148 |
+ } else {
|
|
| 149 |
+ parentFs, err := driver.Get(img.Parent) |
|
| 150 |
+ if err != nil {
|
|
| 151 |
+ return nil, err |
|
| 152 |
+ } |
|
| 153 |
+ changes, err := archive.ChangesDirs(imgFs, parentFs) |
|
| 154 |
+ if err != nil {
|
|
| 155 |
+ return nil, err |
|
| 156 |
+ } |
|
| 157 |
+ return archive.ExportChanges(imgFs, changes) |
|
| 158 |
+ } |
|
| 216 | 159 |
} |
| 217 | 160 |
|
| 218 | 161 |
func ValidateID(id string) error {
|
| ... | ... |
@@ -250,40 +207,6 @@ func (img *Image) History() ([]*Image, error) {
|
| 250 | 250 |
return parents, nil |
| 251 | 251 |
} |
| 252 | 252 |
|
| 253 |
-// layers returns all the filesystem layers needed to mount an image |
|
| 254 |
-// FIXME: @shykes refactor this function with the new error handling |
|
| 255 |
-// (I'll do it if I have time tonight, I focus on the rest) |
|
| 256 |
-func (img *Image) layers() ([]string, error) {
|
|
| 257 |
- var ( |
|
| 258 |
- list []string |
|
| 259 |
- e error |
|
| 260 |
- ) |
|
| 261 |
- if err := img.WalkHistory( |
|
| 262 |
- func(img *Image) (err error) {
|
|
| 263 |
- if layer, err := img.layer(); err != nil {
|
|
| 264 |
- e = err |
|
| 265 |
- } else if layer != "" {
|
|
| 266 |
- list = append(list, layer) |
|
| 267 |
- } |
|
| 268 |
- return err |
|
| 269 |
- }, |
|
| 270 |
- ); err != nil {
|
|
| 271 |
- return nil, err |
|
| 272 |
- } else if e != nil { // Did an error occur inside the handler?
|
|
| 273 |
- return nil, e |
|
| 274 |
- } |
|
| 275 |
- if len(list) == 0 {
|
|
| 276 |
- return nil, fmt.Errorf("No layer found for image %s\n", img.ID)
|
|
| 277 |
- } |
|
| 278 |
- |
|
| 279 |
- // Inject the dockerinit layer (empty place-holder for mount-binding dockerinit) |
|
| 280 |
- dockerinitLayer, err := img.getDockerInitLayer() |
|
| 281 |
- if err != nil {
|
|
| 282 |
- return nil, err |
|
| 283 |
- } |
|
| 284 |
- return append([]string{dockerinitLayer}, list...), nil
|
|
| 285 |
-} |
|
| 286 |
- |
|
| 287 | 253 |
func (img *Image) WalkHistory(handler func(*Image) error) (err error) {
|
| 288 | 254 |
currentImg := img |
| 289 | 255 |
for currentImg != nil {
|
| ... | ... |
@@ -310,13 +233,6 @@ func (img *Image) GetParent() (*Image, error) {
|
| 310 | 310 |
return img.graph.Get(img.Parent) |
| 311 | 311 |
} |
| 312 | 312 |
|
| 313 |
-func (img *Image) getDockerInitLayer() (string, error) {
|
|
| 314 |
- if img.graph == nil {
|
|
| 315 |
- return "", fmt.Errorf("Can't lookup dockerinit layer of unregistered image")
|
|
| 316 |
- } |
|
| 317 |
- return img.graph.getDockerInitLayer() |
|
| 318 |
-} |
|
| 319 |
- |
|
| 320 | 313 |
func (img *Image) root() (string, error) {
|
| 321 | 314 |
if img.graph == nil {
|
| 322 | 315 |
return "", fmt.Errorf("Can't lookup root of unregistered image")
|
| ... | ... |
@@ -324,15 +240,6 @@ func (img *Image) root() (string, error) {
|
| 324 | 324 |
return img.graph.imageRoot(img.ID), nil |
| 325 | 325 |
} |
| 326 | 326 |
|
| 327 |
-// Return the path of an image's layer |
|
| 328 |
-func (img *Image) layer() (string, error) {
|
|
| 329 |
- root, err := img.root() |
|
| 330 |
- if err != nil {
|
|
| 331 |
- return "", err |
|
| 332 |
- } |
|
| 333 |
- return layerPath(root), nil |
|
| 334 |
-} |
|
| 335 |
- |
|
| 336 | 327 |
func (img *Image) getParentsSize(size int64) int64 {
|
| 337 | 328 |
parentImage, err := img.GetParent() |
| 338 | 329 |
if err != nil || parentImage == nil {
|
| ... | ... |
@@ -342,6 +249,25 @@ func (img *Image) getParentsSize(size int64) int64 {
|
| 342 | 342 |
return parentImage.getParentsSize(size) |
| 343 | 343 |
} |
| 344 | 344 |
|
| 345 |
+// Depth returns the number of parents for a |
|
| 346 |
+// current image |
|
| 347 |
+func (img *Image) Depth() (int, error) {
|
|
| 348 |
+ var ( |
|
| 349 |
+ count = 0 |
|
| 350 |
+ parent = img |
|
| 351 |
+ err error |
|
| 352 |
+ ) |
|
| 353 |
+ |
|
| 354 |
+ for parent != nil {
|
|
| 355 |
+ count++ |
|
| 356 |
+ parent, err = parent.GetParent() |
|
| 357 |
+ if err != nil {
|
|
| 358 |
+ return -1, err |
|
| 359 |
+ } |
|
| 360 |
+ } |
|
| 361 |
+ return count, nil |
|
| 362 |
+} |
|
| 363 |
+ |
|
| 345 | 364 |
// Build an Image object from raw json data |
| 346 | 365 |
func NewImgJSON(src []byte) (*Image, error) {
|
| 347 | 366 |
ret := &Image{}
|
| ... | ... |
@@ -840,13 +840,12 @@ func TestImagesTree(t *testing.T) {
|
| 840 | 840 |
t.Fatal(err) |
| 841 | 841 |
} |
| 842 | 842 |
cmdOutput := string(cmdOutputBytes) |
| 843 |
- |
|
| 844 | 843 |
regexpStrings := []string{
|
| 845 | 844 |
fmt.Sprintf("└─%s Size: (\\d+.\\d+ MB) \\(virtual \\d+.\\d+ MB\\) Tags: %s:latest", unitTestImageIDShort, unitTestImageName),
|
| 846 |
- "(?m)^ └─[0-9a-f]+", |
|
| 847 |
- "(?m)^ └─[0-9a-f]+", |
|
| 848 |
- "(?m)^ └─[0-9a-f]+", |
|
| 849 |
- fmt.Sprintf(" └─%s Size: \\d+ B \\(virtual \\d+.\\d+ MB\\) Tags: test:latest", utils.TruncateID(image.ID)),
|
|
| 845 |
+ "(?m) └─[0-9a-f]+.*", |
|
| 846 |
+ "(?m) └─[0-9a-f]+.*", |
|
| 847 |
+ "(?m) └─[0-9a-f]+.*", |
|
| 848 |
+ fmt.Sprintf("(?m)^ └─%s Size: \\d+.\\d+ MB \\(virtual \\d+.\\d+ MB\\) Tags: test:latest", utils.TruncateID(image.ID)),
|
|
| 850 | 849 |
} |
| 851 | 850 |
|
| 852 | 851 |
compiledRegexps := []*regexp.Regexp{}
|
| ... | ... |
@@ -172,7 +172,7 @@ func TestDiff(t *testing.T) {
|
| 172 | 172 |
// Commit the container |
| 173 | 173 |
img, err := runtime.Commit(container1, "", "", "unit test commited image - diff", "", nil) |
| 174 | 174 |
if err != nil {
|
| 175 |
- t.Error(err) |
|
| 175 |
+ t.Fatal(err) |
|
| 176 | 176 |
} |
| 177 | 177 |
|
| 178 | 178 |
// Create a new container from the commited image |
| ... | ... |
@@ -2,6 +2,7 @@ package docker |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"github.com/dotcloud/docker" |
| 5 |
+ "github.com/dotcloud/docker/graphdriver" |
|
| 5 | 6 |
"io/ioutil" |
| 6 | 7 |
"os" |
| 7 | 8 |
"path" |
| ... | ... |
@@ -9,8 +10,10 @@ import ( |
| 9 | 9 |
) |
| 10 | 10 |
|
| 11 | 11 |
func TestMount(t *testing.T) {
|
| 12 |
- graph := tempGraph(t) |
|
| 12 |
+ graph, driver := tempGraph(t) |
|
| 13 | 13 |
defer os.RemoveAll(graph.Root) |
| 14 |
+ defer driver.Cleanup() |
|
| 15 |
+ |
|
| 14 | 16 |
archive, err := fakeTar() |
| 15 | 17 |
if err != nil {
|
| 16 | 18 |
t.Fatal(err) |
| ... | ... |
@@ -32,26 +35,25 @@ func TestMount(t *testing.T) {
|
| 32 | 32 |
if err := os.MkdirAll(rw, 0700); err != nil {
|
| 33 | 33 |
t.Fatal(err) |
| 34 | 34 |
} |
| 35 |
- if err := image.Mount(rootfs, rw); err != nil {
|
|
| 35 |
+ |
|
| 36 |
+ if _, err := driver.Get(image.ID); err != nil {
|
|
| 36 | 37 |
t.Fatal(err) |
| 37 | 38 |
} |
| 38 |
- // FIXME: test for mount contents |
|
| 39 |
- defer func() {
|
|
| 40 |
- if err := docker.Unmount(rootfs); err != nil {
|
|
| 41 |
- t.Error(err) |
|
| 42 |
- } |
|
| 43 |
- }() |
|
| 44 | 39 |
} |
| 45 | 40 |
|
| 46 | 41 |
//FIXME: duplicate |
| 47 |
-func tempGraph(t *testing.T) *docker.Graph {
|
|
| 42 |
+func tempGraph(t *testing.T) (*docker.Graph, graphdriver.Driver) {
|
|
| 48 | 43 |
tmp, err := ioutil.TempDir("", "docker-graph-")
|
| 49 | 44 |
if err != nil {
|
| 50 | 45 |
t.Fatal(err) |
| 51 | 46 |
} |
| 52 |
- graph, err := docker.NewGraph(tmp) |
|
| 47 |
+ driver, err := graphdriver.New(tmp) |
|
| 48 |
+ if err != nil {
|
|
| 49 |
+ t.Fatal(err) |
|
| 50 |
+ } |
|
| 51 |
+ graph, err := docker.NewGraph(tmp, driver) |
|
| 53 | 52 |
if err != nil {
|
| 54 | 53 |
t.Fatal(err) |
| 55 | 54 |
} |
| 56 |
- return graph |
|
| 55 |
+ return graph, driver |
|
| 57 | 56 |
} |
| 58 | 57 |
deleted file mode 100644 |
| ... | ... |
@@ -1,53 +0,0 @@ |
| 1 |
-package docker |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "fmt" |
|
| 5 |
- "github.com/dotcloud/docker/utils" |
|
| 6 |
- "os" |
|
| 7 |
- "os/exec" |
|
| 8 |
- "path/filepath" |
|
| 9 |
- "syscall" |
|
| 10 |
- "time" |
|
| 11 |
-) |
|
| 12 |
- |
|
| 13 |
-func Unmount(target string) error {
|
|
| 14 |
- if err := exec.Command("auplink", target, "flush").Run(); err != nil {
|
|
| 15 |
- utils.Errorf("[warning]: couldn't run auplink before unmount: %s", err)
|
|
| 16 |
- } |
|
| 17 |
- if err := syscall.Unmount(target, 0); err != nil {
|
|
| 18 |
- return err |
|
| 19 |
- } |
|
| 20 |
- // Even though we just unmounted the filesystem, AUFS will prevent deleting the mntpoint |
|
| 21 |
- // for some time. We'll just keep retrying until it succeeds. |
|
| 22 |
- for retries := 0; retries < 1000; retries++ {
|
|
| 23 |
- err := os.Remove(target) |
|
| 24 |
- if err == nil {
|
|
| 25 |
- // rm mntpoint succeeded |
|
| 26 |
- return nil |
|
| 27 |
- } |
|
| 28 |
- if os.IsNotExist(err) {
|
|
| 29 |
- // mntpoint doesn't exist anymore. Success. |
|
| 30 |
- return nil |
|
| 31 |
- } |
|
| 32 |
- // fmt.Printf("(%v) Remove %v returned: %v\n", retries, target, err)
|
|
| 33 |
- time.Sleep(10 * time.Millisecond) |
|
| 34 |
- } |
|
| 35 |
- return fmt.Errorf("Umount: Failed to umount %v", target)
|
|
| 36 |
-} |
|
| 37 |
- |
|
| 38 |
-func Mounted(mountpoint string) (bool, error) {
|
|
| 39 |
- mntpoint, err := os.Stat(mountpoint) |
|
| 40 |
- if err != nil {
|
|
| 41 |
- if os.IsNotExist(err) {
|
|
| 42 |
- return false, nil |
|
| 43 |
- } |
|
| 44 |
- return false, err |
|
| 45 |
- } |
|
| 46 |
- parent, err := os.Stat(filepath.Join(mountpoint, "..")) |
|
| 47 |
- if err != nil {
|
|
| 48 |
- return false, err |
|
| 49 |
- } |
|
| 50 |
- mntpointSt := mntpoint.Sys().(*syscall.Stat_t) |
|
| 51 |
- parentSt := parent.Sys().(*syscall.Stat_t) |
|
| 52 |
- return mntpointSt.Dev != parentSt.Dev, nil |
|
| 53 |
-} |
| ... | ... |
@@ -5,7 +5,12 @@ import ( |
| 5 | 5 |
"container/list" |
| 6 | 6 |
"database/sql" |
| 7 | 7 |
"fmt" |
| 8 |
- "github.com/dotcloud/docker/gograph" |
|
| 8 |
+ "github.com/dotcloud/docker/archive" |
|
| 9 |
+ "github.com/dotcloud/docker/graphdb" |
|
| 10 |
+ "github.com/dotcloud/docker/graphdriver" |
|
| 11 |
+ "github.com/dotcloud/docker/graphdriver/aufs" |
|
| 12 |
+ _ "github.com/dotcloud/docker/graphdriver/devmapper" |
|
| 13 |
+ _ "github.com/dotcloud/docker/graphdriver/vfs" |
|
| 9 | 14 |
"github.com/dotcloud/docker/utils" |
| 10 | 15 |
"io" |
| 11 | 16 |
"io/ioutil" |
| ... | ... |
@@ -19,6 +24,9 @@ import ( |
| 19 | 19 |
"time" |
| 20 | 20 |
) |
| 21 | 21 |
|
| 22 |
+// Set the max depth to the aufs restriction |
|
| 23 |
+const MaxImageDepth = 42 |
|
| 24 |
+ |
|
| 22 | 25 |
var defaultDns = []string{"8.8.8.8", "8.8.4.4"}
|
| 23 | 26 |
|
| 24 | 27 |
type Capabilities struct {
|
| ... | ... |
@@ -39,7 +47,8 @@ type Runtime struct {
|
| 39 | 39 |
volumes *Graph |
| 40 | 40 |
srv *Server |
| 41 | 41 |
config *DaemonConfig |
| 42 |
- containerGraph *gograph.Database |
|
| 42 |
+ containerGraph *graphdb.Database |
|
| 43 |
+ driver graphdriver.Driver |
|
| 43 | 44 |
} |
| 44 | 45 |
|
| 45 | 46 |
// List returns an array of all containers registered in the runtime. |
| ... | ... |
@@ -118,6 +127,13 @@ func (runtime *Runtime) Register(container *Container) error {
|
| 118 | 118 |
return err |
| 119 | 119 |
} |
| 120 | 120 |
|
| 121 |
+ // Get the root filesystem from the driver |
|
| 122 |
+ rootfs, err := runtime.driver.Get(container.ID) |
|
| 123 |
+ if err != nil {
|
|
| 124 |
+ return fmt.Errorf("Error getting container filesystem %s from driver %s: %s", container.ID, runtime.driver, err)
|
|
| 125 |
+ } |
|
| 126 |
+ container.rootfs = rootfs |
|
| 127 |
+ |
|
| 121 | 128 |
container.runtime = runtime |
| 122 | 129 |
|
| 123 | 130 |
// Attach to stdout and stderr |
| ... | ... |
@@ -216,12 +232,8 @@ func (runtime *Runtime) Destroy(container *Container) error {
|
| 216 | 216 |
return err |
| 217 | 217 |
} |
| 218 | 218 |
|
| 219 |
- if mounted, err := container.Mounted(); err != nil {
|
|
| 220 |
- return err |
|
| 221 |
- } else if mounted {
|
|
| 222 |
- if err := container.Unmount(); err != nil {
|
|
| 223 |
- return fmt.Errorf("Unable to unmount container %v: %v", container.ID, err)
|
|
| 224 |
- } |
|
| 219 |
+ if err := runtime.driver.Remove(container.ID); err != nil {
|
|
| 220 |
+ return fmt.Errorf("Driver %s failed to remove root filesystem %s: %s", runtime.driver, container.ID, err)
|
|
| 225 | 221 |
} |
| 226 | 222 |
|
| 227 | 223 |
if _, err := runtime.containerGraph.Purge(container.ID); err != nil {
|
| ... | ... |
@@ -247,6 +259,7 @@ func (runtime *Runtime) restore() error {
|
| 247 | 247 |
return err |
| 248 | 248 |
} |
| 249 | 249 |
containers := make(map[string]*Container) |
| 250 |
+ currentDriver := runtime.driver.String() |
|
| 250 | 251 |
|
| 251 | 252 |
for i, v := range dir {
|
| 252 | 253 |
id := v.Name() |
| ... | ... |
@@ -258,8 +271,14 @@ func (runtime *Runtime) restore() error {
|
| 258 | 258 |
utils.Errorf("Failed to load container %v: %v", id, err)
|
| 259 | 259 |
continue |
| 260 | 260 |
} |
| 261 |
- utils.Debugf("Loaded container %v", container.ID)
|
|
| 262 |
- containers[container.ID] = container |
|
| 261 |
+ |
|
| 262 |
+ // Ignore the container if it does not support the current driver being used by the graph |
|
| 263 |
+ if container.Driver == "" && currentDriver == "aufs" || container.Driver == currentDriver {
|
|
| 264 |
+ utils.Debugf("Loaded container %v", container.ID)
|
|
| 265 |
+ containers[container.ID] = container |
|
| 266 |
+ } else {
|
|
| 267 |
+ utils.Debugf("Cannot load container %s because it was created with another graph driver.", container.ID)
|
|
| 268 |
+ } |
|
| 263 | 269 |
} |
| 264 | 270 |
|
| 265 | 271 |
register := func(container *Container) {
|
| ... | ... |
@@ -344,6 +363,17 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin |
| 344 | 344 |
return nil, nil, err |
| 345 | 345 |
} |
| 346 | 346 |
|
| 347 |
+ // We add 2 layers to the depth because the container's rw and |
|
| 348 |
+ // init layer add to the restriction |
|
| 349 |
+ depth, err := img.Depth() |
|
| 350 |
+ if err != nil {
|
|
| 351 |
+ return nil, nil, err |
|
| 352 |
+ } |
|
| 353 |
+ |
|
| 354 |
+ if depth+2 >= MaxImageDepth {
|
|
| 355 |
+ return nil, nil, fmt.Errorf("Cannot create container with more than %d parents", MaxImageDepth)
|
|
| 356 |
+ } |
|
| 357 |
+ |
|
| 347 | 358 |
checkDeprecatedExpose := func(config *Config) bool {
|
| 348 | 359 |
if config != nil {
|
| 349 | 360 |
if config.PortSpecs != nil {
|
| ... | ... |
@@ -431,6 +461,7 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin |
| 431 | 431 |
// FIXME: do we need to store this in the container? |
| 432 | 432 |
SysInitPath: sysInitPath, |
| 433 | 433 |
Name: name, |
| 434 |
+ Driver: runtime.driver.String(), |
|
| 434 | 435 |
} |
| 435 | 436 |
container.root = runtime.containerRoot(container.ID) |
| 436 | 437 |
// Step 1: create the container directory. |
| ... | ... |
@@ -439,6 +470,21 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin |
| 439 | 439 |
return nil, nil, err |
| 440 | 440 |
} |
| 441 | 441 |
|
| 442 |
+ initID := fmt.Sprintf("%s-init", container.ID)
|
|
| 443 |
+ if err := runtime.driver.Create(initID, img.ID); err != nil {
|
|
| 444 |
+ return nil, nil, err |
|
| 445 |
+ } |
|
| 446 |
+ initPath, err := runtime.driver.Get(initID) |
|
| 447 |
+ if err != nil {
|
|
| 448 |
+ return nil, nil, err |
|
| 449 |
+ } |
|
| 450 |
+ if err := setupInitLayer(initPath); err != nil {
|
|
| 451 |
+ return nil, nil, err |
|
| 452 |
+ } |
|
| 453 |
+ |
|
| 454 |
+ if err := runtime.driver.Create(container.ID, initID); err != nil {
|
|
| 455 |
+ return nil, nil, err |
|
| 456 |
+ } |
|
| 442 | 457 |
resolvConf, err := utils.GetResolvConf() |
| 443 | 458 |
if err != nil {
|
| 444 | 459 |
return nil, nil, err |
| ... | ... |
@@ -549,7 +595,7 @@ func (runtime *Runtime) Children(name string) (map[string]*Container, error) {
|
| 549 | 549 |
} |
| 550 | 550 |
children := make(map[string]*Container) |
| 551 | 551 |
|
| 552 |
- err = runtime.containerGraph.Walk(name, func(p string, e *gograph.Entity) error {
|
|
| 552 |
+ err = runtime.containerGraph.Walk(name, func(p string, e *graphdb.Entity) error {
|
|
| 553 | 553 |
c := runtime.Get(e.ID()) |
| 554 | 554 |
if c == nil {
|
| 555 | 555 |
return fmt.Errorf("Could not get container for name %s and id %s", e.ID(), p)
|
| ... | ... |
@@ -584,24 +630,48 @@ func NewRuntime(config *DaemonConfig) (*Runtime, error) {
|
| 584 | 584 |
} |
| 585 | 585 |
|
| 586 | 586 |
func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
|
| 587 |
+ |
|
| 588 |
+ // Set the default driver |
|
| 589 |
+ graphdriver.DefaultDriver = config.GraphDriver |
|
| 590 |
+ |
|
| 591 |
+ // Load storage driver |
|
| 592 |
+ driver, err := graphdriver.New(config.Root) |
|
| 593 |
+ if err != nil {
|
|
| 594 |
+ return nil, err |
|
| 595 |
+ } |
|
| 596 |
+ utils.Debugf("Using graph driver %s", driver)
|
|
| 597 |
+ |
|
| 587 | 598 |
runtimeRepo := path.Join(config.Root, "containers") |
| 588 | 599 |
|
| 589 | 600 |
if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) {
|
| 590 | 601 |
return nil, err |
| 591 | 602 |
} |
| 592 | 603 |
|
| 604 |
+ if ad, ok := driver.(*aufs.Driver); ok {
|
|
| 605 |
+ if err := ad.Migrate(config.Root, setupInitLayer); err != nil {
|
|
| 606 |
+ return nil, err |
|
| 607 |
+ } |
|
| 608 |
+ } |
|
| 609 |
+ |
|
| 593 | 610 |
if err := linkLxcStart(config.Root); err != nil {
|
| 594 | 611 |
return nil, err |
| 595 | 612 |
} |
| 596 |
- g, err := NewGraph(path.Join(config.Root, "graph")) |
|
| 613 |
+ g, err := NewGraph(path.Join(config.Root, "graph"), driver) |
|
| 597 | 614 |
if err != nil {
|
| 598 | 615 |
return nil, err |
| 599 | 616 |
} |
| 600 |
- volumes, err := NewGraph(path.Join(config.Root, "volumes")) |
|
| 617 |
+ |
|
| 618 |
+ // We don't want to use a complex driver like aufs or devmapper |
|
| 619 |
+ // for volumes, just a plain filesystem |
|
| 620 |
+ volumesDriver, err := graphdriver.GetDriver("vfs", config.Root)
|
|
| 601 | 621 |
if err != nil {
|
| 602 | 622 |
return nil, err |
| 603 | 623 |
} |
| 604 |
- repositories, err := NewTagStore(path.Join(config.Root, "repositories"), g) |
|
| 624 |
+ volumes, err := NewGraph(path.Join(config.Root, "volumes"), volumesDriver) |
|
| 625 |
+ if err != nil {
|
|
| 626 |
+ return nil, err |
|
| 627 |
+ } |
|
| 628 |
+ repositories, err := NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g) |
|
| 605 | 629 |
if err != nil {
|
| 606 | 630 |
return nil, fmt.Errorf("Couldn't create Tag store: %s", err)
|
| 607 | 631 |
} |
| ... | ... |
@@ -613,20 +683,20 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
|
| 613 | 613 |
return nil, err |
| 614 | 614 |
} |
| 615 | 615 |
|
| 616 |
- gographPath := path.Join(config.Root, "linkgraph.db") |
|
| 616 |
+ graphdbPath := path.Join(config.Root, "linkgraph.db") |
|
| 617 | 617 |
initDatabase := false |
| 618 |
- if _, err := os.Stat(gographPath); err != nil {
|
|
| 618 |
+ if _, err := os.Stat(graphdbPath); err != nil {
|
|
| 619 | 619 |
if os.IsNotExist(err) {
|
| 620 | 620 |
initDatabase = true |
| 621 | 621 |
} else {
|
| 622 | 622 |
return nil, err |
| 623 | 623 |
} |
| 624 | 624 |
} |
| 625 |
- conn, err := sql.Open("sqlite3", gographPath)
|
|
| 625 |
+ conn, err := sql.Open("sqlite3", graphdbPath)
|
|
| 626 | 626 |
if err != nil {
|
| 627 | 627 |
return nil, err |
| 628 | 628 |
} |
| 629 |
- graph, err := gograph.NewDatabase(conn, initDatabase) |
|
| 629 |
+ graph, err := graphdb.NewDatabase(conn, initDatabase) |
|
| 630 | 630 |
if err != nil {
|
| 631 | 631 |
return nil, err |
| 632 | 632 |
} |
| ... | ... |
@@ -642,6 +712,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
|
| 642 | 642 |
volumes: volumes, |
| 643 | 643 |
config: config, |
| 644 | 644 |
containerGraph: graph, |
| 645 |
+ driver: driver, |
|
| 645 | 646 |
} |
| 646 | 647 |
|
| 647 | 648 |
if err := runtime.restore(); err != nil {
|
| ... | ... |
@@ -651,8 +722,76 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) {
|
| 651 | 651 |
} |
| 652 | 652 |
|
| 653 | 653 |
func (runtime *Runtime) Close() error {
|
| 654 |
- runtime.networkManager.Close() |
|
| 655 |
- return runtime.containerGraph.Close() |
|
| 654 |
+ errorsStrings := []string{}
|
|
| 655 |
+ if err := runtime.networkManager.Close(); err != nil {
|
|
| 656 |
+ utils.Errorf("runtime.networkManager.Close(): %s", err.Error())
|
|
| 657 |
+ errorsStrings = append(errorsStrings, err.Error()) |
|
| 658 |
+ } |
|
| 659 |
+ if err := runtime.driver.Cleanup(); err != nil {
|
|
| 660 |
+ utils.Errorf("runtime.driver.Cleanup(): %s", err.Error())
|
|
| 661 |
+ errorsStrings = append(errorsStrings, err.Error()) |
|
| 662 |
+ } |
|
| 663 |
+ if err := runtime.containerGraph.Close(); err != nil {
|
|
| 664 |
+ utils.Errorf("runtime.containerGraph.Close(): %s", err.Error())
|
|
| 665 |
+ errorsStrings = append(errorsStrings, err.Error()) |
|
| 666 |
+ } |
|
| 667 |
+ if len(errorsStrings) > 0 {
|
|
| 668 |
+ return fmt.Errorf("%s", strings.Join(errorsStrings, ", "))
|
|
| 669 |
+ } |
|
| 670 |
+ return nil |
|
| 671 |
+} |
|
| 672 |
+ |
|
| 673 |
+func (runtime *Runtime) Mount(container *Container) error {
|
|
| 674 |
+ dir, err := runtime.driver.Get(container.ID) |
|
| 675 |
+ if err != nil {
|
|
| 676 |
+ return fmt.Errorf("Error getting container %s from driver %s: %s", container.ID, runtime.driver, err)
|
|
| 677 |
+ } |
|
| 678 |
+ if container.rootfs == "" {
|
|
| 679 |
+ container.rootfs = dir |
|
| 680 |
+ } else if container.rootfs != dir {
|
|
| 681 |
+ return fmt.Errorf("Error: driver %s is returning inconsistent paths for container %s ('%s' then '%s')",
|
|
| 682 |
+ runtime.driver, container.ID, container.rootfs, dir) |
|
| 683 |
+ } |
|
| 684 |
+ return nil |
|
| 685 |
+} |
|
| 686 |
+ |
|
| 687 |
+func (runtime *Runtime) Unmount(container *Container) error {
|
|
| 688 |
+ // FIXME: Unmount is deprecated because drivers are responsible for mounting |
|
| 689 |
+ // and unmounting when necessary. Use driver.Remove() instead. |
|
| 690 |
+ return nil |
|
| 691 |
+} |
|
| 692 |
+ |
|
| 693 |
+func (runtime *Runtime) Changes(container *Container) ([]archive.Change, error) {
|
|
| 694 |
+ if differ, ok := runtime.driver.(graphdriver.Differ); ok {
|
|
| 695 |
+ return differ.Changes(container.ID) |
|
| 696 |
+ } |
|
| 697 |
+ cDir, err := runtime.driver.Get(container.ID) |
|
| 698 |
+ if err != nil {
|
|
| 699 |
+ return nil, fmt.Errorf("Error getting container rootfs %s from driver %s: %s", container.ID, container.runtime.driver, err)
|
|
| 700 |
+ } |
|
| 701 |
+ initDir, err := runtime.driver.Get(container.ID + "-init") |
|
| 702 |
+ if err != nil {
|
|
| 703 |
+ return nil, fmt.Errorf("Error getting container init rootfs %s from driver %s: %s", container.ID, container.runtime.driver, err)
|
|
| 704 |
+ } |
|
| 705 |
+ return archive.ChangesDirs(cDir, initDir) |
|
| 706 |
+} |
|
| 707 |
+ |
|
| 708 |
+func (runtime *Runtime) Diff(container *Container) (archive.Archive, error) {
|
|
| 709 |
+ if differ, ok := runtime.driver.(graphdriver.Differ); ok {
|
|
| 710 |
+ return differ.Diff(container.ID) |
|
| 711 |
+ } |
|
| 712 |
+ |
|
| 713 |
+ changes, err := runtime.Changes(container) |
|
| 714 |
+ if err != nil {
|
|
| 715 |
+ return nil, err |
|
| 716 |
+ } |
|
| 717 |
+ |
|
| 718 |
+ cDir, err := runtime.driver.Get(container.ID) |
|
| 719 |
+ if err != nil {
|
|
| 720 |
+ return nil, fmt.Errorf("Error getting container rootfs %s from driver %s: %s", container.ID, container.runtime.driver, err)
|
|
| 721 |
+ } |
|
| 722 |
+ |
|
| 723 |
+ return archive.ExportChanges(cDir, changes) |
|
| 656 | 724 |
} |
| 657 | 725 |
|
| 658 | 726 |
// Nuke kills all containers then removes all content |
| ... | ... |
@@ -8,7 +8,7 @@ import ( |
| 8 | 8 |
"github.com/dotcloud/docker/archive" |
| 9 | 9 |
"github.com/dotcloud/docker/auth" |
| 10 | 10 |
"github.com/dotcloud/docker/engine" |
| 11 |
- "github.com/dotcloud/docker/gograph" |
|
| 11 |
+ "github.com/dotcloud/docker/graphdb" |
|
| 12 | 12 |
"github.com/dotcloud/docker/registry" |
| 13 | 13 |
"github.com/dotcloud/docker/utils" |
| 14 | 14 |
"io" |
| ... | ... |
@@ -285,7 +285,7 @@ func (srv *Server) exportImage(image *Image, tempdir string) error {
|
| 285 | 285 |
} |
| 286 | 286 |
|
| 287 | 287 |
// serialize filesystem |
| 288 |
- fs, err := archive.Tar(path.Join(srv.runtime.graph.Root, i.ID, "layer"), archive.Uncompressed) |
|
| 288 |
+ fs, err := i.TarLayer() |
|
| 289 | 289 |
if err != nil {
|
| 290 | 290 |
return err |
| 291 | 291 |
} |
| ... | ... |
@@ -342,7 +342,7 @@ func (srv *Server) ImageLoad(in io.Reader) error {
|
| 342 | 342 |
if err := os.Mkdir(repoDir, os.ModeDir); err != nil {
|
| 343 | 343 |
return err |
| 344 | 344 |
} |
| 345 |
- if err := archive.Untar(repoFile, repoDir); err != nil {
|
|
| 345 |
+ if err := archive.Untar(repoFile, repoDir, nil); err != nil {
|
|
| 346 | 346 |
return err |
| 347 | 347 |
} |
| 348 | 348 |
|
| ... | ... |
@@ -596,6 +596,8 @@ func (srv *Server) DockerInfo() *APIInfo {
|
| 596 | 596 |
return &APIInfo{
|
| 597 | 597 |
Containers: len(srv.runtime.List()), |
| 598 | 598 |
Images: imgcount, |
| 599 |
+ Driver: srv.runtime.driver.String(), |
|
| 600 |
+ DriverStatus: srv.runtime.driver.Status(), |
|
| 599 | 601 |
MemoryLimit: srv.runtime.capabilities.MemoryLimit, |
| 600 | 602 |
SwapLimit: srv.runtime.capabilities.SwapLimit, |
| 601 | 603 |
IPv4Forwarding: !srv.runtime.capabilities.IPv4ForwardingDisabled, |
| ... | ... |
@@ -678,7 +680,7 @@ func (srv *Server) ContainerTop(name, psArgs string) (*APITop, error) {
|
| 678 | 678 |
return nil, fmt.Errorf("No such container: %s", name)
|
| 679 | 679 |
} |
| 680 | 680 |
|
| 681 |
-func (srv *Server) ContainerChanges(name string) ([]Change, error) {
|
|
| 681 |
+func (srv *Server) ContainerChanges(name string) ([]archive.Change, error) {
|
|
| 682 | 682 |
if container := srv.runtime.Get(name); container != nil {
|
| 683 | 683 |
return container.Changes() |
| 684 | 684 |
} |
| ... | ... |
@@ -691,7 +693,7 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API |
| 691 | 691 |
out := []APIContainers{}
|
| 692 | 692 |
|
| 693 | 693 |
names := map[string][]string{}
|
| 694 |
- srv.runtime.containerGraph.Walk("/", func(p string, e *gograph.Entity) error {
|
|
| 694 |
+ srv.runtime.containerGraph.Walk("/", func(p string, e *graphdb.Entity) error {
|
|
| 695 | 695 |
names[e.ID()] = append(names[e.ID()], p) |
| 696 | 696 |
return nil |
| 697 | 697 |
}, -1) |
| ... | ... |
@@ -763,12 +765,13 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin |
| 763 | 763 |
// FIXME: Try to stream the images? |
| 764 | 764 |
// FIXME: Launch the getRemoteImage() in goroutines |
| 765 | 765 |
|
| 766 |
- for _, id := range history {
|
|
| 766 |
+ for i := len(history) - 1; i >= 0; i-- {
|
|
| 767 |
+ id := history[i] |
|
| 767 | 768 |
|
| 768 | 769 |
// ensure no two downloads of the same layer happen at the same time |
| 769 |
- if err := srv.poolAdd("pull", "layer:"+id); err != nil {
|
|
| 770 |
+ if c, err := srv.poolAdd("pull", "layer:"+id); err != nil {
|
|
| 770 | 771 |
utils.Errorf("Image (id: %s) pull is already running, skipping: %v", id, err)
|
| 771 |
- return nil |
|
| 772 |
+ <-c |
|
| 772 | 773 |
} |
| 773 | 774 |
defer srv.poolRemove("pull", "layer:"+id)
|
| 774 | 775 |
|
| ... | ... |
@@ -863,7 +866,7 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName |
| 863 | 863 |
} |
| 864 | 864 |
|
| 865 | 865 |
// ensure no two downloads of the same image happen at the same time |
| 866 |
- if err := srv.poolAdd("pull", "img:"+img.ID); err != nil {
|
|
| 866 |
+ if _, err := srv.poolAdd("pull", "img:"+img.ID); err != nil {
|
|
| 867 | 867 |
utils.Errorf("Image (id: %s) pull is already running, skipping: %v", img.ID, err)
|
| 868 | 868 |
if parallel {
|
| 869 | 869 |
errors <- nil |
| ... | ... |
@@ -934,28 +937,27 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName |
| 934 | 934 |
return nil |
| 935 | 935 |
} |
| 936 | 936 |
|
| 937 |
-func (srv *Server) poolAdd(kind, key string) error {
|
|
| 937 |
+func (srv *Server) poolAdd(kind, key string) (chan struct{}, error) {
|
|
| 938 | 938 |
srv.Lock() |
| 939 | 939 |
defer srv.Unlock() |
| 940 | 940 |
|
| 941 |
- if _, exists := srv.pullingPool[key]; exists {
|
|
| 942 |
- return fmt.Errorf("pull %s is already in progress", key)
|
|
| 941 |
+ if c, exists := srv.pullingPool[key]; exists {
|
|
| 942 |
+ return c, fmt.Errorf("pull %s is already in progress", key)
|
|
| 943 | 943 |
} |
| 944 |
- if _, exists := srv.pushingPool[key]; exists {
|
|
| 945 |
- return fmt.Errorf("push %s is already in progress", key)
|
|
| 944 |
+ if c, exists := srv.pushingPool[key]; exists {
|
|
| 945 |
+ return c, fmt.Errorf("push %s is already in progress", key)
|
|
| 946 | 946 |
} |
| 947 | 947 |
|
| 948 |
+ c := make(chan struct{})
|
|
| 948 | 949 |
switch kind {
|
| 949 | 950 |
case "pull": |
| 950 |
- srv.pullingPool[key] = struct{}{}
|
|
| 951 |
- break |
|
| 951 |
+ srv.pullingPool[key] = c |
|
| 952 | 952 |
case "push": |
| 953 |
- srv.pushingPool[key] = struct{}{}
|
|
| 954 |
- break |
|
| 953 |
+ srv.pushingPool[key] = c |
|
| 955 | 954 |
default: |
| 956 |
- return fmt.Errorf("Unknown pool type")
|
|
| 955 |
+ return nil, fmt.Errorf("Unknown pool type")
|
|
| 957 | 956 |
} |
| 958 |
- return nil |
|
| 957 |
+ return c, nil |
|
| 959 | 958 |
} |
| 960 | 959 |
|
| 961 | 960 |
func (srv *Server) poolRemove(kind, key string) error {
|
| ... | ... |
@@ -963,11 +965,15 @@ func (srv *Server) poolRemove(kind, key string) error {
|
| 963 | 963 |
defer srv.Unlock() |
| 964 | 964 |
switch kind {
|
| 965 | 965 |
case "pull": |
| 966 |
- delete(srv.pullingPool, key) |
|
| 967 |
- break |
|
| 966 |
+ if c, exists := srv.pullingPool[key]; exists {
|
|
| 967 |
+ close(c) |
|
| 968 |
+ delete(srv.pullingPool, key) |
|
| 969 |
+ } |
|
| 968 | 970 |
case "push": |
| 969 |
- delete(srv.pushingPool, key) |
|
| 970 |
- break |
|
| 971 |
+ if c, exists := srv.pushingPool[key]; exists {
|
|
| 972 |
+ close(c) |
|
| 973 |
+ delete(srv.pushingPool, key) |
|
| 974 |
+ } |
|
| 971 | 975 |
default: |
| 972 | 976 |
return fmt.Errorf("Unknown pool type")
|
| 973 | 977 |
} |
| ... | ... |
@@ -979,7 +985,7 @@ func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *ut |
| 979 | 979 |
if err != nil {
|
| 980 | 980 |
return err |
| 981 | 981 |
} |
| 982 |
- if err := srv.poolAdd("pull", localName+":"+tag); err != nil {
|
|
| 982 |
+ if _, err := srv.poolAdd("pull", localName+":"+tag); err != nil {
|
|
| 983 | 983 |
return err |
| 984 | 984 |
} |
| 985 | 985 |
defer srv.poolRemove("pull", localName+":"+tag)
|
| ... | ... |
@@ -1174,7 +1180,7 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, |
| 1174 | 1174 |
|
| 1175 | 1175 |
// FIXME: Allow to interrupt current push when new push of same image is done. |
| 1176 | 1176 |
func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig, metaHeaders map[string][]string) error {
|
| 1177 |
- if err := srv.poolAdd("push", localName); err != nil {
|
|
| 1177 |
+ if _, err := srv.poolAdd("push", localName); err != nil {
|
|
| 1178 | 1178 |
return err |
| 1179 | 1179 |
} |
| 1180 | 1180 |
defer srv.poolRemove("push", localName)
|
| ... | ... |
@@ -1820,8 +1826,8 @@ func NewServer(eng *engine.Engine, config *DaemonConfig) (*Server, error) {
|
| 1820 | 1820 |
srv := &Server{
|
| 1821 | 1821 |
Eng: eng, |
| 1822 | 1822 |
runtime: runtime, |
| 1823 |
- pullingPool: make(map[string]struct{}),
|
|
| 1824 |
- pushingPool: make(map[string]struct{}),
|
|
| 1823 |
+ pullingPool: make(map[string]chan struct{}),
|
|
| 1824 |
+ pushingPool: make(map[string]chan struct{}),
|
|
| 1825 | 1825 |
events: make([]utils.JSONMessage, 0, 64), //only keeps the 64 last events |
| 1826 | 1826 |
listeners: make(map[string]chan utils.JSONMessage), |
| 1827 | 1827 |
reqFactory: nil, |
| ... | ... |
@@ -1872,8 +1878,8 @@ func (srv *Server) GetEvents() []utils.JSONMessage {
|
| 1872 | 1872 |
type Server struct {
|
| 1873 | 1873 |
sync.RWMutex |
| 1874 | 1874 |
runtime *Runtime |
| 1875 |
- pullingPool map[string]struct{}
|
|
| 1876 |
- pushingPool map[string]struct{}
|
|
| 1875 |
+ pullingPool map[string]chan struct{}
|
|
| 1876 |
+ pushingPool map[string]chan struct{}
|
|
| 1877 | 1877 |
events []utils.JSONMessage |
| 1878 | 1878 |
listeners map[string]chan utils.JSONMessage |
| 1879 | 1879 |
reqFactory *utils.HTTPRequestFactory |
| ... | ... |
@@ -8,49 +8,38 @@ import ( |
| 8 | 8 |
|
| 9 | 9 |
func TestPools(t *testing.T) {
|
| 10 | 10 |
srv := &Server{
|
| 11 |
- pullingPool: make(map[string]struct{}),
|
|
| 12 |
- pushingPool: make(map[string]struct{}),
|
|
| 11 |
+ pullingPool: make(map[string]chan struct{}),
|
|
| 12 |
+ pushingPool: make(map[string]chan struct{}),
|
|
| 13 | 13 |
} |
| 14 | 14 |
|
| 15 |
- err := srv.poolAdd("pull", "test1")
|
|
| 16 |
- if err != nil {
|
|
| 15 |
+ if _, err := srv.poolAdd("pull", "test1"); err != nil {
|
|
| 17 | 16 |
t.Fatal(err) |
| 18 | 17 |
} |
| 19 |
- err = srv.poolAdd("pull", "test2")
|
|
| 20 |
- if err != nil {
|
|
| 18 |
+ if _, err := srv.poolAdd("pull", "test2"); err != nil {
|
|
| 21 | 19 |
t.Fatal(err) |
| 22 | 20 |
} |
| 23 |
- err = srv.poolAdd("push", "test1")
|
|
| 24 |
- if err == nil || err.Error() != "pull test1 is already in progress" {
|
|
| 21 |
+ if _, err := srv.poolAdd("push", "test1"); err == nil || err.Error() != "pull test1 is already in progress" {
|
|
| 25 | 22 |
t.Fatalf("Expected `pull test1 is already in progress`")
|
| 26 | 23 |
} |
| 27 |
- err = srv.poolAdd("pull", "test1")
|
|
| 28 |
- if err == nil || err.Error() != "pull test1 is already in progress" {
|
|
| 24 |
+ if _, err := srv.poolAdd("pull", "test1"); err == nil || err.Error() != "pull test1 is already in progress" {
|
|
| 29 | 25 |
t.Fatalf("Expected `pull test1 is already in progress`")
|
| 30 | 26 |
} |
| 31 |
- err = srv.poolAdd("wait", "test3")
|
|
| 32 |
- if err == nil || err.Error() != "Unknown pool type" {
|
|
| 27 |
+ if _, err := srv.poolAdd("wait", "test3"); err == nil || err.Error() != "Unknown pool type" {
|
|
| 33 | 28 |
t.Fatalf("Expected `Unknown pool type`")
|
| 34 | 29 |
} |
| 35 |
- |
|
| 36 |
- err = srv.poolRemove("pull", "test2")
|
|
| 37 |
- if err != nil {
|
|
| 30 |
+ if err := srv.poolRemove("pull", "test2"); err != nil {
|
|
| 38 | 31 |
t.Fatal(err) |
| 39 | 32 |
} |
| 40 |
- err = srv.poolRemove("pull", "test2")
|
|
| 41 |
- if err != nil {
|
|
| 33 |
+ if err := srv.poolRemove("pull", "test2"); err != nil {
|
|
| 42 | 34 |
t.Fatal(err) |
| 43 | 35 |
} |
| 44 |
- err = srv.poolRemove("pull", "test1")
|
|
| 45 |
- if err != nil {
|
|
| 36 |
+ if err := srv.poolRemove("pull", "test1"); err != nil {
|
|
| 46 | 37 |
t.Fatal(err) |
| 47 | 38 |
} |
| 48 |
- err = srv.poolRemove("push", "test1")
|
|
| 49 |
- if err != nil {
|
|
| 39 |
+ if err := srv.poolRemove("push", "test1"); err != nil {
|
|
| 50 | 40 |
t.Fatal(err) |
| 51 | 41 |
} |
| 52 |
- err = srv.poolRemove("wait", "test3")
|
|
| 53 |
- if err == nil || err.Error() != "Unknown pool type" {
|
|
| 42 |
+ if err := srv.poolRemove("wait", "test3"); err == nil || err.Error() != "Unknown pool type" {
|
|
| 54 | 43 |
t.Fatalf("Expected `Unknown pool type`")
|
| 55 | 44 |
} |
| 56 | 45 |
} |
| ... | ... |
@@ -1,6 +1,7 @@ |
| 1 | 1 |
package docker |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "github.com/dotcloud/docker/graphdriver" |
|
| 4 | 5 |
"github.com/dotcloud/docker/utils" |
| 5 | 6 |
"os" |
| 6 | 7 |
"path" |
| ... | ... |
@@ -8,12 +9,16 @@ import ( |
| 8 | 8 |
) |
| 9 | 9 |
|
| 10 | 10 |
const ( |
| 11 |
- testImageName string = "myapp" |
|
| 12 |
- testImageID string = "foo" |
|
| 11 |
+ testImageName = "myapp" |
|
| 12 |
+ testImageID = "foo" |
|
| 13 | 13 |
) |
| 14 | 14 |
|
| 15 | 15 |
func mkTestTagStore(root string, t *testing.T) *TagStore {
|
| 16 |
- graph, err := NewGraph(root) |
|
| 16 |
+ driver, err := graphdriver.New(root) |
|
| 17 |
+ if err != nil {
|
|
| 18 |
+ t.Fatal(err) |
|
| 19 |
+ } |
|
| 20 |
+ graph, err := NewGraph(root, driver) |
|
| 17 | 21 |
if err != nil {
|
| 18 | 22 |
t.Fatal(err) |
| 19 | 23 |
} |
| ... | ... |
@@ -42,6 +47,7 @@ func TestLookupImage(t *testing.T) {
|
| 42 | 42 |
} |
| 43 | 43 |
defer os.RemoveAll(tmp) |
| 44 | 44 |
store := mkTestTagStore(tmp, t) |
| 45 |
+ defer store.graph.driver.Cleanup() |
|
| 45 | 46 |
|
| 46 | 47 |
if img, err := store.LookupImage(testImageName); err != nil {
|
| 47 | 48 |
t.Fatal(err) |
| ... | ... |
@@ -1,14 +1,43 @@ |
| 1 | 1 |
package docker |
| 2 | 2 |
|
| 3 |
+/* |
|
| 4 |
+#include <sys/ioctl.h> |
|
| 5 |
+#include <linux/fs.h> |
|
| 6 |
+#include <errno.h> |
|
| 7 |
+ |
|
| 8 |
+// See linux.git/fs/btrfs/ioctl.h |
|
| 9 |
+#define BTRFS_IOCTL_MAGIC 0x94 |
|
| 10 |
+#define BTRFS_IOC_CLONE _IOW(BTRFS_IOCTL_MAGIC, 9, int) |
|
| 11 |
+ |
|
| 12 |
+int |
|
| 13 |
+btrfs_reflink(int fd_out, int fd_in) |
|
| 14 |
+{
|
|
| 15 |
+ int res; |
|
| 16 |
+ res = ioctl(fd_out, BTRFS_IOC_CLONE, fd_in); |
|
| 17 |
+ if (res < 0) |
|
| 18 |
+ return errno; |
|
| 19 |
+ return 0; |
|
| 20 |
+} |
|
| 21 |
+ |
|
| 22 |
+*/ |
|
| 23 |
+import "C" |
|
| 3 | 24 |
import ( |
| 4 | 25 |
"fmt" |
| 26 |
+ "github.com/dotcloud/docker/archive" |
|
| 5 | 27 |
"github.com/dotcloud/docker/namesgenerator" |
| 6 | 28 |
"github.com/dotcloud/docker/utils" |
| 29 |
+ "io" |
|
| 7 | 30 |
"io/ioutil" |
| 31 |
+ "os" |
|
| 8 | 32 |
"strconv" |
| 9 | 33 |
"strings" |
| 34 |
+ "syscall" |
|
| 10 | 35 |
) |
| 11 | 36 |
|
| 37 |
+type Change struct {
|
|
| 38 |
+ archive.Change |
|
| 39 |
+} |
|
| 40 |
+ |
|
| 12 | 41 |
// Compare two Config struct. Do not compare the "Image" nor "Hostname" fields |
| 13 | 42 |
// If OpenStdin is set, then it differs |
| 14 | 43 |
func CompareConfig(a, b *Config) bool {
|
| ... | ... |
@@ -317,6 +346,14 @@ func migratePortMappings(config *Config, hostConfig *HostConfig) error {
|
| 317 | 317 |
return nil |
| 318 | 318 |
} |
| 319 | 319 |
|
| 320 |
+func BtrfsReflink(fd_out, fd_in uintptr) error {
|
|
| 321 |
+ res := C.btrfs_reflink(C.int(fd_out), C.int(fd_in)) |
|
| 322 |
+ if res != 0 {
|
|
| 323 |
+ return syscall.Errno(res) |
|
| 324 |
+ } |
|
| 325 |
+ return nil |
|
| 326 |
+} |
|
| 327 |
+ |
|
| 320 | 328 |
// Links come in the format of |
| 321 | 329 |
// name:alias |
| 322 | 330 |
func parseLink(rawLink string) (map[string]string, error) {
|
| ... | ... |
@@ -349,3 +386,14 @@ func (c *checker) Exists(name string) bool {
|
| 349 | 349 |
func generateRandomName(runtime *Runtime) (string, error) {
|
| 350 | 350 |
return namesgenerator.GenerateRandomName(&checker{runtime})
|
| 351 | 351 |
} |
| 352 |
+ |
|
| 353 |
+func CopyFile(dstFile, srcFile *os.File) error {
|
|
| 354 |
+ err := BtrfsReflink(dstFile.Fd(), srcFile.Fd()) |
|
| 355 |
+ if err == nil {
|
|
| 356 |
+ return nil |
|
| 357 |
+ } |
|
| 358 |
+ |
|
| 359 |
+ // Fall back to normal copy |
|
| 360 |
+ _, err = io.Copy(dstFile, srcFile) |
|
| 361 |
+ return err |
|
| 362 |
+} |
| 352 | 363 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,35 @@ |
| 0 |
+package utils |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "os" |
|
| 4 |
+ "path/filepath" |
|
| 5 |
+ "syscall" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+// TreeSize walks a directory tree and returns its total size in bytes. |
|
| 9 |
+func TreeSize(dir string) (size int64, err error) {
|
|
| 10 |
+ data := make(map[uint64]bool) |
|
| 11 |
+ err = filepath.Walk(dir, func(d string, fileInfo os.FileInfo, e error) error {
|
|
| 12 |
+ // Ignore directory sizes |
|
| 13 |
+ if fileInfo == nil {
|
|
| 14 |
+ return nil |
|
| 15 |
+ } |
|
| 16 |
+ |
|
| 17 |
+ s := fileInfo.Size() |
|
| 18 |
+ if fileInfo.IsDir() || s == 0 {
|
|
| 19 |
+ return nil |
|
| 20 |
+ } |
|
| 21 |
+ |
|
| 22 |
+ // Check inode to handle hard links correctly |
|
| 23 |
+ inode := fileInfo.Sys().(*syscall.Stat_t).Ino |
|
| 24 |
+ if _, exists := data[inode]; exists {
|
|
| 25 |
+ return nil |
|
| 26 |
+ } |
|
| 27 |
+ data[inode] = false |
|
| 28 |
+ |
|
| 29 |
+ size += s |
|
| 30 |
+ |
|
| 31 |
+ return nil |
|
| 32 |
+ }) |
|
| 33 |
+ return |
|
| 34 |
+} |