Simplify graphdriver interface: Create, Get. No more external mounting or Dir/Image interface

This commit is contained in:
Solomon Hykes 2013-11-07 20:34:01 +00:00
parent a63ff8da46
commit f2bab1557c
7 changed files with 162 additions and 158 deletions

View File

@ -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 {

View File

@ -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)
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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,

View File

@ -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()
}

15
utils/fs.go Normal file
View File

@ -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
}