From 7e3e167f73fd5223d13962932a30bdcaa98bbc27 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 7 Nov 2013 20:34:01 +0000 Subject: [PATCH] Simplify graphdriver interface: Create, Get. No more external mounting or Dir/Image interface Upstream-commit: f2bab1557c3fef4a95b5b982fe7127fcb29c4f8f Component: engine --- components/engine/container.go | 55 +++++++--------- components/engine/graph.go | 86 ++++++++++++------------- components/engine/graphdriver/driver.go | 19 +++--- components/engine/image.go | 60 ++++++----------- components/engine/runtime.go | 82 ++++++++++++++--------- components/engine/server.go | 3 +- components/engine/utils/fs.go | 15 +++++ 7 files changed, 162 insertions(+), 158 deletions(-) create mode 100644 components/engine/utils/fs.go diff --git a/components/engine/container.go b/components/engine/container.go index f71c1db3fd..fd52e1489a 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -9,6 +9,7 @@ import ( "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/term" "github.com/dotcloud/docker/utils" + "github.com/dotcloud/docker/graphdriver" // FIXME: graphdriver.Change is a placeholder for archive.Change "github.com/kr/pty" "io" "io/ioutil" @@ -25,7 +26,8 @@ import ( ) type Container struct { - root string + root string // Path to the "home" of the container, including metadata. + rootfs string // Path to the root filesystem of the container. ID string @@ -767,6 +769,7 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) { } } + volumesDriver := container.runtime.volumes.driver // Create the requested volumes if they don't exist for volPath := range container.Config.Volumes { volPath = path.Clean(volPath) @@ -790,9 +793,9 @@ func (container *Container) Start(hostConfig *HostConfig) (err error) { if err != nil { return err } - srcPath, err = c.layer() + srcPath, err = volumesDriver.Get(c.ID) if err != nil { - return err + return fmt.Errorf("Driver %s failed to get volume rootfs %s: %s", volumesDriver, c.ID, err) } srcRW = true // RW by default } @@ -1338,15 +1341,10 @@ func (container *Container) Resize(h, w int) error { } func (container *Container) ExportRw() (archive.Archive, error) { - return archive.Tar(container.rwPath(), archive.Uncompressed) -} - -func (container *Container) RwChecksum() (string, error) { - rwData, err := archive.Tar(container.rwPath(), archive.Xz) - if err != nil { - return "", err + if container.runtime == nil { + return nil, fmt.Errorf("Can't load storage driver for unregistered container %s", container.ID) } - return utils.HashData(rwData) + return container.runtime.driver.Diff(container.ID) } func (container *Container) Export() (archive.Archive, error) { @@ -1372,11 +1370,8 @@ func (container *Container) WaitTimeout(timeout time.Duration) error { } func (container *Container) EnsureMounted() error { - if mounted, err := container.Mounted(); err != nil { - return err - } else if mounted { - return nil - } + // FIXME: EnsureMounted is deprecated because drivers are now responsible + // for re-entrant mounting in their Get() method. return container.Mount() } @@ -1384,7 +1379,7 @@ func (container *Container) Mount() error { return container.runtime.Mount(container) } -func (container *Container) Changes() ([]Change, error) { +func (container *Container) Changes() ([]graphdriver.Change, error) { return container.runtime.Changes(container) } @@ -1395,10 +1390,6 @@ func (container *Container) GetImage() (*Image, error) { return container.runtime.graph.Get(container.Image) } -func (container *Container) Mounted() (bool, error) { - return container.runtime.Mounted(container) -} - func (container *Container) Unmount() error { return container.runtime.Unmount(container) } @@ -1437,11 +1428,7 @@ func (container *Container) lxcConfigPath() string { // This method must be exported to be used from the lxc template func (container *Container) RootfsPath() string { - return path.Join(container.root, "rootfs") -} - -func (container *Container) rwPath() string { - return path.Join(container.root, "rw") + return container.rootfs } func validateID(id string) error { @@ -1455,18 +1442,20 @@ func validateID(id string) error { func (container *Container) GetSize() (int64, int64) { var sizeRw, sizeRootfs int64 - filepath.Walk(container.rwPath(), func(path string, fileInfo os.FileInfo, err error) error { - if fileInfo != nil { - sizeRw += fileInfo.Size() - } - return nil - }) + driver := container.runtime.driver + sizeRw, err := driver.DiffSize(container.ID) + if err != nil { + utils.Errorf("Warning: driver %s couldn't return diff size of container %s: %s", driver, container.ID, err) + // FIXME: GetSize should return an error. Not changing it now in case + // there is a side-effect. + sizeRw = -1 + } if err := container.EnsureMounted(); err != nil { utils.Errorf("Warning: failed to compute size of container rootfs %s: %s", container.ID, err) return sizeRw, sizeRootfs } - _, err := os.Stat(container.RootfsPath()) + _, err = os.Stat(container.RootfsPath()) if err == nil { filepath.Walk(container.RootfsPath(), func(path string, fileInfo os.FileInfo, err error) error { if fileInfo != nil { diff --git a/components/engine/graph.go b/components/engine/graph.go index 660352b60c..21647bba82 100644 --- a/components/engine/graph.go +++ b/components/engine/graph.go @@ -3,10 +3,8 @@ package docker import ( "fmt" "github.com/dotcloud/docker/archive" - _ "github.com/dotcloud/docker/aufs" - _ "github.com/dotcloud/docker/devmapper" - "github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/utils" + "github.com/dotcloud/docker/graphdriver" "io" "io/ioutil" "os" @@ -25,7 +23,7 @@ type Graph struct { // NewGraph instantiates a new graph at the given root path in the filesystem. // `root` will be created if it doesn't exist. -func NewGraph(root string) (*Graph, error) { +func NewGraph(root string, driver graphdriver.Driver) (*Graph, error) { abspath, err := filepath.Abs(root) if err != nil { return nil, err @@ -35,10 +33,6 @@ func NewGraph(root string) (*Graph, error) { return nil, err } - driver, err := graphdriver.New(root) - if err != nil { - return nil, err - } graph := &Graph{ Root: abspath, @@ -89,16 +83,22 @@ func (graph *Graph) Get(name string) (*Image, error) { if err != nil { return nil, err } + // Check that the filesystem layer exists + rootfs, err := graph.driver.Get(img.ID) + if err != nil { + return nil, fmt.Errorf("Driver %s failed to get image rootfs %s: %s", graph.driver, img.ID, err) + } if img.ID != id { return nil, fmt.Errorf("Image stored at '%s' has wrong id '%s'", id, img.ID) } img.graph = graph if img.Size == 0 { - root, err := img.root() + size, err := utils.TreeSize(rootfs) if err != nil { - return nil, err + return nil, fmt.Errorf("Error computing size of rootfs %s: %s", img.ID, err) } - if err := StoreSize(img, root); err != nil { + img.Size = size + if err := img.SaveSize(graph.imageRoot(id)); err != nil { return nil, err } } @@ -142,7 +142,17 @@ func (graph *Graph) Register(jsonData []byte, layerData archive.Archive, img *Im if err != nil { return fmt.Errorf("Mktemp failed: %s", err) } - if err := StoreImage(img, jsonData, layerData, tmp); err != nil { + + // Create root filesystem in the driver + if err := graph.driver.Create(img.ID, img.Parent); err != nil { + return fmt.Errorf("Driver %s failed to create image rootfs %s: %s", graph.driver, img.ID, err) + } + // Mount the root filesystem so we can apply the diff/layer + rootfs, err := graph.driver.Get(img.ID) + if err != nil { + return fmt.Errorf("Driver %s failed to get image rootfs %s: %s", graph.driver, img.ID, err) + } + if err := StoreImage(img, jsonData, layerData, tmp, rootfs); err != nil { return err } // Commit @@ -163,7 +173,7 @@ func (graph *Graph) TempLayerArchive(id string, compression archive.Compression, if err != nil { return nil, err } - tmp, err := graph.tmp() + tmp, err := graph.Mktemp("") if err != nil { return nil, err } @@ -171,7 +181,7 @@ func (graph *Graph) TempLayerArchive(id string, compression archive.Compression, if err != nil { return nil, err } - return archive.NewTempArchive(utils.ProgressReader(ioutil.NopCloser(a), 0, output, sf.FormatProgress("", "Buffering to disk", "%v/%v (%v)"), sf, true), tmp.Root) + return archive.NewTempArchive(utils.ProgressReader(ioutil.NopCloser(a), 0, output, sf.FormatProgress("", "Buffering to disk", "%v/%v (%v)"), sf, true), tmp) } // Mktemp creates a temporary sub-directory inside the graph's filesystem. @@ -179,34 +189,26 @@ func (graph *Graph) Mktemp(id string) (string, error) { if id == "" { id = GenerateID() } - tmp, err := graph.tmp() + // FIXME: use a separate "tmp" driver instead of the regular driver, + // to allow for removal at cleanup. + // Right now temp directories are never removed! + if err := graph.driver.Create(id, ""); err != nil { + return "", fmt.Errorf("Driver %s couldn't create temporary directory %s: %s", graph.driver, id, err) + } + dir, err := graph.driver.Get(id) if err != nil { - return "", fmt.Errorf("Couldn't create temp: %s", err) + return "", fmt.Errorf("Driver %s couldn't get temporary directory %s: %s", graph.driver, id, err) } - if tmp.Exists(id) { - return "", fmt.Errorf("Image %s already exists", id) - } - return tmp.imageRoot(id), nil + return dir, nil } -// getDockerInitLayer returns the path of a layer containing a mountpoint suitable +// setupInitLayer populates a directory with mountpoints suitable // for bind-mounting dockerinit into the container. The mountpoint is simply an // empty file at /.dockerinit // // This extra layer is used by all containers as the top-most ro layer. It protects // the container from unwanted side-effects on the rw layer. -func (graph *Graph) getDockerInitLayer() (string, error) { - tmp, err := graph.tmp() - if err != nil { - return "", err - } - initLayer := tmp.imageRoot("_dockerinit") - if err := os.Mkdir(initLayer, 0755); err != nil && !os.IsExist(err) { - // If directory already existed, keep going. - // For all other errors, abort. - return "", err - } - +func setupInitLayer(initLayer string) error { for pth, typ := range map[string]string{ "/dev/pts": "dir", "/dev/shm": "dir", @@ -225,32 +227,27 @@ func (graph *Graph) getDockerInitLayer() (string, error) { switch typ { case "dir": if err := os.MkdirAll(path.Join(initLayer, pth), 0755); err != nil { - return "", err + return err } case "file": if err := os.MkdirAll(path.Join(initLayer, path.Dir(pth)), 0755); err != nil { - return "", err + return err } if f, err := os.OpenFile(path.Join(initLayer, pth), os.O_CREATE, 0755); err != nil { - return "", err + return err } else { f.Close() } } } else { - return "", err + return err } } } // Layer is ready to use, if it wasn't before. - return initLayer, nil -} - -func (graph *Graph) tmp() (*Graph, error) { - // Changed to _tmp from :tmp:, because it messed with ":" separators in aufs branch syntax... - return NewGraph(path.Join(graph.Root, "_tmp")) + return nil } // Check if given error is "not empty". @@ -282,6 +279,9 @@ func (graph *Graph) Delete(name string) error { if err != nil { return err } + // Remove rootfs data from the driver + graph.driver.Remove(id) + // Remove the trashed image directory return os.RemoveAll(tmp) } diff --git a/components/engine/graphdriver/driver.go b/components/engine/graphdriver/driver.go index b17ff75a0c..5a83259324 100644 --- a/components/engine/graphdriver/driver.go +++ b/components/engine/graphdriver/driver.go @@ -7,21 +7,20 @@ import ( type InitFunc func(root string) (Driver, error) -type Dir interface { - ID() string - Path() string - Parent() (Dir, error) +// FIXME: this is a temporary placeholder for archive.Change +// (to be merged from master) +type Change interface { } type Driver interface { - OnCreate(dir Dir, layer archive.Archive) error - OnRemove(dir Dir) error + Create(id, parent string) error + Remove(id string) error - OnMount(dir Dir, dest string) error - OnUnmount(dest string) error - Mounted(dest string) (bool, error) + Get(id string) (dir string, err error) - Layer(dir Dir, dest string) (archive.Archive, error) + Diff(id string) (archive.Archive, error) + DiffSize(id string) (bytes int64, err error) + Changes(id string) ([]Change, error) Cleanup() error } diff --git a/components/engine/image.go b/components/engine/image.go index 5c672adb4b..e954e7b022 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -11,7 +11,6 @@ import ( "io/ioutil" "os" "path" - "path/filepath" "strconv" "strings" "time" @@ -59,19 +58,10 @@ func LoadImage(root string) (*Image, error) { } } - // Check that the filesystem layer exists - if stat, err := os.Stat(layerPath(root)); err != nil { - if os.IsNotExist(err) { - return nil, fmt.Errorf("Couldn't load image %s: no filesystem layer", img.ID) - } - return nil, err - } else if !stat.IsDir() { - return nil, fmt.Errorf("Couldn't load image %s: %s is not a directory", img.ID, layerPath(root)) - } return img, nil } -func StoreImage(img *Image, jsonData []byte, layerData archive.Archive, root string) error { +func StoreImage(img *Image, jsonData []byte, layerData archive.Archive, root, rootfs string) error { // Check that root doesn't already exist if _, err := os.Stat(root); err == nil { return fmt.Errorf("Image %s already exists", img.ID) @@ -79,7 +69,7 @@ func StoreImage(img *Image, jsonData []byte, layerData archive.Archive, root str return err } // Store the layer - layer := layerPath(root) + layer := rootfs if err := os.MkdirAll(layer, 0755); err != nil { return err } @@ -106,29 +96,25 @@ func StoreImage(img *Image, jsonData []byte, layerData archive.Archive, root str return err } } - - return StoreSize(img, root) -} - -func StoreSize(img *Image, root string) error { - layer := layerPath(root) - - var totalSize int64 = 0 - filepath.Walk(layer, func(path string, fileInfo os.FileInfo, err error) error { - totalSize += fileInfo.Size() - return nil - }) - img.Size = totalSize - - if err := ioutil.WriteFile(path.Join(root, "layersize"), []byte(strconv.Itoa(int(totalSize))), 0600); err != nil { - return nil + // Compute and save the size of the rootfs + size, err := utils.TreeSize(rootfs) + if err != nil { + return fmt.Errorf("Error computing size of rootfs %s: %s", img.ID, err) + } + img.Size = size + if err := img.SaveSize(root); err != nil { + return err } return nil } -func layerPath(root string) string { - return path.Join(root, "layer") +// SaveSize stores the current `size` value of `img` in the directory `root`. +func (img *Image) SaveSize(root string) error { + if err := ioutil.WriteFile(path.Join(root, "layersize"), []byte(strconv.Itoa(int(img.Size))), 0600); err != nil { + return fmt.Errorf("Error storing image size in %s/layersize: %s", root, err) + } + return nil } func jsonPath(root string) string { @@ -137,7 +123,10 @@ func jsonPath(root string) string { // TarLayer returns a tar archive of the image's filesystem layer. func (image *Image) TarLayer(compression archive.Compression) (archive.Archive, error) { - layerPath, err := image.layer() + if image.graph == nil { + return nil, fmt.Errorf("Can't load storage driver for unregistered image %s", image.ID) + } + layerPath, err := image.graph.driver.Get(image.ID) if err != nil { return nil, err } @@ -216,15 +205,6 @@ func (img *Image) root() (string, error) { return img.graph.imageRoot(img.ID), nil } -// Return the path of an image's layer -func (img *Image) layer() (string, error) { - root, err := img.root() - if err != nil { - return "", err - } - return layerPath(root), nil -} - func (img *Image) getParentsSize(size int64) int64 { parentImage, err := img.GetParent() if err != nil || parentImage == nil { diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 6acea49ad3..4d46d8fbab 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/dotcloud/docker/gograph" "github.com/dotcloud/docker/utils" + "github.com/dotcloud/docker/graphdriver" "io" "io/ioutil" "log" @@ -38,6 +39,7 @@ type Runtime struct { srv *Server config *DaemonConfig containerGraph *gograph.Database + driver graphdriver.Driver } // List returns an array of all containers registered in the runtime. @@ -113,6 +115,13 @@ func (runtime *Runtime) Register(container *Container) error { return err } + // Get the root filesystem from the driver + rootfs, err := runtime.driver.Get(container.ID) + if err != nil { + return fmt.Errorf("Error getting container filesystem %s from driver %s: %s", container.ID, runtime.driver, err) + } + container.rootfs = rootfs + // init the wait lock container.waitLock = make(chan struct{}) @@ -200,12 +209,8 @@ func (runtime *Runtime) Destroy(container *Container) error { return err } - if mounted, err := container.Mounted(); err != nil { - return err - } else if mounted { - if err := container.Unmount(); err != nil { - return fmt.Errorf("Unable to unmount container %v: %v", container.ID, err) - } + if err := runtime.driver.Remove(container.ID); err != nil { + return fmt.Errorf("Driver %s failed to remove root filesystem %s: %s", runtime.driver, container.ID, err) } if _, err := runtime.containerGraph.Purge(container.ID); err != nil { @@ -413,6 +418,21 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin return nil, nil, err } + initID := fmt.Sprintf("%s-init", container.ID) + if err := runtime.driver.Create(initID, img.ID); err != nil { + return nil, nil, err + } + initPath, err := runtime.driver.Get(initID) + if err != nil { + return nil, nil, err + } + if err := setupInitLayer(initPath); err != nil { + return nil, nil, err + } + + if err := runtime.driver.Create(container.ID, initID); err != nil { + return nil, nil, err + } resolvConf, err := utils.GetResolvConf() if err != nil { return nil, nil, err @@ -568,17 +588,23 @@ func NewRuntime(config *DaemonConfig) (*Runtime, error) { } func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { + // Load storage driver + driver, err := graphdriver.New(config.Root) + if err != nil { + return nil, err + } + runtimeRepo := path.Join(config.Root, "containers") if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) { return nil, err } - g, err := NewGraph(path.Join(config.Root, "graph")) + g, err := NewGraph(path.Join(config.Root, "graph"), driver) if err != nil { return nil, err } - volumes, err := NewGraph(path.Join(config.Root, "volumes")) + volumes, err := NewGraph(path.Join(config.Root, "volumes"), driver) if err != nil { return nil, err } @@ -612,6 +638,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { return nil, err } + runtime := &Runtime{ repository: runtimeRepo, containers: list.New(), @@ -623,6 +650,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { volumes: volumes, config: config, containerGraph: graph, + driver: driver, } if err := runtime.restore(); err != nil { @@ -633,40 +661,32 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { func (runtime *Runtime) Close() error { runtime.networkManager.Close() + runtime.driver.Cleanup() return runtime.containerGraph.Close() } func (runtime *Runtime) Mount(container *Container) error { - if mounted, err := runtime.Mounted(container); err != nil { - return err - } else if mounted { - return fmt.Errorf("%s is already mounted", container.RootfsPath()) - } - img, err := container.GetImage() + dir, err := runtime.driver.Get(container.ID) if err != nil { - return err + return fmt.Errorf("Error getting container %s from driver %s: %s", container.ID, runtime.driver, err) } - return runtime.graph.driver.Mount(img, container.root) + if container.rootfs == "" { + container.rootfs = dir + } else if container.rootfs != dir { + return fmt.Errorf("Error: driver %s is returning inconsistent paths for container %s ('%s' then '%s')", + runtime.driver, container.ID, container.rootfs, dir) + } + return nil } func (runtime *Runtime) Unmount(container *Container) error { - return runtime.graph.driver.Unmount(container.root) + // FIXME: Unmount is deprecated because drivers are responsible for mounting + // and unmounting when necessary. Use driver.Remove() instead. + return nil } -func (runtime *Runtime) Mounted(container *Container) (bool, error) { - return runtime.graph.driver.Mounted(container.root) -} - -func (runtime *Runtime) Changes(container *Container) ([]Change, error) { - img, err := container.GetImage() - if err != nil { - return nil, err - } - layers, err := img.Layers() - if err != nil { - return nil, err - } - return Changes(layers, container.rwPath()) +func (runtime *Runtime) Changes(container *Container) ([]graphdriver.Change, error) { + return runtime.driver.Changes(container.ID) } // History is a convenience type for storing a list of containers, diff --git a/components/engine/server.go b/components/engine/server.go index cdf2751226..f8ea224b8a 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -11,6 +11,7 @@ import ( "github.com/dotcloud/docker/gograph" "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/utils" + "github.com/dotcloud/docker/graphdriver" // FIXME: graphdriver.Change is a placeholder for archive.Change "io" "io/ioutil" "log" @@ -430,7 +431,7 @@ func (srv *Server) ContainerTop(name, ps_args string) (*APITop, error) { return nil, fmt.Errorf("No such container: %s", name) } -func (srv *Server) ContainerChanges(name string) ([]Change, error) { +func (srv *Server) ContainerChanges(name string) ([]graphdriver.Change, error) { if container := srv.runtime.Get(name); container != nil { return container.Changes() } diff --git a/components/engine/utils/fs.go b/components/engine/utils/fs.go new file mode 100644 index 0000000000..891a200507 --- /dev/null +++ b/components/engine/utils/fs.go @@ -0,0 +1,15 @@ +package utils + +import ( + "os" + "path/filepath" +) + +// TreeSize walks a directory tree and returns its total size in bytes. +func TreeSize(dir string) (size int64, err error) { + err = filepath.Walk(dir, func(d string, fileInfo os.FileInfo, e error) error { + size += fileInfo.Size() + return nil + }) + return +}