From d06f87abd9141efd3cad1ac3dfa43a856cef62bd Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 11:14:31 +0200 Subject: [PATCH 001/301] Add libdevmapper wrapper Upstream-commit: 739af0a17f6a5a9956bbc9fd1e81e4d40bff8167 Component: engine --- components/engine/devmapper/devmapper.go | 349 +++++++++++++++++++++++ 1 file changed, 349 insertions(+) create mode 100644 components/engine/devmapper/devmapper.go diff --git a/components/engine/devmapper/devmapper.go b/components/engine/devmapper/devmapper.go new file mode 100644 index 0000000000..f007091827 --- /dev/null +++ b/components/engine/devmapper/devmapper.go @@ -0,0 +1,349 @@ +package devmapper + +/* +#cgo LDFLAGS: -L. -ldevmapper +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static char * +attach_loop_device(const char *filename, int *loop_fd_out) +{ + struct loop_info64 loopinfo = { 0 }; + struct stat st; + char buf[64]; + int i, loop_fd, fd, start_index; + char *loopname; + + *loop_fd_out = -1; + + start_index = 0; + fd = open("/dev/loop-control", O_RDONLY); + if (fd == 0) { + start_index = ioctl(fd, LOOP_CTL_GET_FREE); + close(fd); + + if (start_index < 0) + start_index = 0; + } + + fd = open(filename, O_RDWR); + if (fd < 0) { + return NULL; + } + + loop_fd = -1; + for (i = start_index ; loop_fd < 0 ; i++ ) { + if (sprintf(buf, "/dev/loop%d", i) < 0) { + close(fd); + return NULL; + } + + if (stat(buf, &st) || !S_ISBLK(st.st_mode)) { + close(fd); + return NULL; + } + + loop_fd = open(buf, O_RDWR); + if (loop_fd < 0 && errno == ENOENT) { + close(fd); + fprintf (stderr, "no available loopback device!"); + return NULL; + } else if (loop_fd < 0) + continue; + + if (ioctl (loop_fd, LOOP_SET_FD, (void *)(size_t)fd) < 0) { + close(loop_fd); + loop_fd = -1; + if (errno != EBUSY) { + close (fd); + fprintf (stderr, "cannot set up loopback device %s", buf); + return NULL; + } + continue; + } + + close (fd); + + strncpy((char*)loopinfo.lo_file_name, buf, LO_NAME_SIZE); + loopinfo.lo_offset = 0; + loopinfo.lo_flags = LO_FLAGS_AUTOCLEAR; + + if (ioctl(loop_fd, LOOP_SET_STATUS64, &loopinfo) < 0) { + ioctl(loop_fd, LOOP_CLR_FD, 0); + close(loop_fd); + fprintf (stderr, "cannot set up loopback device info"); + return NULL; + } + + loopname = strdup(buf); + if (loopname == NULL) { + close(loop_fd); + return NULL; + } + + *loop_fd_out = loop_fd; + return loopname; + } + return NULL; +} + +static int64_t +get_block_size(int fd) +{ + uint64_t size; + if (ioctl(fd, BLKGETSIZE64, &size) == -1) + return -1; + return (int64_t)size; +} + + +*/ +import "C" +import "unsafe" +import "fmt" +import "runtime" +import "os" + +func SetDevDir(dir string) error { + c_dir := C.CString(dir) + defer C.free(unsafe.Pointer(c_dir)) + res := C.dm_set_dev_dir(c_dir) + if res != 1 { + return fmt.Errorf("dm_set_dev_dir failed") + } + return nil +} + +func GetLibraryVersion() (string, error) { + buffer := (*C.char)(C.malloc(128)) + defer C.free(unsafe.Pointer(buffer)) + res := C.dm_get_library_version(buffer, 128) + if res != 1 { + return "", fmt.Errorf("dm_get_library_version failed") + } else { + return C.GoString(buffer), nil + } +} + +type TaskType int + +const ( + DeviceCreate TaskType = iota + DeviceReload + DeviceRemove + DeviceRemoveAll + DeviceSuspend + DeviceResume + DeviceInfo + DeviceDeps + DeviceRename + DeviceVersion + DeviceStatus + DeviceTable + DeviceWaitevent + DeviceList + DeviceClear + DeviceMknodes + DeviceListVersions + DeviceTargetMsg + DeviceSetGeometry +) + +type Task struct { + unmanaged *C.struct_dm_task +} + +type Info struct { + Exists int + Suspended int + LiveTable int + InactiveTable int + OpenCount int32 + EventNr uint32 + Major uint32 + Minor uint32 + ReadOnly int + TargetCount int32 +} + +func (t *Task) destroy() { + if t != nil { + C.dm_task_destroy(t.unmanaged) + runtime.SetFinalizer(t, nil) + } +} + +func TaskCreate(tasktype TaskType) *Task { + c_task := C.dm_task_create(C.int(int(tasktype))) + if c_task == nil { + return nil + } + task := &Task{c_task} + runtime.SetFinalizer(task, (*Task).destroy) + return task +} + +func (t *Task) Run() error { + res := C.dm_task_run(t.unmanaged) + if res != 1 { + return fmt.Errorf("dm_task_run failed") + } + return nil +} + +func (t *Task) SetName(name string) error { + c_name := C.CString(name) + defer C.free(unsafe.Pointer(c_name)) + + res := C.dm_task_set_name(t.unmanaged, c_name) + if res != 1 { + return fmt.Errorf("dm_task_set_name failed") + } + return nil +} + +func (t *Task) SetMessage(message string) error { + c_message := C.CString(message) + defer C.free(unsafe.Pointer(c_message)) + + res := C.dm_task_set_message(t.unmanaged, c_message) + if res != 1 { + return fmt.Errorf("dm_task_set_message failed") + } + return nil +} + +func (t *Task) SetSector(sector uint64) error { + res := C.dm_task_set_sector(t.unmanaged, C.uint64_t(sector)) + if res != 1 { + return fmt.Errorf("dm_task_set_add_node failed") + } + return nil +} + +func (t *Task) SetCookie(cookie *uint32, flags uint16) error { + var c_cookie C.uint32_t + c_cookie = C.uint32_t(*cookie) + res := C.dm_task_set_cookie(t.unmanaged, &c_cookie, C.uint16_t(flags)) + if res != 1 { + return fmt.Errorf("dm_task_set_add_node failed") + } + *cookie = uint32(c_cookie) + return nil +} + +func (t *Task) SetRo() error { + res := C.dm_task_set_ro(t.unmanaged) + if res != 1 { + return fmt.Errorf("dm_task_set_ro failed") + } + return nil +} + +func (t *Task) AddTarget(start uint64, size uint64, ttype string, params string) error { + c_ttype := C.CString(ttype) + defer C.free(unsafe.Pointer(c_ttype)) + + c_params := C.CString(params) + defer C.free(unsafe.Pointer(c_params)) + + res := C.dm_task_add_target(t.unmanaged, C.uint64_t(start), C.uint64_t(size), c_ttype, c_params) + if res != 1 { + return fmt.Errorf("dm_task_add_target failed") + } + return nil +} + +func (t *Task) GetDriverVersion() (string, error) { + buffer := (*C.char)(C.malloc(128)) + defer C.free(unsafe.Pointer(buffer)) + + res := C.dm_task_get_driver_version(t.unmanaged, buffer, 128) + if res != 1 { + return "", fmt.Errorf("dm_task_get_driver_version") + } else { + return C.GoString(buffer), nil + } +} + +func (t *Task) GetInfo() (*Info, error) { + c_info := C.struct_dm_info{} + res := C.dm_task_get_info(t.unmanaged, &c_info) + if res != 1 { + return nil, fmt.Errorf("dm_task_get_driver_version") + } else { + info := &Info{} + info.Exists = int(c_info.exists) + info.Suspended = int(c_info.suspended) + info.LiveTable = int(c_info.live_table) + info.InactiveTable = int(c_info.inactive_table) + info.OpenCount = int32(c_info.open_count) + info.EventNr = uint32(c_info.event_nr) + info.Major = uint32(c_info.major) + info.Minor = uint32(c_info.minor) + info.ReadOnly = int(c_info.read_only) + info.TargetCount = int32(c_info.target_count) + + return info, nil + } +} + +func (t *Task) GetNextTarget(next uintptr) (uintptr, uint64, uint64, string, string) { + nextp := unsafe.Pointer(next) + var c_start C.uint64_t + var c_length C.uint64_t + var c_target_type *C.char + var c_params *C.char + + nextp = C.dm_get_next_target(t.unmanaged, nextp, &c_start, &c_length, &c_target_type, &c_params) + + target_type := C.GoString(c_target_type) + params := C.GoString(c_params) + + return uintptr(nextp), uint64(c_start), uint64(c_length), target_type, params +} + +func AttachLoopDevice(filename string) (*os.File, error) { + c_filename := C.CString(filename) + defer C.free(unsafe.Pointer(c_filename)) + + var fd C.int + res := C.attach_loop_device(c_filename, &fd) + if res == nil { + return nil, fmt.Errorf("error loopback mounting") + } + file := os.NewFile(uintptr(fd), C.GoString(res)) + C.free(unsafe.Pointer(res)) + return file, nil +} + +func GetBlockDeviceSize(file *os.File) (uint64, error) { + fd := file.Fd() + size := C.get_block_size(C.int(fd)) + if size == -1 { + return 0, fmt.Errorf("Can't get block size") + } + return uint64(size), nil + +} + +func UdevWait(cookie uint32) error { + res := C.dm_udev_wait(C.uint32_t(cookie)) + if res != 1 { + return fmt.Errorf("Failed to wait on udev cookie %d", cookie) + } + return nil +} + +func LogInitVerbose(level int) { + C.dm_log_init_verbose(C.int(level)) +} From 51bfa55a020dd579980fe21948512e4e04a4d719 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 11:18:23 +0200 Subject: [PATCH 002/301] devmapper: Add DeviceSet device-mapper helper This is a module that uses the device-mapper create CoW snapshots You instantiate a DeviceSetDM object on a specified root (/var/lib/docker), and it will create a subdirectory there called "loopback". It will contain two sparse files which are loopback mounted into a thin-pool device-mapper device called "docker-pool". We then create a base snapshot in the pool with an empty filesystem which can be used as a base for docker snapshots. It also keeps track of the mapping between docker image ids and the snapshots in the pool. Typical use of is something like (without error checking): devices = NewDeviceSetDM("/var/lib/docker") devices.AddDevice(imageId, "") // "" is the base image id devices.MountDevice(imageId, "/mnt/image") ... extract base image to /mnt/image devices.AddDevice(containerId, imageId) devices.MountDevice(containerId, "/mnt/container") ... start container at /mnt/container Upstream-commit: 0b12702c0c9753cbb18a942bac2600b036e4f80e Component: engine --- .../engine/devmapper/deviceset_devmapper.go | 895 ++++++++++++++++++ 1 file changed, 895 insertions(+) create mode 100644 components/engine/devmapper/deviceset_devmapper.go diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go new file mode 100644 index 0000000000..d7e122cf28 --- /dev/null +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -0,0 +1,895 @@ +package devmapper + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "os/exec" + "path" + "path/filepath" + "syscall" +) + +const defaultDataLoopbackSize int64 = 100 * 1024 * 1024 * 1024 +const defaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024 +const defaultBaseFsSize uint64 = 10 * 1024 * 1024 * 1024 + +type DevInfo struct { + Hash string `json:"-"` + DeviceId int `json:"device_id"` + Size uint64 `json:"size"` + TransactionId uint64 `json:"transaction_id"` + Initialized bool `json:"initialized"` +} + +type MetaData struct { + Devices map[string]*DevInfo `json:devices` +} + +type DeviceSetDM struct { + initialized bool + root string + MetaData + TransactionId uint64 + NewTransactionId uint64 + nextFreeDevice int +} + +func getDevName(name string) string { + return "/dev/mapper/" + name +} + +func (info *DevInfo) Name() string { + hash := info.Hash + if hash == "" { + hash = "base" + } + return fmt.Sprintf("docker-%s", hash) +} + +func (info *DevInfo) DevName() string { + return getDevName(info.Name()) +} + +func (devices *DeviceSetDM) loopbackDir() string { + return path.Join(devices.root, "loopback") +} + +func (devices *DeviceSetDM) jsonFile() string { + return path.Join(devices.loopbackDir(), "json") +} + +func (devices *DeviceSetDM) getPoolName() string { + return "docker-pool" +} + +func (devices *DeviceSetDM) getPoolDevName() string { + return getDevName(devices.getPoolName()) +} + +func (devices *DeviceSetDM) createTask(t TaskType, name string) (*Task, error) { + task := TaskCreate(t) + if task == nil { + return nil, fmt.Errorf("Can't create task of type %d", int(t)) + } + err := task.SetName(name) + if err != nil { + return nil, fmt.Errorf("Can't set task name %s", name) + } + return task, nil +} + +func (devices *DeviceSetDM) getInfo(name string) (*Info, error) { + task, err := devices.createTask(DeviceInfo, name) + if task == nil { + return nil, err + } + err = task.Run() + if err != nil { + return nil, err + } + info, err := task.GetInfo() + if err != nil { + return nil, err + } + return info, nil +} + +func (devices *DeviceSetDM) getStatus(name string) (uint64, uint64, string, string, error) { + task, err := devices.createTask(DeviceStatus, name) + if task == nil { + return 0, 0, "", "", err + } + err = task.Run() + if err != nil { + return 0, 0, "", "", err + } + + devinfo, err := task.GetInfo() + if err != nil { + return 0, 0, "", "", err + } + if devinfo.Exists == 0 { + return 0, 0, "", "", fmt.Errorf("Non existing device %s", name) + } + + var next uintptr = 0 + next, start, length, target_type, params := task.GetNextTarget(next) + + return start, length, target_type, params, nil +} + +func (devices *DeviceSetDM) setTransactionId(oldId uint64, newId uint64) error { + task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) + if task == nil { + return err + } + + err = task.SetSector(0) + if err != nil { + return fmt.Errorf("Can't set sector") + } + + message := fmt.Sprintf("set_transaction_id %d %d", oldId, newId) + err = task.SetMessage(message) + if err != nil { + return fmt.Errorf("Can't set message") + } + + err = task.Run() + if err != nil { + return fmt.Errorf("Error running setTransactionId") + } + return nil +} + +func (devices *DeviceSetDM) hasImage(name string) bool { + dirname := devices.loopbackDir() + filename := path.Join(dirname, name) + + _, err := os.Stat(filename) + return err == nil +} + +func (devices *DeviceSetDM) ensureImage(name string, size int64) (string, error) { + dirname := devices.loopbackDir() + filename := path.Join(dirname, name) + + if err := os.MkdirAll(dirname, 0700); err != nil && !os.IsExist(err) { + return "", err + } + + _, err := os.Stat(filename) + if err != nil { + if !os.IsNotExist(err) { + return "", err + } + log.Printf("Creating loopback file %s for device-manage use", filename) + file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0600) + if err != nil { + return "", err + } + err = file.Truncate(size) + if err != nil { + return "", err + } + } + return filename, nil +} + +func (devices *DeviceSetDM) createPool(dataFile *os.File, metadataFile *os.File) error { + log.Printf("Activating device-mapper pool %s", devices.getPoolName()) + task, err := devices.createTask(DeviceCreate, devices.getPoolName()) + if task == nil { + return err + } + + size, err := GetBlockDeviceSize(dataFile) + if err != nil { + return fmt.Errorf("Can't get data size") + } + + params := metadataFile.Name() + " " + dataFile.Name() + " 512 8192" + err = task.AddTarget(0, size/512, "thin-pool", params) + if err != nil { + return fmt.Errorf("Can't add target") + } + + var cookie uint32 = 0 + err = task.SetCookie(&cookie, 32) + if err != nil { + return fmt.Errorf("Can't set cookie") + } + + err = task.Run() + if err != nil { + return fmt.Errorf("Error running DeviceCreate") + } + + UdevWait(cookie) + + return nil +} + +func (devices *DeviceSetDM) suspendDevice(info *DevInfo) error { + task, err := devices.createTask(DeviceSuspend, info.Name()) + if task == nil { + return err + } + err = task.Run() + if err != nil { + return fmt.Errorf("Error running DeviceSuspend") + } + return nil +} + +func (devices *DeviceSetDM) resumeDevice(info *DevInfo) error { + task, err := devices.createTask(DeviceResume, info.Name()) + if task == nil { + return err + } + + var cookie uint32 = 0 + err = task.SetCookie(&cookie, 32) + if err != nil { + return fmt.Errorf("Can't set cookie") + } + + err = task.Run() + if err != nil { + return fmt.Errorf("Error running DeviceSuspend") + } + + UdevWait(cookie) + + return nil +} + +func (devices *DeviceSetDM) createDevice(deviceId int) error { + task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) + if task == nil { + return err + } + + err = task.SetSector(0) + if err != nil { + return fmt.Errorf("Can't set sector") + } + + message := fmt.Sprintf("create_thin %d", deviceId) + err = task.SetMessage(message) + if err != nil { + return fmt.Errorf("Can't set message") + } + + err = task.Run() + if err != nil { + return fmt.Errorf("Error running createDevice") + } + return nil +} + +func (devices *DeviceSetDM) createSnapDevice(deviceId int, baseInfo *DevInfo) error { + doSuspend := false + devinfo, _ := devices.getInfo(baseInfo.Name()) + if devinfo != nil && devinfo.Exists != 0 { + doSuspend = true + } + + if doSuspend { + err := devices.suspendDevice(baseInfo) + if err != nil { + return err + } + } + + task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) + if task == nil { + _ = devices.resumeDevice(baseInfo) + return err + } + err = task.SetSector(0) + if err != nil { + _ = devices.resumeDevice(baseInfo) + return fmt.Errorf("Can't set sector") + } + + message := fmt.Sprintf("create_snap %d %d", deviceId, baseInfo.DeviceId) + err = task.SetMessage(message) + if err != nil { + _ = devices.resumeDevice(baseInfo) + return fmt.Errorf("Can't set message") + } + + err = task.Run() + if err != nil { + _ = devices.resumeDevice(baseInfo) + return fmt.Errorf("Error running DeviceCreate") + } + + if doSuspend { + err = devices.resumeDevice(baseInfo) + if err != nil { + return err + } + } + + return nil +} + +func (devices *DeviceSetDM) deleteDevice(deviceId int) error { + task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) + if task == nil { + return err + } + + err = task.SetSector(0) + if err != nil { + return fmt.Errorf("Can't set sector") + } + + message := fmt.Sprintf("delete %d", deviceId) + err = task.SetMessage(message) + if err != nil { + return fmt.Errorf("Can't set message") + } + + err = task.Run() + if err != nil { + return fmt.Errorf("Error running deleteDevice") + } + return nil +} + +func (devices *DeviceSetDM) removeDevice(info *DevInfo) error { + task, err := devices.createTask(DeviceRemove, info.Name()) + if task == nil { + return err + } + err = task.Run() + if err != nil { + return fmt.Errorf("Error running removeDevice") + } + return nil +} + +func (devices *DeviceSetDM) activateDevice(info *DevInfo) error { + task, err := devices.createTask(DeviceCreate, info.Name()) + if task == nil { + return err + } + + params := fmt.Sprintf("%s %d", devices.getPoolDevName(), info.DeviceId) + err = task.AddTarget(0, info.Size/512, "thin", params) + if err != nil { + return fmt.Errorf("Can't add target") + } + + var cookie uint32 = 0 + err = task.SetCookie(&cookie, 32) + if err != nil { + return fmt.Errorf("Can't set cookie") + } + + err = task.Run() + if err != nil { + return fmt.Errorf("Error running DeviceCreate") + } + + UdevWait(cookie) + + return nil +} + +func (devices *DeviceSetDM) allocateDeviceId() int { + // TODO: Add smarter reuse of deleted devices + id := devices.nextFreeDevice + devices.nextFreeDevice = devices.nextFreeDevice + 1 + return id +} + +func (devices *DeviceSetDM) allocateTransactionId() uint64 { + devices.NewTransactionId = devices.NewTransactionId + 1 + return devices.NewTransactionId +} + +func (devices *DeviceSetDM) saveMetadata() error { + jsonData, err := json.Marshal(devices.MetaData) + if err != nil { + return err + } + tmpFile, err := ioutil.TempFile(filepath.Dir(devices.jsonFile()), ".json") + if err != nil { + return err + } + + n, err := tmpFile.Write(jsonData) + if err != nil { + return err + } + if n < len(jsonData) { + err = io.ErrShortWrite + } + err = tmpFile.Sync() + if err != nil { + return err + } + err = tmpFile.Close() + if err != nil { + return err + } + err = os.Rename(tmpFile.Name(), devices.jsonFile()) + if err != nil { + return err + } + + if devices.NewTransactionId != devices.TransactionId { + err = devices.setTransactionId(devices.TransactionId, devices.NewTransactionId) + if err != nil { + return err + } + devices.TransactionId = devices.NewTransactionId + } + + return nil +} + +func (devices *DeviceSetDM) registerDevice(id int, hash string, size uint64) (*DevInfo, error) { + transaction := devices.allocateTransactionId() + + info := &DevInfo{ + Hash: hash, + DeviceId: id, + Size: size, + TransactionId: transaction, + Initialized: false, + } + + devices.Devices[hash] = info + err := devices.saveMetadata() + if err != nil { + // Try to remove unused device + devices.Devices[hash] = nil + return nil, err + } + + return info, nil +} + +func (devices *DeviceSetDM) activateDeviceIfNeeded(hash string) error { + info := devices.Devices[hash] + if info == nil { + return fmt.Errorf("Unknown device %s", hash) + } + + name := info.Name() + devinfo, _ := devices.getInfo(name) + if devinfo != nil && devinfo.Exists != 0 { + return nil + } + + return devices.activateDevice(info) +} + +func (devices *DeviceSetDM) createFilesystem(info *DevInfo) error { + devname := info.DevName() + + err := exec.Command("mkfs.ext4", "-E", + "discard,lazy_itable_init=0,lazy_journal_init=0", devname).Run() + if err != nil { + err = exec.Command("mkfs.ext4", "-E", + "discard,lazy_itable_init=0", devname).Run() + } + if err != nil { + return err + } + return nil +} + +func (devices *DeviceSetDM) loadMetaData() error { + _, _, _, params, err := devices.getStatus(devices.getPoolName()) + if err != nil { + return err + } + var currentTransaction uint64 + _, err = fmt.Sscanf(params, "%d", ¤tTransaction) + if err != nil { + return err + } + + devices.TransactionId = currentTransaction + devices.NewTransactionId = devices.TransactionId + + jsonData, err := ioutil.ReadFile(devices.jsonFile()) + if err != nil && !os.IsNotExist(err) { + return err + } + + metadata := &MetaData{ + Devices: make(map[string]*DevInfo), + } + if jsonData != nil { + if err := json.Unmarshal(jsonData, metadata); err != nil { + return err + } + } + devices.MetaData = *metadata + + for hash, d := range devices.Devices { + d.Hash = hash + + if d.DeviceId >= devices.nextFreeDevice { + devices.nextFreeDevice = d.DeviceId + 1 + } + + // If the transaction id is larger than the actual one we lost the device due to some crash + if d.TransactionId > currentTransaction { + log.Printf("Removing lost device %s with id %d", hash, d.TransactionId) + delete(devices.Devices, hash) + } + } + + return nil +} + +func (devices *DeviceSetDM) createBaseLayer(dir string) error { + for pth, typ := range map[string]string{ + "/dev/pts": "dir", + "/dev/shm": "dir", + "/proc": "dir", + "/sys": "dir", + "/.dockerinit": "file", + "/etc/resolv.conf": "file", + "/etc/hosts": "file", + "/etc/hostname": "file", + // "var/run": "dir", + // "var/lock": "dir", + } { + if _, err := os.Stat(path.Join(dir, pth)); err != nil { + if os.IsNotExist(err) { + switch typ { + case "dir": + if err := os.MkdirAll(path.Join(dir, pth), 0755); err != nil { + return err + } + case "file": + if err := os.MkdirAll(path.Join(dir, path.Dir(pth)), 0755); err != nil { + return err + } + + if f, err := os.OpenFile(path.Join(dir, pth), os.O_CREATE, 0755); err != nil { + return err + } else { + f.Close() + } + } + } else { + return err + } + } + } + return nil +} + +func (devices *DeviceSetDM) setupBaseImage() error { + oldInfo := devices.Devices[""] + if oldInfo != nil && oldInfo.Initialized { + return nil + } + + if oldInfo != nil && !oldInfo.Initialized { + log.Printf("Removing uninitialized base image") + if err := devices.RemoveDevice(""); err != nil { + return err + } + } + + log.Printf("Initializing base device-manager snapshot") + + id := devices.allocateDeviceId() + + // Create initial device + err := devices.createDevice(id) + if err != nil { + return err + } + + info, err := devices.registerDevice(id, "", defaultBaseFsSize) + if err != nil { + _ = devices.deleteDevice(id) + return err + } + + log.Printf("Creating filesystem on base device-manager snapshot") + + err = devices.activateDeviceIfNeeded("") + if err != nil { + return err + } + + err = devices.createFilesystem(info) + if err != nil { + return err + } + + tmpDir := path.Join(devices.loopbackDir(), "basefs") + if err = os.MkdirAll(tmpDir, 0700); err != nil && !os.IsExist(err) { + return err + } + + err = devices.MountDevice("", tmpDir) + if err != nil { + return err + } + + err = devices.createBaseLayer(tmpDir) + if err != nil { + _ = syscall.Unmount(tmpDir, 0) + return err + } + + err = syscall.Unmount(tmpDir, 0) + if err != nil { + return err + } + + _ = os.Remove(tmpDir) + + info.Initialized = true + + err = devices.saveMetadata() + if err != nil { + info.Initialized = false + return err + } + + return nil +} + +func (devices *DeviceSetDM) initDevmapper() error { + info, err := devices.getInfo(devices.getPoolName()) + if info == nil { + return err + } + + if info.Exists != 0 { + /* Pool exists, assume everything is up */ + err = devices.loadMetaData() + if err != nil { + return err + } + err = devices.setupBaseImage() + if err != nil { + return err + } + return nil + } + + createdLoopback := false + if !devices.hasImage("data") || !devices.hasImage("metadata") { + /* If we create the loopback mounts we also need to initialize the base fs */ + createdLoopback = true + } + + data, err := devices.ensureImage("data", defaultDataLoopbackSize) + if err != nil { + return err + } + + metadata, err := devices.ensureImage("metadata", defaultMetaDataLoopbackSize) + if err != nil { + return err + } + + dataFile, err := AttachLoopDevice(data) + if err != nil { + return err + } + defer dataFile.Close() + + metadataFile, err := AttachLoopDevice(metadata) + if err != nil { + return err + } + defer metadataFile.Close() + + err = devices.createPool(dataFile, metadataFile) + if err != nil { + return err + } + + if !createdLoopback { + err = devices.loadMetaData() + if err != nil { + return err + } + } + + err = devices.setupBaseImage() + if err != nil { + return err + } + + return nil +} + +func (devices *DeviceSetDM) AddDevice(hash, baseHash string) error { + if err := devices.ensureInit(); err != nil { + return err + } + + if devices.Devices[hash] != nil { + return fmt.Errorf("hash %s already exists", hash) + } + + baseInfo := devices.Devices[baseHash] + if baseInfo == nil { + return fmt.Errorf("Unknown base hash %s", baseHash) + } + + deviceId := devices.allocateDeviceId() + + err := devices.createSnapDevice(deviceId, baseInfo) + if err != nil { + return err + } + + _, err = devices.registerDevice(deviceId, hash, baseInfo.Size) + if err != nil { + _ = devices.deleteDevice(deviceId) + return err + } + return nil +} + +func (devices *DeviceSetDM) RemoveDevice(hash string) error { + if err := devices.ensureInit(); err != nil { + return err + } + + info := devices.Devices[hash] + if info == nil { + return fmt.Errorf("hash %s doesn't exists", hash) + } + + devinfo, _ := devices.getInfo(info.Name()) + if devinfo != nil && devinfo.Exists != 0 { + err := devices.removeDevice(info) + if err != nil { + return err + } + } + + if info.Initialized { + info.Initialized = false + err := devices.saveMetadata() + if err != nil { + return err + } + } + + err := devices.deleteDevice(info.DeviceId) + if err != nil { + return err + } + + _ = devices.allocateTransactionId() + delete(devices.Devices, info.Hash) + + err = devices.saveMetadata() + if err != nil { + devices.Devices[info.Hash] = info + return err + } + + return nil +} + +func (devices *DeviceSetDM) DeactivateDevice(hash string) error { + if err := devices.ensureInit(); err != nil { + return err + } + + info := devices.Devices[hash] + if info == nil { + return fmt.Errorf("hash %s doesn't exists", hash) + } + + devinfo, err := devices.getInfo(info.Name()) + if err != nil { + return err + } + if devinfo.Exists != 0 { + err := devices.removeDevice(info) + if err != nil { + return err + } + } + + return nil +} + +func (devices *DeviceSetDM) MountDevice(hash, path string) error { + if err := devices.ensureInit(); err != nil { + return err + } + + err := devices.activateDeviceIfNeeded(hash) + if err != nil { + return err + } + + info := devices.Devices[hash] + + err = syscall.Mount(info.DevName(), path, "ext4", syscall.MS_MGC_VAL, "discard") + if err != nil && err == syscall.EINVAL { + err = syscall.Mount(info.DevName(), path, "ext4", syscall.MS_MGC_VAL, "") + } + if err != nil { + return err + } + return nil +} + +func (devices *DeviceSetDM) HasDevice(hash string) bool { + if err := devices.ensureInit(); err != nil { + return false + } + + info := devices.Devices[hash] + return info != nil +} + +func (devices *DeviceSetDM) HasInitializedDevice(hash string) bool { + if err := devices.ensureInit(); err != nil { + return false + } + + info := devices.Devices[hash] + return info != nil && info.Initialized +} + +func (devices *DeviceSetDM) SetInitialized(hash string) error { + if err := devices.ensureInit(); err != nil { + return err + } + + info := devices.Devices[hash] + if info == nil { + return fmt.Errorf("Unknown device %s", hash) + } + + info.Initialized = true + err := devices.saveMetadata() + if err != nil { + info.Initialized = false + return err + } + + return nil +} + +func (devices *DeviceSetDM) ensureInit() error { + if (!devices.initialized) { + devices.initialized = true + err := devices.initDevmapper() + if err != nil { + return err + } + } + return nil +} + +func NewDeviceSetDM(root string) *DeviceSetDM { + SetDevDir("/dev") + devices := &DeviceSetDM{ + initialized: false, + root: root, + } + devices.Devices = make(map[string]*DevInfo) + + return devices +} From 167815c85ca5ae5115f1ae739ea61b8f9a6140bd Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 11:25:32 +0200 Subject: [PATCH 003/301] devmapper: Add simple tool to test the DeviceSet commands Upstream-commit: 2b1dc8a8a3b99e6edbdf2cc71bf5461d81b9c354 Component: engine --- .../docker-device-tool/device_tool.go | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 components/engine/devmapper/docker-device-tool/device_tool.go diff --git a/components/engine/devmapper/docker-device-tool/device_tool.go b/components/engine/devmapper/docker-device-tool/device_tool.go new file mode 100644 index 0000000000..28bdf56074 --- /dev/null +++ b/components/engine/devmapper/docker-device-tool/device_tool.go @@ -0,0 +1,62 @@ +package main + +import ( + "fmt" + "github.com/dotcloud/docker/devmapper" + "os" +) + +func usage() { + fmt.Printf("Usage: %s [snap new-id base-id] | [remove id] | [mount id mountpoint]\n", os.Args[0]) + os.Exit(1) +} + +func main() { + devices := devmapper.NewDeviceSetDM("/var/lib/docker") + + if len(os.Args) < 2 { + usage() + } + + cmd := os.Args[1] + if cmd == "snap" { + if len(os.Args) < 4 { + usage() + } + + err := devices.AddDevice(os.Args[2], os.Args[3]) + if err != nil { + fmt.Println("Can't create snap device: ", err) + os.Exit(1) + } + } else if cmd == "remove" { + if len(os.Args) < 3 { + usage() + } + + err := devices.RemoveDevice(os.Args[2]) + if err != nil { + fmt.Println("Can't remove device: ", err) + os.Exit(1) + } + } else if cmd == "mount" { + if len(os.Args) < 4 { + usage() + } + + err := devices.MountDevice(os.Args[2], os.Args[3]) + if err != nil { + fmt.Println("Can't create snap device: ", err) + os.Exit(1) + } + } else { + fmt.Printf("Unknown command %s\n", cmd) + if len(os.Args) < 4 { + usage() + } + + os.Exit(1) + } + + return +} From e6cab9d6882f09b8402fe9d78eb4f1f67bc722a5 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 10 Sep 2013 20:15:23 +0200 Subject: [PATCH 004/301] Add a separate docker-init binary This may be used for the .dockerinit case if the main binary is not statically linked. Upstream-commit: 250bc3f61547c6bcdc17ffe41e8a9a0307099cc3 Component: engine --- components/engine/docker-init/docker-init.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 components/engine/docker-init/docker-init.go diff --git a/components/engine/docker-init/docker-init.go b/components/engine/docker-init/docker-init.go new file mode 100644 index 0000000000..c368c53138 --- /dev/null +++ b/components/engine/docker-init/docker-init.go @@ -0,0 +1,16 @@ +package main + +import ( + "github.com/dotcloud/docker" +) + +var ( + GITCOMMIT string + VERSION string +) + +func main() { + // Running in init mode + docker.SysInit() + return +} From 600d1bb0c4a7a912d689e6d1c241db8d21ea0f1f Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 10 Sep 2013 20:21:15 +0200 Subject: [PATCH 005/301] Runtime: Automatically use docker-init if it exists In some builds the main docker binary is not statically linked, and as such not usable in as the .dockerinit binary, for those cases we look for a separately shipped docker-init binary and use that instead. Upstream-commit: 0f5ccf934e01c75e467e6f35e883cf95bae74f2c Component: engine --- components/engine/runtime.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/components/engine/runtime.go b/components/engine/runtime.go index aff1773fdf..f8dc48562a 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -10,6 +10,7 @@ import ( "os" "os/exec" "path" + "path/filepath" "sort" "strings" "time" @@ -42,7 +43,17 @@ type Runtime struct { var sysInitPath string func init() { - sysInitPath = utils.SelfPath() + selfPath := utils.SelfPath() + + // If we have a separate docker-init, use that, otherwise use the + // main docker binary + dir := filepath.Dir(selfPath) + dockerInitPath := filepath.Join(dir, "docker-init") + if _, err := os.Stat(dockerInitPath); err != nil { + sysInitPath = selfPath + } else { + sysInitPath = dockerInitPath + } } // List returns an array of all containers registered in the runtime. From dc920bde90f7d077443c52f27aaa4043a0c05f71 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 11:41:38 +0200 Subject: [PATCH 006/301] Image: Add runtime and container id args to Mount() We will later need the runtime to get access to the VolumeSet singleton, and the container id to have a name for the volume for the container Upstream-commit: 8637ba710e8bd10ee36aa5c71f5ef54ab9037dfa Component: engine --- components/engine/container.go | 2 +- components/engine/graph_test.go | 5 ++++- components/engine/image.go | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/components/engine/container.go b/components/engine/container.go index 2616fb221f..54451baad6 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -1157,7 +1157,7 @@ func (container *Container) Mount() error { if err != nil { return err } - return image.Mount(container.RootfsPath(), container.rwPath()) + return image.Mount(container.runtime, container.RootfsPath(), container.rwPath(), container.ID) } func (container *Container) Changes() ([]Change, error) { diff --git a/components/engine/graph_test.go b/components/engine/graph_test.go index 471016938d..d89e6efe9d 100644 --- a/components/engine/graph_test.go +++ b/components/engine/graph_test.go @@ -121,6 +121,9 @@ func TestRegister(t *testing.T) { } func TestMount(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + graph := tempGraph(t) defer os.RemoveAll(graph.Root) archive, err := fakeTar() @@ -144,7 +147,7 @@ func TestMount(t *testing.T) { if err := os.MkdirAll(rw, 0700); err != nil { t.Fatal(err) } - if err := image.Mount(rootfs, rw); err != nil { + if err := image.Mount(runtime, rootfs, rw, "testing"); err != nil { t.Fatal(err) } // FIXME: test for mount contents diff --git a/components/engine/image.go b/components/engine/image.go index 9f34160b80..bbf559e23a 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -170,7 +170,7 @@ func (image *Image) TarLayer(compression Compression) (Archive, error) { return Tar(layerPath, compression) } -func (image *Image) Mount(root, rw string) error { +func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { if mounted, err := Mounted(root); err != nil { return err } else if mounted { From 056e5df9a6e3441b5cd069619c8b3eb07bd7b9a7 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 11:44:11 +0200 Subject: [PATCH 007/301] Add DeviceSet interface This interface matches the device-mapper implementation (DeviceSetDM) but is free from any dependencies. This allows core docker code to refer to a DeviceSet without having an explicit dependency on the devmapper package. This is important, because the devmapper package has external dependencies which are not wanted in the docker client app, as it needs to run with minimal dependencies in the docker image. Upstream-commit: ac194fc696fb95045ee5b634d04a9f7093f45685 Component: engine --- components/engine/deviceset.go | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 components/engine/deviceset.go diff --git a/components/engine/deviceset.go b/components/engine/deviceset.go new file mode 100644 index 0000000000..01bdba411f --- /dev/null +++ b/components/engine/deviceset.go @@ -0,0 +1,11 @@ +package docker + +type DeviceSet interface { + AddDevice(hash, baseHash string) error + SetInitialized(hash string) error + DeactivateDevice(hash string) error + RemoveDevice(hash string) error + MountDevice(hash, path string) error + HasDevice(hash string) bool + HasInitializedDevice(hash string) bool +} From 676dd4d04464883064836cb2374893541e8bc498 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 12:03:45 +0200 Subject: [PATCH 008/301] Server: Pass in device-mapper DeviceSet to server This makes docker (but not docker-init) link to libdevmapper and will allow it to use the DeviceSet Upstream-commit: 87e248f52458eca2a40ed6e54e36de89228f6790 Component: engine --- components/engine/docker/docker.go | 3 ++- components/engine/server.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/components/engine/docker/docker.go b/components/engine/docker/docker.go index a0021f3a87..4dcc174d5e 100644 --- a/components/engine/docker/docker.go +++ b/components/engine/docker/docker.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" "github.com/dotcloud/docker" + "github.com/dotcloud/docker/devmapper" "github.com/dotcloud/docker/utils" "io/ioutil" "log" @@ -133,7 +134,7 @@ func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart if flDns != "" { dns = []string{flDns} } - server, err := docker.NewServer(flGraphPath, autoRestart, enableCors, dns) + server, err := docker.NewServer(flGraphPath, devmapper.NewDeviceSetDM(flGraphPath), autoRestart, enableCors, dns) if err != nil { return err } diff --git a/components/engine/server.go b/components/engine/server.go index 88c32d17e1..295b7d80b9 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -1294,7 +1294,7 @@ func (srv *Server) ContainerCopy(name string, resource string, out io.Writer) er } -func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) (*Server, error) { +func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors bool, dns ListOpts) (*Server, error) { if runtime.GOARCH != "amd64" { log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) } From 19e1e13936d028cc22fdaf7220481a74831032d2 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 5 Sep 2013 14:40:12 +0200 Subject: [PATCH 009/301] Runtime: Add DeviceSet singleton This adds a DeviceSet singleton to the Runtime object which will be used for any DeviceMapper dependent code. Upstream-commit: f317a6b6fe31685445ac97a1475136c5ab7860b5 Component: engine --- components/engine/runtime.go | 15 ++++++++++++--- components/engine/runtime_test.go | 5 +++-- components/engine/server.go | 2 +- components/engine/utils_test.go | 3 ++- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/components/engine/runtime.go b/components/engine/runtime.go index f8dc48562a..2ae7c03a03 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -38,6 +38,7 @@ type Runtime struct { volumes *Graph srv *Server Dns []string + deviceSet DeviceSet } var sysInitPath string @@ -75,6 +76,13 @@ func (runtime *Runtime) getContainerElement(id string) *list.Element { return nil } +func (runtime *Runtime) GetDeviceSet() (DeviceSet, error) { + if runtime.deviceSet == nil { + return nil, fmt.Errorf("No device set available") + } + return runtime.deviceSet, nil +} + // Get looks for a container by the specified ID or name, and returns it. // If the container is not found, or if an error occurs, nil is returned. func (runtime *Runtime) Get(name string) *Container { @@ -438,8 +446,8 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a } // FIXME: harmonize with NewGraph() -func NewRuntime(flGraphPath string, autoRestart bool, dns []string) (*Runtime, error) { - runtime, err := NewRuntimeFromDirectory(flGraphPath, autoRestart) +func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns []string) (*Runtime, error) { + runtime, err := NewRuntimeFromDirectory(flGraphPath, deviceSet, autoRestart) if err != nil { return nil, err } @@ -457,7 +465,7 @@ func NewRuntime(flGraphPath string, autoRestart bool, dns []string) (*Runtime, e return runtime, nil } -func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) { +func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool) (*Runtime, error) { runtimeRepo := path.Join(root, "containers") if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) { @@ -494,6 +502,7 @@ func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) { capabilities: &Capabilities{}, autoRestart: autoRestart, volumes: volumes, + deviceSet: deviceSet, } if err := runtime.restore(); err != nil { diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index f4f5d5af1e..503f519d12 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "github.com/dotcloud/docker/utils" + "github.com/dotcloud/docker/devmapper" "io" "log" "net" @@ -87,7 +88,7 @@ func init() { NetworkBridgeIface = unitTestNetworkBridge // Make it our Store root - if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false); err != nil { + if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreBase), false); err != nil { panic(err) } else { globalRuntime = runtime @@ -456,7 +457,7 @@ func TestRestore(t *testing.T) { // Here are are simulating a docker restart - that is, reloading all containers // from scratch - runtime2, err := NewRuntimeFromDirectory(runtime1.root, false) + runtime2, err := NewRuntimeFromDirectory(runtime1.root, devmapper.NewDeviceSetDM(runtime1.root), false) if err != nil { t.Fatal(err) } diff --git a/components/engine/server.go b/components/engine/server.go index 295b7d80b9..1b4c0790ae 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -1298,7 +1298,7 @@ func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors if runtime.GOARCH != "amd64" { log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) } - runtime, err := NewRuntime(flGraphPath, autoRestart, dns) + runtime, err := NewRuntime(flGraphPath, deviceSet, autoRestart, dns) if err != nil { return nil, err } diff --git a/components/engine/utils_test.go b/components/engine/utils_test.go index 740a5fc1bc..bc4dd65a3c 100644 --- a/components/engine/utils_test.go +++ b/components/engine/utils_test.go @@ -2,6 +2,7 @@ package docker import ( "github.com/dotcloud/docker/utils" + "github.com/dotcloud/docker/devmapper" "io" "io/ioutil" "os" @@ -42,7 +43,7 @@ func newTestRuntime() (*Runtime, error) { return nil, err } - runtime, err := NewRuntimeFromDirectory(root, false) + runtime, err := NewRuntimeFromDirectory(root, devmapper.NewDeviceSetDM(root), false) if err != nil { return nil, err } From d814a010c64a3b54fd6e2c361e7f0f84bba04415 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 10 Sep 2013 22:15:18 +0200 Subject: [PATCH 010/301] Runtime: Add MountMethod to allow AUFS and device-mapper to coexist Upstream-commit: 53851474c0b8127442ce11ab38fa0ae8d5c694f0 Component: engine --- components/engine/runtime.go | 48 ++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 2ae7c03a03..1a89340075 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -17,6 +17,13 @@ import ( ) var defaultDns = []string{"8.8.8.8", "8.8.4.4"} +type MountMethod int + +const ( + MountMethodNone MountMethod = iota + MountMethodAUFS + MountMethodDeviceMapper +) type Capabilities struct { MemoryLimit bool @@ -39,6 +46,7 @@ type Runtime struct { srv *Server Dns []string deviceSet DeviceSet + mountMethod MountMethod } var sysInitPath string @@ -76,6 +84,46 @@ func (runtime *Runtime) getContainerElement(id string) *list.Element { return nil } +func hasFilesystemSupport(fstype string) bool { + content, err := ioutil.ReadFile("/proc/filesystems") + if err != nil { + log.Printf("WARNING: Unable to read /proc/filesystems, assuming fs %s is not supported.", fstype) + return false + } + lines := strings.Split(string(content), "\n") + for _, line := range lines { + if strings.HasPrefix(line, "nodev") { + line = line[5:] + } + line = strings.TrimSpace(line) + if line == fstype { + return true + } + } + return false +} + +func (runtime *Runtime) GetMountMethod() MountMethod { + if runtime.mountMethod == MountMethodNone { + // Try to automatically pick a method + if hasFilesystemSupport("aufs") { + log.Printf("Using AUFS backend.") + runtime.mountMethod = MountMethodAUFS + } else { + _ = exec.Command("modprobe", "aufs").Run() + if hasFilesystemSupport("aufs") { + log.Printf("Using AUFS backend.") + runtime.mountMethod = MountMethodAUFS + } else { + log.Printf("Using device-mapper backend.") + runtime.mountMethod = MountMethodDeviceMapper + } + } + } + + return runtime.mountMethod +} + func (runtime *Runtime) GetDeviceSet() (DeviceSet, error) { if runtime.deviceSet == nil { return nil, fmt.Errorf("No device set available") From f67c85708785ad60d17e57374cd6429bad3dc809 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 12:55:48 +0200 Subject: [PATCH 011/301] Image: Initial support for device-mapper mounts This supports creating images from layers and mounting them for running a container. Not supported yet are: * Creating diffs between images/containers * Creating layers for new images from a device-mapper container Upstream-commit: fcd41fe51ae0418d583f3d33dfac7fc0879ca30e Component: engine --- components/engine/image.go | 269 +++++++++++++++++++++++++++++++++++-- 1 file changed, 260 insertions(+), 9 deletions(-) diff --git a/components/engine/image.go b/components/engine/image.go index bbf559e23a..07eca11269 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -15,6 +15,7 @@ import ( "path/filepath" "strconv" "strings" + "syscall" "time" ) @@ -136,6 +137,10 @@ func jsonPath(root string) string { return path.Join(root, "json") } +func mountPath(root string) string { + return path.Join(root, "mount") +} + func MountAUFS(ro []string, rw string, target string) error { // FIXME: Now mount the layers rwBranch := fmt.Sprintf("%v=rw", rw) @@ -170,25 +175,271 @@ func (image *Image) TarLayer(compression Compression) (Archive, error) { return Tar(layerPath, compression) } +func (image *Image) applyLayer(layer, target string) error { + oldmask := syscall.Umask(0) + defer syscall.Umask(oldmask) + err := filepath.Walk(layer, func(srcPath string, f os.FileInfo, err error) error { + if err != nil { + return err + } + + // Skip root + if srcPath == layer { + return nil + } + + var srcStat syscall.Stat_t + err = syscall.Lstat(srcPath, &srcStat) + if err != nil { + return err + } + + relPath, err := filepath.Rel(layer, srcPath) + if err != nil { + return err + } + + targetPath := filepath.Join(target, relPath) + + // Skip AUFS metadata + if matched, err := filepath.Match(".wh..wh.*", relPath); err != nil || matched { + if err != nil || !f.IsDir() { + return err + } + return filepath.SkipDir + } + + // Find out what kind of modification happened + file := filepath.Base(srcPath) + + // If there is a whiteout, then the file was removed + if strings.HasPrefix(file, ".wh.") { + originalFile := file[len(".wh."):] + deletePath := filepath.Join(filepath.Dir(targetPath), originalFile) + + err = os.RemoveAll(deletePath) + if err != nil { + return err + } + } else { + var targetStat = &syscall.Stat_t{} + err := syscall.Lstat(targetPath, targetStat) + if err != nil { + if !os.IsNotExist(err) { + return err + } + targetStat = nil + } + + if targetStat != nil && !(targetStat.Mode&syscall.S_IFDIR == syscall.S_IFDIR && srcStat.Mode&syscall.S_IFDIR == syscall.S_IFDIR) { + // Unless both src and dest are directories we remove the target and recreate it + // This is a bit wasteful in the case of only a mode change, but that is unlikely + // to matter much + err = os.RemoveAll(targetPath) + if err != nil { + return err + } + targetStat = nil + } + + if f.IsDir() { + // Source is a directory + if targetStat == nil { + err = syscall.Mkdir(targetPath, srcStat.Mode&07777) + if err != nil { + return err + } + } else if srcStat.Mode&07777 != targetStat.Mode&07777 { + err = syscall.Chmod(targetPath, srcStat.Mode&07777) + if err != nil { + return err + } + } + } else if srcStat.Mode&syscall.S_IFLNK == syscall.S_IFLNK { + // Source is symlink + link, err := os.Readlink(srcPath) + if err != nil { + return err + } + + err = os.Symlink(link, targetPath) + if err != nil { + return err + } + } else if srcStat.Mode&syscall.S_IFBLK == syscall.S_IFBLK || + srcStat.Mode&syscall.S_IFCHR == syscall.S_IFCHR || + srcStat.Mode&syscall.S_IFIFO == syscall.S_IFIFO || + srcStat.Mode&syscall.S_IFSOCK == syscall.S_IFSOCK { + // Source is special file + err = syscall.Mknod(targetPath, srcStat.Mode, int(srcStat.Rdev)) + if err != nil { + return err + } + } else if srcStat.Mode&syscall.S_IFREG == syscall.S_IFREG { + // Source is regular file + fd, err := syscall.Open(targetPath, syscall.O_CREAT|syscall.O_WRONLY, srcStat.Mode&07777) + if err != nil { + return err + } + dstFile := os.NewFile(uintptr(fd), targetPath) + srcFile, err := os.Open(srcPath) + _, err = io.Copy(dstFile, srcFile) + if err != nil { + return err + } + _ = srcFile.Close() + _ = dstFile.Close() + } else { + return fmt.Errorf("Unknown type for file %s", srcPath) + } + + if srcStat.Mode&syscall.S_IFLNK != syscall.S_IFLNK { + err = syscall.Chown(targetPath, int(srcStat.Uid), int(srcStat.Gid)) + if err != nil { + return err + } + ts := []syscall.Timeval{ + syscall.NsecToTimeval(srcStat.Atim.Nano()), + syscall.NsecToTimeval(srcStat.Mtim.Nano()), + } + syscall.Utimes(targetPath, ts) + } + + } + return nil + }) + return err +} + +func (image *Image) ensureImageDevice(devices DeviceSet) error { + if devices.HasInitializedDevice(image.ID) { + return nil + } + + if image.Parent != "" && !devices.HasInitializedDevice(image.Parent) { + parentImg, err := image.GetParent() + if err != nil { + return fmt.Errorf("Error while getting parent image: %v", err) + } + err = parentImg.ensureImageDevice(devices) + if err != nil { + return err + } + } + + root, err := image.root() + if err != nil { + return err + } + + mountDir := mountPath(root) + if err := os.Mkdir(mountDir, 0600); err != nil && !os.IsExist(err) { + return err + } + + mounted, err := Mounted(mountDir) + if err == nil && mounted { + log.Printf("Image %s is unexpectedly mounted, unmounting...", image.ID) + err = syscall.Unmount(mountDir, 0) + if err != nil { + return err + } + } + + if devices.HasDevice(image.ID) { + log.Printf("Found non-initialized demove-mapper device for image %s, removing", image.ID) + err = devices.RemoveDevice(image.ID) + if err != nil { + return err + } + } + + log.Printf("Creating device-mapper device for image id %s", image.ID) + + err = devices.AddDevice(image.ID, image.Parent) + if err != nil { + return err + } + + utils.Debugf("Mounting device %s at %s for image setup", image.ID, mountDir) + err = devices.MountDevice(image.ID, mountDir) + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + + utils.Debugf("Applying layer %s at %s", image.ID, mountDir) + err = image.applyLayer(layerPath(root), mountDir) + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + + utils.Debugf("Unmounting %s", mountDir) + err = syscall.Unmount(mountDir, 0) + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + + devices.SetInitialized(image.ID) + + // No need to the device-mapper device to hang around once we've written + // the image, it can be enabled on-demand when needed + devices.DeactivateDevice(image.ID) + + return nil +} + func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { if mounted, err := Mounted(root); err != nil { return err } else if mounted { return fmt.Errorf("%s is already mounted", root) } - layers, err := image.layers() - if err != nil { - return err - } // Create the target directories if they don't exist if err := os.Mkdir(root, 0755); err != nil && !os.IsExist(err) { return err } - if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { - return err - } - if err := MountAUFS(layers, rw, root); err != nil { - return err + switch runtime.GetMountMethod() { + case MountMethodNone: + return fmt.Errorf("No supported Mount implementation") + + case MountMethodAUFS: + if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { + return err + } + layers, err := image.layers() + if err != nil { + return err + } + if err := MountAUFS(layers, rw, root); err != nil { + return err + } + + case MountMethodDeviceMapper: + devices, err := runtime.GetDeviceSet() + if err != nil { + return err + } + err = image.ensureImageDevice(devices) + if err != nil { + return err + } + + if !devices.HasDevice(id) { + utils.Debugf("Creating device %s for container based on image %s", id, image.ID) + err = devices.AddDevice(id, image.ID) + if err != nil { + return err + } + } + + utils.Debugf("Mounting container %s at %s for container", id, root) + err = devices.MountDevice(id, root) + if err != nil { + return err + } } return nil } From 3fad97e978e10289e06071d021dfba50fcb60ab8 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 14:21:53 +0200 Subject: [PATCH 012/301] Image: Deactivate image device when unmounting container There is no need to keep all the device-mapper devices active, we can just activate them on demand if needed. Upstream-commit: a89a51128ecdf2db02a333a406752416de8a1db6 Component: engine --- components/engine/container.go | 7 ++++++- components/engine/image.go | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/components/engine/container.go b/components/engine/container.go index 54451baad6..9b4412c6ba 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -1180,7 +1180,12 @@ func (container *Container) Mounted() (bool, error) { } func (container *Container) Unmount() error { - return Unmount(container.RootfsPath()) + image, err := container.GetImage() + if err != nil { + return err + } + err = image.Unmount(container.runtime, container.RootfsPath(), container.ID) + return err } // ShortID returns a shorthand version of the container's id for convenience. diff --git a/components/engine/image.go b/components/engine/image.go index 07eca11269..b306dfd956 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -444,6 +444,30 @@ func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { return nil } +func (image *Image) Unmount(runtime *Runtime, root string, id string) error { + switch runtime.GetMountMethod() { + case MountMethodNone: + return fmt.Errorf("No supported Unmount implementation") + + case MountMethodAUFS: + return Unmount(root) + + case MountMethodDeviceMapper: + err := syscall.Unmount(root, 0) + if err != nil { + return err + } + + // Try to deactivate the device as generally there is no use for it anymore + devices, err := runtime.GetDeviceSet() + if err != nil { + return err; + } + return devices.DeactivateDevice(id) + } + return nil +} + func (image *Image) Changes(rw string) ([]Change, error) { layers, err := image.layers() if err != nil { From abe30d50128a21fa0416cb88ef6fc1ab15e6a27c Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 15:44:01 +0200 Subject: [PATCH 013/301] Image: Always create a .docker-id file in the devices we create Without this there is really no way to map back from the device-mapper devices to the actual docker image/container ids in case the json file somehow got lost Upstream-commit: b125f2334c7b68f624ee1eee06cb9d68922d0314 Component: engine --- components/engine/image.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/components/engine/image.go b/components/engine/image.go index b306dfd956..9d9a64cceb 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -368,6 +368,13 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { return err } + + err = ioutil.WriteFile(path.Join(mountDir, ".docker-id"), []byte(image.ID), 0600) + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + utils.Debugf("Applying layer %s at %s", image.ID, mountDir) err = image.applyLayer(layerPath(root), mountDir) if err != nil { @@ -427,12 +434,14 @@ func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { return err } + createdDevice := false if !devices.HasDevice(id) { utils.Debugf("Creating device %s for container based on image %s", id, image.ID) err = devices.AddDevice(id, image.ID) if err != nil { return err } + createdDevice = true } utils.Debugf("Mounting container %s at %s for container", id, root) @@ -440,6 +449,15 @@ func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { if err != nil { return err } + + if createdDevice { + err = ioutil.WriteFile(path.Join(root, ".docker-id"), []byte(id), 0600) + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + } + } return nil } From eda48ebeaf7c1deb17b87e5a516254cf976f1604 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 5 Sep 2013 15:32:57 +0200 Subject: [PATCH 014/301] devmapper: Base the device-mapper names on the root dir name This means the default is "docker-*", but for tests we get separate prefixes for each test. Upstream-commit: 8f343ea65a05b374ffa64940a1946abf28402689 Component: engine --- .../engine/devmapper/deviceset_devmapper.go | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index d7e122cf28..4544215b63 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -10,6 +10,7 @@ import ( "os/exec" "path" "path/filepath" + "strings" "syscall" ) @@ -18,11 +19,12 @@ const defaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024 const defaultBaseFsSize uint64 = 10 * 1024 * 1024 * 1024 type DevInfo struct { - Hash string `json:"-"` - DeviceId int `json:"device_id"` - Size uint64 `json:"size"` - TransactionId uint64 `json:"transaction_id"` - Initialized bool `json:"initialized"` + Hash string `json:"-"` + DeviceId int `json:"device_id"` + Size uint64 `json:"size"` + TransactionId uint64 `json:"transaction_id"` + Initialized bool `json:"initialized"` + devices *DeviceSetDM `json:"-"` } type MetaData struct { @@ -31,7 +33,8 @@ type MetaData struct { type DeviceSetDM struct { initialized bool - root string + root string + devicePrefix string MetaData TransactionId uint64 NewTransactionId uint64 @@ -47,7 +50,7 @@ func (info *DevInfo) Name() string { if hash == "" { hash = "base" } - return fmt.Sprintf("docker-%s", hash) + return fmt.Sprintf("%s-%s", info.devices.devicePrefix, hash) } func (info *DevInfo) DevName() string { @@ -63,7 +66,7 @@ func (devices *DeviceSetDM) jsonFile() string { } func (devices *DeviceSetDM) getPoolName() string { - return "docker-pool" + return fmt.Sprintf("%s-pool", devices.devicePrefix) } func (devices *DeviceSetDM) getPoolDevName() string { @@ -446,6 +449,7 @@ func (devices *DeviceSetDM) registerDevice(id int, hash string, size uint64) (*D Size: size, TransactionId: transaction, Initialized: false, + devices: devices, } devices.Devices[hash] = info @@ -520,6 +524,7 @@ func (devices *DeviceSetDM) loadMetaData() error { for hash, d := range devices.Devices { d.Hash = hash + d.devices = devices if d.DeviceId >= devices.nextFreeDevice { devices.nextFreeDevice = d.DeviceId + 1 @@ -873,7 +878,7 @@ func (devices *DeviceSetDM) SetInitialized(hash string) error { } func (devices *DeviceSetDM) ensureInit() error { - if (!devices.initialized) { + if !devices.initialized { devices.initialized = true err := devices.initDevmapper() if err != nil { @@ -885,9 +890,16 @@ func (devices *DeviceSetDM) ensureInit() error { func NewDeviceSetDM(root string) *DeviceSetDM { SetDevDir("/dev") + + base := filepath.Base(root) + if !strings.HasPrefix(base, "docker") { + base = "docker-" + base + } + devices := &DeviceSetDM{ initialized: false, - root: root, + root: root, + devicePrefix: base, } devices.Devices = make(map[string]*DevInfo) From 5f5630fd652397752cc0c6f6d70ea4b5322c0351 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 5 Sep 2013 18:03:49 +0200 Subject: [PATCH 015/301] Implement docker diff for device-mapper To do diffing we just compare file metadata, so this relies on things like size and mtime/ctime to catch any changes. Its *possible* to trick this by updating a file without changing the size and setting back the mtime/ctime, but that seems pretty unlikely to happen in reality, and lets us avoid comparing the actual file data. Upstream-commit: 8e8ef7cb5b208ac657af812ebc5ffa783664cf3b Component: engine --- components/engine/changes.go | 104 ++++++++++++++++++++++++++++++++- components/engine/container.go | 6 +- components/engine/image.go | 39 +++++++++++-- 3 files changed, 142 insertions(+), 7 deletions(-) diff --git a/components/engine/changes.go b/components/engine/changes.go index 43573cd606..00c9cc7c77 100644 --- a/components/engine/changes.go +++ b/components/engine/changes.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" "strings" + "syscall" ) type ChangeType int @@ -33,7 +34,7 @@ func (change *Change) String() string { return fmt.Sprintf("%s %s", kind, change.Path) } -func Changes(layers []string, rw string) ([]Change, error) { +func ChangesAUFS(layers []string, rw string) ([]Change, error) { var changes []Change err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error { if err != nil { @@ -104,3 +105,104 @@ func Changes(layers []string, rw string) ([]Change, error) { } return changes, nil } + +func ChangesDirs(newDir, oldDir string) ([]Change, error) { + var changes []Change + err := filepath.Walk(newDir, func(newPath string, f os.FileInfo, err error) error { + if err != nil { + return err + } + + var newStat syscall.Stat_t + err = syscall.Lstat(newPath, &newStat) + if err != nil { + return err + } + + // Rebase path + relPath, err := filepath.Rel(newDir, newPath) + if err != nil { + return err + } + relPath = filepath.Join("/", relPath) + + // Skip root + if relPath == "/" || relPath == "/.docker-id" { + return nil + } + + change := Change{ + Path: relPath, + } + + oldPath := filepath.Join(oldDir, relPath) + + var oldStat = &syscall.Stat_t{} + err = syscall.Lstat(oldPath, oldStat) + if err != nil { + if !os.IsNotExist(err) { + return err + } + oldStat = nil + } + + if oldStat == nil { + change.Kind = ChangeAdd + changes = append(changes, change) + } else { + if oldStat.Ino != newStat.Ino || + oldStat.Mode != newStat.Mode || + oldStat.Uid != newStat.Uid || + oldStat.Gid != newStat.Gid || + oldStat.Rdev != newStat.Rdev || + oldStat.Size != newStat.Size || + oldStat.Blocks != newStat.Blocks || + oldStat.Mtim != newStat.Mtim || + oldStat.Ctim != newStat.Ctim { + change.Kind = ChangeModify + changes = append(changes, change) + } + } + + return nil + }) + if err != nil { + return nil, err + } + err = filepath.Walk(oldDir, func(oldPath string, f os.FileInfo, err error) error { + if err != nil { + return err + } + + // Rebase path + relPath, err := filepath.Rel(oldDir, oldPath) + if err != nil { + return err + } + relPath = filepath.Join("/", relPath) + + // Skip root + if relPath == "/" { + return nil + } + + change := Change{ + Path: relPath, + } + + newPath := filepath.Join(newDir, relPath) + + var newStat = &syscall.Stat_t{} + err = syscall.Lstat(newPath, newStat) + if err != nil && os.IsNotExist(err) { + change.Kind = ChangeDelete + changes = append(changes, change) + } + + return nil + }) + if err != nil { + return nil, err + } + return changes, nil +} diff --git a/components/engine/container.go b/components/engine/container.go index 9b4412c6ba..33b410ee5a 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -1161,11 +1161,15 @@ func (container *Container) Mount() error { } func (container *Container) Changes() ([]Change, error) { + if err := container.EnsureMounted(); err != nil { + return nil, err + } + image, err := container.GetImage() if err != nil { return nil, err } - return image.Changes(container.rwPath()) + return image.Changes(container.runtime, container.RootfsPath(), container.rwPath(), container.ID) } func (container *Container) GetImage() (*Image, error) { diff --git a/components/engine/image.go b/components/engine/image.go index 9d9a64cceb..119e07c70e 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -486,12 +486,41 @@ func (image *Image) Unmount(runtime *Runtime, root string, id string) error { return nil } -func (image *Image) Changes(rw string) ([]Change, error) { - layers, err := image.layers() - if err != nil { - return nil, err +func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, error) { + switch runtime.GetMountMethod() { + case MountMethodAUFS: + layers, err := image.layers() + if err != nil { + return nil, err + } + return ChangesAUFS(layers, rw) + + case MountMethodDeviceMapper: + devices, err := runtime.GetDeviceSet() + if err != nil { + return nil, err + } + + if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { + return nil, err + } + + // We re-use rw for the temporary mount of the base image as its + // not used by device-mapper otherwise + err = devices.MountDevice(image.ID, rw) + if err != nil { + return nil, err + } + + changes, err := ChangesDirs(root, rw) + _ = syscall.Unmount(rw, 0) + if err != nil { + return nil, err + } + return changes, nil } - return Changes(layers, rw) + + return nil, fmt.Errorf("No supported Changes implementation") } func (image *Image) ShortID() string { From 1fb4f4dbcf7fab2eea88e5787b251b5855e4a254 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 5 Sep 2013 20:11:18 +0200 Subject: [PATCH 016/301] Archive: Fix up tar commandline arguments in TarFilter() There is no need to duplicate the compression flags for every element in the filter. Upstream-commit: 8f23945f7f666e3151890de90ebb39db3c7f5ada Component: engine --- components/engine/archive.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/engine/archive.go b/components/engine/archive.go index bb019fb033..80b305a418 100644 --- a/components/engine/archive.go +++ b/components/engine/archive.go @@ -90,8 +90,9 @@ func TarFilter(path string, compression Compression, filter []string) (io.Reader if filter == nil { filter = []string{"."} } + args = append(args, "-c"+compression.Flag()) for _, f := range filter { - args = append(args, "-c"+compression.Flag(), f) + args = append(args, f) } return CmdStream(exec.Command(args[0], args[1:]...)) } From ce8763b12384a74a084e55c430df109972d938db Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 5 Sep 2013 22:10:29 +0200 Subject: [PATCH 017/301] Make TarFilter more useful There are a few changes: * Callers can specify if they want recursive behaviour or not * All file listings to tar are sent on stdin, to handle long lists better * We can pass in a list of filenames which will be created as empty files in the tarball This is exactly what we want for the creation of layer tarballs given a container fs, a set of files to add and a set of whiteout files to create. Upstream-commit: 223280f31967edb7b3eb325cce8a3b82939c0f2b Component: engine --- components/engine/archive.go | 82 ++++++++++++++++++++++++++++--- components/engine/archive_test.go | 6 +-- components/engine/container.go | 2 +- 3 files changed, 78 insertions(+), 12 deletions(-) diff --git a/components/engine/archive.go b/components/engine/archive.go index 80b305a418..f3f7b8c59e 100644 --- a/components/engine/archive.go +++ b/components/engine/archive.go @@ -80,21 +80,73 @@ func (compression *Compression) Extension() string { // Tar creates an archive from the directory at `path`, and returns it as a // stream of bytes. func Tar(path string, compression Compression) (io.Reader, error) { - return TarFilter(path, compression, nil) + return TarFilter(path, compression, nil, true, nil) +} + +func escapeName(name string) string { + escaped := make([]byte,0) + for i, c := range []byte(name) { + if i == 0 && c == '/' { + continue + } + // all printable chars except "-" which is 0x2d + if (0x20 <= c && c <= 0x7E) && c != 0x2d { + escaped = append(escaped, c) + } else { + escaped = append(escaped, fmt.Sprintf("\\%03o", c)...) + } + } + return string(escaped) } // Tar creates an archive from the directory at `path`, only including files whose relative // paths are included in `filter`. If `filter` is nil, then all files are included. -func TarFilter(path string, compression Compression, filter []string) (io.Reader, error) { - args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path} +func TarFilter(path string, compression Compression, filter []string, recursive bool, createFiles []string) (io.Reader, error) { + args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path, "-T", "-",} if filter == nil { filter = []string{"."} } args = append(args, "-c"+compression.Flag()) - for _, f := range filter { - args = append(args, f) + + if !recursive { + args = append(args, "--no-recursion") } - return CmdStream(exec.Command(args[0], args[1:]...)) + + files := "" + for _, f := range filter { + files = files + escapeName(f) + "\n" + } + + tmpDir := "" + + if createFiles != nil { + tmpDir, err := ioutil.TempDir("", "docker-tar") + if err != nil { + return nil, err + } + + files = files + "-C" + tmpDir + "\n" + for _, f := range createFiles { + path := filepath.Join(tmpDir, f) + err := os.MkdirAll(filepath.Dir(path), 0600) + if err != nil { + return nil, err + } + + if file, err := os.OpenFile(path, os.O_CREATE, 0600); err != nil { + return nil, err + } else { + file.Close() + } + files = files + escapeName(f) + "\n" + } + } + + return CmdStream(exec.Command(args[0], args[1:]...), &files, func () { + if tmpDir != "" { + _ = os.RemoveAll(tmpDir) + } + }) } // Untar reads a stream of bytes from `archive`, parses it as a tar archive, @@ -141,7 +193,7 @@ func Untar(archive io.Reader, path string) error { // TarUntar aborts and returns the error. func TarUntar(src string, filter []string, dst string) error { utils.Debugf("TarUntar(%s %s %s)", src, filter, dst) - archive, err := TarFilter(src, Uncompressed, filter) + archive, err := TarFilter(src, Uncompressed, filter, true, nil) if err != nil { return err } @@ -228,7 +280,18 @@ func CopyFileWithTar(src, dst string) error { // CmdStream executes a command, and returns its stdout as a stream. // If the command fails to run or doesn't complete successfully, an error // will be returned, including anything written on stderr. -func CmdStream(cmd *exec.Cmd) (io.Reader, error) { +func CmdStream(cmd *exec.Cmd, input *string, atEnd func()) (io.Reader, error) { + if input != nil { + stdin, err := cmd.StdinPipe() + if err != nil { + return nil, err + } + // Write stdin if any + go func() { + _, _ = stdin.Write([]byte(*input)) + stdin.Close() + }() + } stdout, err := cmd.StdoutPipe() if err != nil { return nil, err @@ -259,6 +322,9 @@ func CmdStream(cmd *exec.Cmd) (io.Reader, error) { } else { pipeW.Close() } + if atEnd != nil { + atEnd() + } }() // Run the command and return the pipe if err := cmd.Start(); err != nil { diff --git a/components/engine/archive_test.go b/components/engine/archive_test.go index 9a0a8e1b9e..c86b4511c4 100644 --- a/components/engine/archive_test.go +++ b/components/engine/archive_test.go @@ -14,7 +14,7 @@ import ( func TestCmdStreamLargeStderr(t *testing.T) { cmd := exec.Command("/bin/sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello") - out, err := CmdStream(cmd) + out, err := CmdStream(cmd, nil, nil) if err != nil { t.Fatalf("Failed to start command: %s", err) } @@ -35,7 +35,7 @@ func TestCmdStreamLargeStderr(t *testing.T) { func TestCmdStreamBad(t *testing.T) { badCmd := exec.Command("/bin/sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1") - out, err := CmdStream(badCmd) + out, err := CmdStream(badCmd, nil, nil) if err != nil { t.Fatalf("Failed to start command: %s", err) } @@ -50,7 +50,7 @@ func TestCmdStreamBad(t *testing.T) { func TestCmdStreamGood(t *testing.T) { cmd := exec.Command("/bin/sh", "-c", "echo hello; exit 0") - out, err := CmdStream(cmd) + out, err := CmdStream(cmd, nil, nil) if err != nil { t.Fatal(err) } diff --git a/components/engine/container.go b/components/engine/container.go index 33b410ee5a..bd1d5a41a5 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -1277,5 +1277,5 @@ func (container *Container) Copy(resource string) (Archive, error) { filter = []string{path.Base(basePath)} basePath = path.Dir(basePath) } - return TarFilter(basePath, Uncompressed, filter) + return TarFilter(basePath, Uncompressed, filter, true, nil) } From 5c4c83afbc4ef10c7b76180f531a09610608fd5b Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 5 Sep 2013 22:14:19 +0200 Subject: [PATCH 018/301] Implement container.ExportRW() on device-mapper Upstream-commit: 94fa3c7bb5cadc31b64630b0fe8abfaeba0644aa Component: engine --- components/engine/container.go | 10 +++++++++- components/engine/image.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/components/engine/container.go b/components/engine/container.go index bd1d5a41a5..fcbad17c86 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -1110,7 +1110,15 @@ func (container *Container) Resize(h, w int) error { } func (container *Container) ExportRw() (Archive, error) { - return Tar(container.rwPath(), Uncompressed) + if err := container.EnsureMounted(); err != nil { + return nil, err + } + + image, err := container.GetImage() + if err != nil { + return nil, err + } + return image.ExportChanges(container.runtime, container.RootfsPath(), container.rwPath(), container.ID) } func (container *Container) RwChecksum() (string, error) { diff --git a/components/engine/image.go b/components/engine/image.go index 119e07c70e..546c54a577 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -523,6 +523,37 @@ func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, er return nil, fmt.Errorf("No supported Changes implementation") } +func (image *Image) ExportChanges(runtime *Runtime, root, rw, id string) (Archive, error) { + switch runtime.GetMountMethod() { + case MountMethodAUFS: + return Tar(rw, Uncompressed) + + case MountMethodDeviceMapper: + changes, err := image.Changes(runtime, root, rw, id) + if err != nil { + return nil, err + } + + files := make([]string, 0) + deletions := make([]string, 0) + for _, change := range changes { + if change.Kind == ChangeModify || change.Kind == ChangeAdd { + files = append(files, change.Path) + } + if change.Kind == ChangeDelete { + base := filepath.Base(change.Path) + dir := filepath.Dir(change.Path) + deletions = append(deletions, filepath.Join(dir, ".wh."+base)) + } + } + + return TarFilter(root, Uncompressed, files, false, deletions) + } + + return nil, fmt.Errorf("No supported Changes implementation") +} + + func (image *Image) ShortID() string { return utils.TruncateID(image.ID) } From 3ffe64603d98b0957245e16c28ba67cb0963b41c Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 6 Sep 2013 12:03:21 +0200 Subject: [PATCH 019/301] Runtime: Delete corresponding devices when deleting container Upstream-commit: 19ba0b851bb00248b62a40695a60fc534d0df2cb Component: engine --- components/engine/runtime.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 1a89340075..d207f5186d 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -282,6 +282,11 @@ func (runtime *Runtime) Destroy(container *Container) error { if err := os.RemoveAll(container.root); err != nil { return fmt.Errorf("Unable to remove filesystem for %v: %v", container.ID, err) } + if runtime.GetMountMethod() == MountMethodDeviceMapper { + if err := runtime.deviceSet.RemoveDevice(container.ID); err != nil { + return fmt.Errorf("Unable to remove device for %v: %v", container.ID, err) + } + } return nil } From 2a4e60c781cddb8b4b9393817b385650de3ab230 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 6 Sep 2013 12:04:00 +0200 Subject: [PATCH 020/301] Delete corresponding Devices when deleting Images If an image is deleted and there is a corresponding device for that image we also delete the image. Upstream-commit: 3f3f5f0bbaafc67d3f77b920194f8d7bfb7bf6ee Component: engine --- components/engine/runtime.go | 13 +++++++++++++ components/engine/runtime_test.go | 2 +- components/engine/server.go | 4 ++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/components/engine/runtime.go b/components/engine/runtime.go index d207f5186d..ea62353a68 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -290,6 +290,19 @@ func (runtime *Runtime) Destroy(container *Container) error { return nil } +func (runtime *Runtime) DeleteImage(id string) error { + err := runtime.graph.Delete(id) + if err != nil { + return err + } + if runtime.GetMountMethod() == MountMethodDeviceMapper && runtime.deviceSet.HasDevice(id) { + if err := runtime.deviceSet.RemoveDevice(id); err != nil { + return fmt.Errorf("Unable to remove device for %v: %v", id, err) + } + } + return nil +} + func (runtime *Runtime) restore() error { wheel := "-\\|/" if os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" { diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index 503f519d12..f9a209008c 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -57,7 +57,7 @@ func cleanup(runtime *Runtime) error { } for _, image := range images { if image.ID != unitTestImageID { - runtime.graph.Delete(image.ID) + runtime.DeleteImage(image.ID) } } return nil diff --git a/components/engine/server.go b/components/engine/server.go index 1b4c0790ae..7f82d7adab 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -1025,7 +1025,7 @@ func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi) error { if err := srv.runtime.repositories.DeleteAll(id); err != nil { return err } - err := srv.runtime.graph.Delete(id) + err := srv.runtime.DeleteImage(id) if err != nil { return err } @@ -1099,7 +1099,7 @@ func (srv *Server) ImageDelete(name string, autoPrune bool) ([]APIRmi, error) { return nil, fmt.Errorf("No such image: %s", name) } if !autoPrune { - if err := srv.runtime.graph.Delete(img.ID); err != nil { + if err := srv.runtime.DeleteImage(img.ID); err != nil { return nil, fmt.Errorf("Error deleting image %s: %s", name, err) } return nil, nil From dc257303364cfad13e441b38225c007fe94268ac Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 6 Sep 2013 14:37:04 +0200 Subject: [PATCH 021/301] Add DeviceSetWrapper This wraps an existing DeviceSet and just adds a prefix to all ids in it. This will be useful for reusing a single DeviceSet for all the tests (but with separate ids) Upstream-commit: 0e686fa2f4d38eb6253e92ad701dd4c9caebfdce Component: engine --- components/engine/deviceset.go | 49 ++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/components/engine/deviceset.go b/components/engine/deviceset.go index 01bdba411f..2caaf153b2 100644 --- a/components/engine/deviceset.go +++ b/components/engine/deviceset.go @@ -9,3 +9,52 @@ type DeviceSet interface { HasDevice(hash string) bool HasInitializedDevice(hash string) bool } + +type DeviceSetWrapper struct { + wrapped DeviceSet + prefix string +} + +func (wrapper *DeviceSetWrapper) wrap(hash string) string { + if hash != "" { + hash = wrapper.prefix + "-" + hash + } + return hash +} + + +func (wrapper *DeviceSetWrapper) AddDevice(hash, baseHash string) error { + return wrapper.wrapped.AddDevice(wrapper.wrap(hash), wrapper.wrap(baseHash)) +} + +func (wrapper *DeviceSetWrapper) SetInitialized(hash string) error { + return wrapper.wrapped.SetInitialized(wrapper.wrap(hash)) +} + +func (wrapper *DeviceSetWrapper) DeactivateDevice(hash string) error { + return wrapper.wrapped.DeactivateDevice(wrapper.wrap(hash)) +} + +func (wrapper *DeviceSetWrapper) RemoveDevice(hash string) error { + return wrapper.wrapped.RemoveDevice(wrapper.wrap(hash)) +} + +func (wrapper *DeviceSetWrapper) MountDevice(hash, path string) error { + return wrapper.wrapped.MountDevice(wrapper.wrap(hash), path) +} + +func (wrapper *DeviceSetWrapper) HasDevice(hash string) bool { + return wrapper.wrapped.HasDevice(wrapper.wrap(hash)) +} + +func (wrapper *DeviceSetWrapper) HasInitializedDevice(hash string) bool { + return wrapper.wrapped.HasInitializedDevice(wrapper.wrap(hash)) +} + +func NewDeviceSetWrapper(wrapped DeviceSet, prefix string) DeviceSet { + wrapper := &DeviceSetWrapper{ + wrapped: wrapped, + prefix: prefix, + } + return wrapper +} From 1e89c8ec2f27cecab6b17d751ccf9806f8e5342a Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 6 Sep 2013 14:38:47 +0200 Subject: [PATCH 022/301] Reuse a single DeviceSetDM for all the tests We wrap the "real" DeviceSet for each test so that we get only a single device-mapper pool and loopback mounts, but still separate out the IDs in the tests. This makes the test run much faster. Upstream-commit: d47c18c5fbe50a2ad6ec011704f86a3c27360ff9 Component: engine --- components/engine/runtime_test.go | 2 +- components/engine/utils_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index f9a209008c..5fa6c46bfe 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -457,7 +457,7 @@ func TestRestore(t *testing.T) { // Here are are simulating a docker restart - that is, reloading all containers // from scratch - runtime2, err := NewRuntimeFromDirectory(runtime1.root, devmapper.NewDeviceSetDM(runtime1.root), false) + runtime2, err := NewRuntimeFromDirectory(runtime1.root, runtime1.deviceSet, false) if err != nil { t.Fatal(err) } diff --git a/components/engine/utils_test.go b/components/engine/utils_test.go index bc4dd65a3c..f5bdac3271 100644 --- a/components/engine/utils_test.go +++ b/components/engine/utils_test.go @@ -2,7 +2,7 @@ package docker import ( "github.com/dotcloud/docker/utils" - "github.com/dotcloud/docker/devmapper" + "path/filepath" "io" "io/ioutil" "os" @@ -43,7 +43,7 @@ func newTestRuntime() (*Runtime, error) { return nil, err } - runtime, err := NewRuntimeFromDirectory(root, devmapper.NewDeviceSetDM(root), false) + runtime, err := NewRuntimeFromDirectory(root, NewDeviceSetWrapper (globalRuntime.deviceSet, filepath.Base(root)), false) if err != nil { return nil, err } From 5e59cd4e23505ef8f74a670a8d0a0b57051315f7 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 6 Sep 2013 16:15:44 +0200 Subject: [PATCH 023/301] Limit the amount of prints during normal runs This removes some Debugf() calls and chages some direct prints to Debugf(). This means we don't get a bunch of spew when running the tests. Upstream-commit: bc7fa7b95773d638754eb72e7921ac328acb2ad6 Component: engine --- components/engine/devmapper/deviceset_devmapper.go | 3 ++- components/engine/image.go | 10 +++------- components/engine/runtime.go | 6 +++--- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index 4544215b63..d163609a7f 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -1,6 +1,7 @@ package devmapper import ( + "github.com/dotcloud/docker/utils" "encoding/json" "fmt" "io" @@ -184,7 +185,7 @@ func (devices *DeviceSetDM) ensureImage(name string, size int64) (string, error) } func (devices *DeviceSetDM) createPool(dataFile *os.File, metadataFile *os.File) error { - log.Printf("Activating device-mapper pool %s", devices.getPoolName()) + utils.Debugf("Activating device-mapper pool %s", devices.getPoolName()) task, err := devices.createTask(DeviceCreate, devices.getPoolName()) if task == nil { return err diff --git a/components/engine/image.go b/components/engine/image.go index 546c54a577..081d28249a 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -339,7 +339,7 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { mounted, err := Mounted(mountDir) if err == nil && mounted { - log.Printf("Image %s is unexpectedly mounted, unmounting...", image.ID) + utils.Debugf("Image %s is unexpectedly mounted, unmounting...", image.ID) err = syscall.Unmount(mountDir, 0) if err != nil { return err @@ -347,21 +347,19 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { } if devices.HasDevice(image.ID) { - log.Printf("Found non-initialized demove-mapper device for image %s, removing", image.ID) + utils.Debugf("Found non-initialized demove-mapper device for image %s, removing", image.ID) err = devices.RemoveDevice(image.ID) if err != nil { return err } } - log.Printf("Creating device-mapper device for image id %s", image.ID) - + utils.Debugf("Creating device-mapper device for image id %s", image.ID) err = devices.AddDevice(image.ID, image.Parent) if err != nil { return err } - utils.Debugf("Mounting device %s at %s for image setup", image.ID, mountDir) err = devices.MountDevice(image.ID, mountDir) if err != nil { _ = devices.RemoveDevice(image.ID) @@ -375,14 +373,12 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { return err } - utils.Debugf("Applying layer %s at %s", image.ID, mountDir) err = image.applyLayer(layerPath(root), mountDir) if err != nil { _ = devices.RemoveDevice(image.ID) return err } - utils.Debugf("Unmounting %s", mountDir) err = syscall.Unmount(mountDir, 0) if err != nil { _ = devices.RemoveDevice(image.ID) diff --git a/components/engine/runtime.go b/components/engine/runtime.go index ea62353a68..1e07141780 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -107,15 +107,15 @@ func (runtime *Runtime) GetMountMethod() MountMethod { if runtime.mountMethod == MountMethodNone { // Try to automatically pick a method if hasFilesystemSupport("aufs") { - log.Printf("Using AUFS backend.") + utils.Debugf("Using AUFS backend.") runtime.mountMethod = MountMethodAUFS } else { _ = exec.Command("modprobe", "aufs").Run() if hasFilesystemSupport("aufs") { - log.Printf("Using AUFS backend.") + utils.Debugf("Using AUFS backend.") runtime.mountMethod = MountMethodAUFS } else { - log.Printf("Using device-mapper backend.") + utils.Debugf("Using device-mapper backend.") runtime.mountMethod = MountMethodDeviceMapper } } From a7892eb98a334225624d8456020e3d068921ad5f Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 10 Sep 2013 21:04:25 +0200 Subject: [PATCH 024/301] Allow specifying the docker client path in _DOCKER_INIT_PATH I currently need this to get the tests running, otherwise it will mount the docker.test binary inside the containers, which doesn't work due to the libdevmapper.so dependency. Upstream-commit: 6938a36c6985336205f1db247baec5e56fdac466 Component: engine --- components/engine/runtime.go | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 1e07141780..30250e713b 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -52,16 +52,21 @@ type Runtime struct { var sysInitPath string func init() { - selfPath := utils.SelfPath() - - // If we have a separate docker-init, use that, otherwise use the - // main docker binary - dir := filepath.Dir(selfPath) - dockerInitPath := filepath.Join(dir, "docker-init") - if _, err := os.Stat(dockerInitPath); err != nil { - sysInitPath = selfPath + env := os.Getenv("_DOCKER_INIT_PATH") + if env != "" { + sysInitPath = env } else { - sysInitPath = dockerInitPath + selfPath := utils.SelfPath() + + // If we have a separate docker-init, use that, otherwise use the + // main docker binary + dir := filepath.Dir(selfPath) + dockerInitPath := filepath.Join(dir, "docker-init") + if _, err := os.Stat(dockerInitPath); err != nil { + sysInitPath = selfPath + } else { + sysInitPath = dockerInitPath + } } } From 1c3fa571b9fcdbe489ec57520f4c618c4e485cb9 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 11:46:16 +0200 Subject: [PATCH 025/301] tests: Store the loopback images for test outside unit-tests This directory is copied to each test prefix which is really slow with the large loopback mounts. Upstream-commit: a7fd1fce5d6fb29a8c627022da7cbbf0f4b740c7 Component: engine --- components/engine/runtime_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index 5fa6c46bfe..6e5ca00518 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -23,6 +23,7 @@ const ( unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 unitTestNetworkBridge = "testdockbr0" unitTestStoreBase = "/var/lib/docker/unit-tests" + unitTestStoreDevicesBase = "/var/lib/docker/unit-tests-devices" testDaemonAddr = "127.0.0.1:4270" testDaemonProto = "tcp" ) @@ -88,7 +89,7 @@ func init() { NetworkBridgeIface = unitTestNetworkBridge // Make it our Store root - if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreBase), false); err != nil { + if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreDevicesBase), false); err != nil { panic(err) } else { globalRuntime = runtime From a4196242f76092325cc575ed263777c2a28b9e6b Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 12:06:55 +0200 Subject: [PATCH 026/301] Always start tests from a clean set of loopback images This way we don't get any issues with leftovers Upstream-commit: 261b0b01dffde42595d987b609d38c3b6d8368f7 Component: engine --- components/engine/runtime_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index 6e5ca00518..5bf9af5fdb 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -88,6 +88,12 @@ func init() { NetworkBridgeIface = unitTestNetworkBridge + // Always start from a clean set of loopback mounts + err := os.RemoveAll(unitTestStoreDevicesBase) + if err != nil { + panic(err) + } + // Make it our Store root if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreDevicesBase), false); err != nil { panic(err) From f82807f4346e3520ed753d8b413e44c2a8ee6b5e Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 12:39:42 +0200 Subject: [PATCH 027/301] DeviceSet: Add UnmountDevice() Right now this does nothing but add a new layer, but it means that all DeviceMounts are paired with DeviceUnmounts so that we can track (and cleanup) active mounts. Upstream-commit: 251a7ed437c17ecb66d33782f0b42633033198dd Component: engine --- components/engine/deviceset.go | 5 +++++ .../engine/devmapper/deviceset_devmapper.go | 12 +++++++++++- components/engine/image.go | 15 ++++++++------- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/components/engine/deviceset.go b/components/engine/deviceset.go index 2caaf153b2..95a3d48f27 100644 --- a/components/engine/deviceset.go +++ b/components/engine/deviceset.go @@ -6,6 +6,7 @@ type DeviceSet interface { DeactivateDevice(hash string) error RemoveDevice(hash string) error MountDevice(hash, path string) error + UnmountDevice(hash, path string) error HasDevice(hash string) bool HasInitializedDevice(hash string) bool } @@ -43,6 +44,10 @@ func (wrapper *DeviceSetWrapper) MountDevice(hash, path string) error { return wrapper.wrapped.MountDevice(wrapper.wrap(hash), path) } +func (wrapper *DeviceSetWrapper) UnmountDevice(hash, path string) error { + return wrapper.wrapped.UnmountDevice(wrapper.wrap(hash), path) +} + func (wrapper *DeviceSetWrapper) HasDevice(hash string) bool { return wrapper.wrapped.HasDevice(wrapper.wrap(hash)) } diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index d163609a7f..e31515042c 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -637,7 +637,7 @@ func (devices *DeviceSetDM) setupBaseImage() error { return err } - err = syscall.Unmount(tmpDir, 0) + err = devices.UnmountDevice("", tmpDir) if err != nil { return err } @@ -840,6 +840,16 @@ func (devices *DeviceSetDM) MountDevice(hash, path string) error { return nil } +func (devices *DeviceSetDM) UnmountDevice(hash, path string) error { + err := syscall.Unmount(path, 0) + if err != nil { + return err + } + + return nil +} + + func (devices *DeviceSetDM) HasDevice(hash string) bool { if err := devices.ensureInit(); err != nil { return false diff --git a/components/engine/image.go b/components/engine/image.go index 081d28249a..da03fb8ff1 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -379,7 +379,7 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { return err } - err = syscall.Unmount(mountDir, 0) + err = devices.UnmountDevice(image.ID, mountDir) if err != nil { _ = devices.RemoveDevice(image.ID) return err @@ -467,16 +467,17 @@ func (image *Image) Unmount(runtime *Runtime, root string, id string) error { return Unmount(root) case MountMethodDeviceMapper: - err := syscall.Unmount(root, 0) - if err != nil { - return err - } - // Try to deactivate the device as generally there is no use for it anymore devices, err := runtime.GetDeviceSet() if err != nil { return err; } + + err = devices.UnmountDevice(id, root) + if err != nil { + return err + } + return devices.DeactivateDevice(id) } return nil @@ -509,7 +510,7 @@ func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, er } changes, err := ChangesDirs(root, rw) - _ = syscall.Unmount(rw, 0) + _ = devices.UnmountDevice(image.ID, rw) if err != nil { return nil, err } From 51abe008c82129f90e6f5c3a52325df696048c11 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 13:47:29 +0200 Subject: [PATCH 028/301] deviceset: Cleanup device sets on test end We unmount all mounts and deactivate all device mapper devices to make sure we're left with no leftovers after the test. Upstream-commit: c6e8813c979bbea8832f47dc6468e805a1a18c3e Component: engine --- components/engine/deviceset.go | 5 ++ .../engine/devmapper/deviceset_devmapper.go | 54 +++++++++++++++++-- components/engine/runtime_test.go | 7 +++ components/engine/z_final_test.go | 2 +- 4 files changed, 63 insertions(+), 5 deletions(-) diff --git a/components/engine/deviceset.go b/components/engine/deviceset.go index 95a3d48f27..5dd608361f 100644 --- a/components/engine/deviceset.go +++ b/components/engine/deviceset.go @@ -9,6 +9,7 @@ type DeviceSet interface { UnmountDevice(hash, path string) error HasDevice(hash string) bool HasInitializedDevice(hash string) bool + Shutdown() error } type DeviceSetWrapper struct { @@ -36,6 +37,10 @@ func (wrapper *DeviceSetWrapper) DeactivateDevice(hash string) error { return wrapper.wrapped.DeactivateDevice(wrapper.wrap(hash)) } +func (wrapper *DeviceSetWrapper) Shutdown() error { + return nil +} + func (wrapper *DeviceSetWrapper) RemoveDevice(hash string) error { return wrapper.wrapped.RemoveDevice(wrapper.wrap(hash)) } diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index e31515042c..4fcfac4464 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -40,6 +40,7 @@ type DeviceSetDM struct { TransactionId uint64 NewTransactionId uint64 nextFreeDevice int + activeMounts map[string]int } func getDevName(name string) string { @@ -348,8 +349,8 @@ func (devices *DeviceSetDM) deleteDevice(deviceId int) error { return nil } -func (devices *DeviceSetDM) removeDevice(info *DevInfo) error { - task, err := devices.createTask(DeviceRemove, info.Name()) +func (devices *DeviceSetDM) removeDevice(name string) error { + task, err := devices.createTask(DeviceRemove, name) if task == nil { return err } @@ -763,7 +764,7 @@ func (devices *DeviceSetDM) RemoveDevice(hash string) error { devinfo, _ := devices.getInfo(info.Name()) if devinfo != nil && devinfo.Exists != 0 { - err := devices.removeDevice(info) + err := devices.removeDevice(info.Name()) if err != nil { return err } @@ -809,7 +810,7 @@ func (devices *DeviceSetDM) DeactivateDevice(hash string) error { return err } if devinfo.Exists != 0 { - err := devices.removeDevice(info) + err := devices.removeDevice(info.Name()) if err != nil { return err } @@ -818,6 +819,39 @@ func (devices *DeviceSetDM) DeactivateDevice(hash string) error { return nil } +func (devices *DeviceSetDM) Shutdown() error { + if !devices.initialized { + return nil + } + + for path, count := range devices.activeMounts { + for i := count; i > 0; i-- { + err := syscall.Unmount(path, 0) + if err != nil { + fmt.Printf("Shutdown unmounting %s, error: %s\n", path, err) + } + } + delete(devices.activeMounts, path) + } + + for _, d := range devices.Devices { + if err := devices.DeactivateDevice(d.Hash); err != nil { + fmt.Printf("Shutdown deactivate %s , error: %s\n", d.Hash, err) + } + } + + + pool := devices.getPoolDevName() + devinfo, err := devices.getInfo(pool) + if err == nil && devinfo.Exists != 0 { + if err := devices.removeDevice(pool); err != nil { + fmt.Printf("Shutdown deactivate %s , error: %s\n", pool, err) + } + } + + return nil +} + func (devices *DeviceSetDM) MountDevice(hash, path string) error { if err := devices.ensureInit(); err != nil { return err @@ -837,6 +871,10 @@ func (devices *DeviceSetDM) MountDevice(hash, path string) error { if err != nil { return err } + + count := devices.activeMounts[path] + devices.activeMounts[path] = count + 1 + return nil } @@ -846,6 +884,13 @@ func (devices *DeviceSetDM) UnmountDevice(hash, path string) error { return err } + count := devices.activeMounts[path] + if count > 1 { + devices.activeMounts[path] = count - 1 + } else { + delete(devices.activeMounts, path) + } + return nil } @@ -913,6 +958,7 @@ func NewDeviceSetDM(root string) *DeviceSetDM { devicePrefix: base, } devices.Devices = make(map[string]*DevInfo) + devices.activeMounts = make(map[string]int) return devices } diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index 5bf9af5fdb..ccdf4d9563 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -64,6 +64,13 @@ func cleanup(runtime *Runtime) error { return nil } +func cleanupLast(runtime *Runtime) error { + cleanup(runtime) + runtime.deviceSet.Shutdown() + return nil +} + + func layerArchive(tarfile string) (io.Reader, error) { // FIXME: need to close f somewhere f, err := os.Open(tarfile) diff --git a/components/engine/z_final_test.go b/components/engine/z_final_test.go index 08a180baaf..c52f87cddb 100644 --- a/components/engine/z_final_test.go +++ b/components/engine/z_final_test.go @@ -11,7 +11,7 @@ func displayFdGoroutines(t *testing.T) { } func TestFinal(t *testing.T) { - cleanup(globalRuntime) + cleanupLast(globalRuntime) t.Logf("Start Fds: %d, Start Goroutines: %d", startFds, startGoroutines) displayFdGoroutines(t) } From 8b38bd9ddffdac7d7a90affb12346eef4f12e176 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 13:48:58 +0200 Subject: [PATCH 029/301] graph test: Unmount image via image.Unmount() This helps us track the unmount Upstream-commit: 3343b3f8f8520c2a1a841675609b4b2c54f3533d Component: engine --- components/engine/graph_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/graph_test.go b/components/engine/graph_test.go index d89e6efe9d..ecb5ffb34e 100644 --- a/components/engine/graph_test.go +++ b/components/engine/graph_test.go @@ -152,7 +152,7 @@ func TestMount(t *testing.T) { } // FIXME: test for mount contents defer func() { - if err := Unmount(rootfs); err != nil { + if err := image.Unmount(runtime, rootfs, "testing"); err != nil { t.Error(err) } }() From 122f65c42ad7a380c57da180d30a118d8b204343 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 14:18:20 +0200 Subject: [PATCH 030/301] Runtime: Only remove device on destroy if it exists Upstream-commit: e1c418cac3cf06d453c7ac10dd54b2b0f617f1f6 Component: engine --- components/engine/runtime.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 30250e713b..54071e96a9 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -287,7 +287,7 @@ func (runtime *Runtime) Destroy(container *Container) error { if err := os.RemoveAll(container.root); err != nil { return fmt.Errorf("Unable to remove filesystem for %v: %v", container.ID, err) } - if runtime.GetMountMethod() == MountMethodDeviceMapper { + if runtime.GetMountMethod() == MountMethodDeviceMapper && runtime.deviceSet.HasDevice(container.ID) { if err := runtime.deviceSet.RemoveDevice(container.ID); err != nil { return fmt.Errorf("Unable to remove device for %v: %v", container.ID, err) } From 5e59d43e57bfe6ff1bf606aa2e0d607a84411d65 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 14:44:19 +0200 Subject: [PATCH 031/301] api_test: Fix PostContainersCreate We can't look for the created file in the rwpath, because that doesn't exist in the device-mapper world, instead look in the RootfsPath. Upstream-commit: 2566e2604c2e079f9597749a1da11f22bb39eb51 Component: engine --- components/engine/api_test.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/components/engine/api_test.go b/components/engine/api_test.go index d24cf7cfda..edff6788e1 100644 --- a/components/engine/api_test.go +++ b/components/engine/api_test.go @@ -647,13 +647,21 @@ func TestPostContainersCreate(t *testing.T) { t.Fatal(err) } - if _, err := os.Stat(path.Join(container.rwPath(), "test")); err != nil { + if err := container.EnsureMounted(); err != nil { + t.Fatalf("Unable to mount container: %s", err) + } + + if _, err := os.Stat(path.Join(container.RootfsPath(), "test")); err != nil { if os.IsNotExist(err) { utils.Debugf("Err: %s", err) t.Fatalf("The test file has not been created") } t.Fatal(err) } + + if err := container.Unmount(); err != nil { + t.Fatalf("Unable to unmount container: %s", err) + } } func TestPostContainersKill(t *testing.T) { From 0254852cc36bd47a9ed51b07e570ffb14a3b0180 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 15:56:59 +0200 Subject: [PATCH 032/301] Container: Inject into the mount, not the rwPath For device-mapper setups we can't just push the file into the rwPath. Upstream-commit: 20bac716b525e1cbd2a778122a4ce41c0c2768c2 Component: engine --- components/engine/container.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/components/engine/container.go b/components/engine/container.go index fcbad17c86..d5dbddb773 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -292,12 +292,17 @@ func (settings *NetworkSettings) PortMappingAPI() []APIPort { // Inject the io.Reader at the given path. Note: do not close the reader func (container *Container) Inject(file io.Reader, pth string) error { - // Make sure the directory exists - if err := os.MkdirAll(path.Join(container.rwPath(), path.Dir(pth)), 0755); err != nil { + if err := container.EnsureMounted(); err != nil { return err } + + // Make sure the directory exists + if err := os.MkdirAll(path.Join(container.RootfsPath(), path.Dir(pth)), 0755); err != nil { + return err + } + // FIXME: Handle permissions/already existing dest - dest, err := os.Create(path.Join(container.rwPath(), pth)) + dest, err := os.Create(path.Join(container.RootfsPath(), pth)) if err != nil { return err } From 9536e01f9bc85078b69ab3045629c1e1751cfeec Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 12 Sep 2013 15:17:39 +0200 Subject: [PATCH 033/301] Utils: Add ShellQuoteArguments Upstream-commit: d80be57c1538905a46f75c95d592acec49a498d6 Component: engine --- components/engine/utils/utils.go | 33 ++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/components/engine/utils/utils.go b/components/engine/utils/utils.go index d417690c0c..94aa0dc902 100644 --- a/components/engine/utils/utils.go +++ b/components/engine/utils/utils.go @@ -1021,3 +1021,36 @@ type StatusError struct { func (e *StatusError) Error() string { return fmt.Sprintf("Status: %d", e.Status) } + +func quote(word string, buf *bytes.Buffer) { + // Bail out early for "simple" strings + if word != "" && !strings.ContainsAny(word, "\\'\"`${[|&;<>()~*?! \t\n") { + buf.WriteString(word) + return + } + + buf.WriteString("''") + + for i := 0; i < len(word); i++ { + b := word[i] + if b == '\'' { + // Replace literal ' with a close ', a \', and a open ' + buf.WriteString("'\\''") + } else { + buf.WriteByte(b) + } + } + + buf.WriteString("''") +} + +func ShellQuoteArguments(args []string) string { + var buf bytes.Buffer + for i, arg := range args { + if i != 0 { + buf.WriteByte(' ') + } + quote(arg, &buf) + } + return buf.String() +} From 40ab3d41380315a0bc8d85135886ca9c34b99fe9 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 12 Sep 2013 15:18:11 +0200 Subject: [PATCH 034/301] lxc: Work around lxc-start need for private mounts lxc-start requires / to be mounted private, otherwise the changes it does inside the container (both mounts and unmounts) will propagate out to the host. We work around this by starting up lxc-start in its own namespace where we set / to rprivate. Unfortunately go can't really execute any code between clone and exec, so we can't do this in a nice way. Instead we have a horrible hack that use the unshare command, the shell and the mount command... Upstream-commit: e40f5c7cb90fbc719241ace45b05c2c61aced658 Component: engine --- components/engine/container.go | 17 ++++++++++++++++- components/engine/utils.go | 15 +++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/components/engine/container.go b/components/engine/container.go index d5dbddb773..549449080c 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -747,6 +747,7 @@ func (container *Container) Start(hostConfig *HostConfig) error { } params := []string{ + "lxc-start", "-n", container.ID, "-f", container.lxcConfigPath(), "--", @@ -795,7 +796,21 @@ func (container *Container) Start(hostConfig *HostConfig) error { params = append(params, "--", container.Path) params = append(params, container.Args...) - container.cmd = exec.Command("lxc-start", params...) + if RootIsShared() { + // lxc-start really needs / to be private, or all kinds of stuff break + // What we really want is to clone into a new namespace and then + // mount / MS_REC|MS_PRIVATE, but since we can't really clone or fork + // without exec in go we have to do this horrible shell hack... + shellString := + "mount --make-rprivate /; exec " + + utils.ShellQuoteArguments(params) + + params = []string{ + "unshare", "-m", "--", "/bin/sh", "-c", shellString, + } + } + + container.cmd = exec.Command(params[0], params[1:]...) // Setup logging of stdout and stderr to disk if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil { diff --git a/components/engine/utils.go b/components/engine/utils.go index aed8ffdd76..babca65bf1 100644 --- a/components/engine/utils.go +++ b/components/engine/utils.go @@ -2,6 +2,7 @@ package docker import ( "fmt" + "io/ioutil" "strings" ) @@ -167,3 +168,17 @@ func parseLxcOpt(opt string) (string, string, error) { } return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil } + +func RootIsShared() bool { + if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil { + for _, line := range strings.Split(string(data), "\n") { + cols := strings.Split(line, " ") + if cols[3] == "/" && cols[4] == "/" { + return strings.HasPrefix(cols[6], "shared") + } + } + } + + // No idea, probably safe to assume so + return true +} From ee414671bbd4a67c8d1101579751ee19cab70424 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 12 Sep 2013 20:30:55 +0200 Subject: [PATCH 035/301] devmapper: Move init layer to top rather than bottom The init layer needs to be topmost to make sure certain files are always there (for instance, the ubuntu:12.10 image wrongly has /dev/shm being a symlink to /run/shm, and we need to override that). However, previously the devmapper code implemented the init layer by putting it in the base devmapper device, which meant layers above it could override these files (so that ubuntu:12.10 broke). So, instead we put the base layer in *each* images devmapper device. This is "safe" because we still have the pristine layer data in the layer directory. Also, it means we diff the container against the image with the init layer applied, so it won't show up in diffs/commits. Upstream-commit: c199ed228baf0e5d33b7739cc2442a32dece7020 Component: engine --- .../engine/devmapper/deviceset_devmapper.go | 62 ------------------- components/engine/image.go | 17 +++++ 2 files changed, 17 insertions(+), 62 deletions(-) diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index 4fcfac4464..670d7621c4 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -542,45 +542,6 @@ func (devices *DeviceSetDM) loadMetaData() error { return nil } -func (devices *DeviceSetDM) createBaseLayer(dir string) error { - for pth, typ := range map[string]string{ - "/dev/pts": "dir", - "/dev/shm": "dir", - "/proc": "dir", - "/sys": "dir", - "/.dockerinit": "file", - "/etc/resolv.conf": "file", - "/etc/hosts": "file", - "/etc/hostname": "file", - // "var/run": "dir", - // "var/lock": "dir", - } { - if _, err := os.Stat(path.Join(dir, pth)); err != nil { - if os.IsNotExist(err) { - switch typ { - case "dir": - if err := os.MkdirAll(path.Join(dir, pth), 0755); err != nil { - return err - } - case "file": - if err := os.MkdirAll(path.Join(dir, path.Dir(pth)), 0755); err != nil { - return err - } - - if f, err := os.OpenFile(path.Join(dir, pth), os.O_CREATE, 0755); err != nil { - return err - } else { - f.Close() - } - } - } else { - return err - } - } - } - return nil -} - func (devices *DeviceSetDM) setupBaseImage() error { oldInfo := devices.Devices[""] if oldInfo != nil && oldInfo.Initialized { @@ -622,29 +583,6 @@ func (devices *DeviceSetDM) setupBaseImage() error { return err } - tmpDir := path.Join(devices.loopbackDir(), "basefs") - if err = os.MkdirAll(tmpDir, 0700); err != nil && !os.IsExist(err) { - return err - } - - err = devices.MountDevice("", tmpDir) - if err != nil { - return err - } - - err = devices.createBaseLayer(tmpDir) - if err != nil { - _ = syscall.Unmount(tmpDir, 0) - return err - } - - err = devices.UnmountDevice("", tmpDir) - if err != nil { - return err - } - - _ = os.Remove(tmpDir) - info.Initialized = true err = devices.saveMetadata() diff --git a/components/engine/image.go b/components/engine/image.go index da03fb8ff1..1a279654d5 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -379,6 +379,23 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { return err } + // The docker init layer is conceptually above all other layers, so we apply + // it for every image. This is safe because the layer directory is the + // definition of the image, and the device-mapper device is just a cache + // of it instantiated. Diffs/commit compare the container device with the + // image device, which will then *not* pick up the init layer changes as + // part of the container changes + dockerinitLayer, err := image.getDockerInitLayer() + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + err = image.applyLayer(dockerinitLayer, mountDir) + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + err = devices.UnmountDevice(image.ID, mountDir) if err != nil { _ = devices.RemoveDevice(image.ID) From 07b0e1a559d2b002ab8226612e7723bbc57b941c Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 11:08:59 +0200 Subject: [PATCH 036/301] RootIsShared() - Fix array out of bounds error This happened for me on the last (empty) line, but better safe than sorry so we make the check general. Upstream-commit: d478a4bb5401d7d657a2a100f98ee892a96fef2a Component: engine --- components/engine/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/utils.go b/components/engine/utils.go index babca65bf1..57737f7b25 100644 --- a/components/engine/utils.go +++ b/components/engine/utils.go @@ -173,7 +173,7 @@ func RootIsShared() bool { if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil { for _, line := range strings.Split(string(data), "\n") { cols := strings.Split(line, " ") - if cols[3] == "/" && cols[4] == "/" { + if len(cols) >= 6 && cols[3] == "/" && cols[4] == "/" { return strings.HasPrefix(cols[6], "shared") } } From 8f15bebd632485622117099ba7c48f96dca17fe3 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 12:56:58 +0200 Subject: [PATCH 037/301] Change how ChangesDirs() works Rather than scan the files in the old directory twice to detect the deletions we now scan both directories twice and then do all the diffing on the in-memory structure. This is more efficient, but it also lets us diff more complex things later that are not exact on-disk trees. Upstream-commit: 02b5f1369ce09d597336e77df98e56d467b8d1ff Component: engine --- components/engine/changes.go | 201 +++++++++++++++++++++++++---------- 1 file changed, 143 insertions(+), 58 deletions(-) diff --git a/components/engine/changes.go b/components/engine/changes.go index 00c9cc7c77..d1b0a25b0d 100644 --- a/components/engine/changes.go +++ b/components/engine/changes.go @@ -106,50 +106,85 @@ func ChangesAUFS(layers []string, rw string) ([]Change, error) { return changes, nil } -func ChangesDirs(newDir, oldDir string) ([]Change, error) { - var changes []Change - err := filepath.Walk(newDir, func(newPath string, f os.FileInfo, err error) error { - if err != nil { - return err - } +type FileInfo struct { + parent *FileInfo + name string + stat syscall.Stat_t + children map[string]*FileInfo +} - var newStat syscall.Stat_t - err = syscall.Lstat(newPath, &newStat) - if err != nil { - return err - } +func (root *FileInfo) LookUp(path string) *FileInfo { + parent := root + if path == "/" { + return root + } - // Rebase path - relPath, err := filepath.Rel(newDir, newPath) - if err != nil { - return err - } - relPath = filepath.Join("/", relPath) - - // Skip root - if relPath == "/" || relPath == "/.docker-id" { - return nil - } - - change := Change{ - Path: relPath, - } - - oldPath := filepath.Join(oldDir, relPath) - - var oldStat = &syscall.Stat_t{} - err = syscall.Lstat(oldPath, oldStat) - if err != nil { - if !os.IsNotExist(err) { - return err + pathElements := strings.Split(path, "/") + for _, elem := range pathElements { + if elem != "" { + child := parent.children[elem] + if child == nil { + return nil } - oldStat = nil + parent = child } + } + return parent +} - if oldStat == nil { - change.Kind = ChangeAdd - changes = append(changes, change) - } else { +func (info *FileInfo)path() string { + if info.parent == nil { + return "/" + } + return filepath.Join(info.parent.path(), info.name) +} + +func (info *FileInfo)unlink() { + if info.parent != nil { + delete(info.parent.children, info.name) + } +} + +func (info *FileInfo)Remove(path string) bool { + child := info.LookUp(path) + if child != nil { + child.unlink() + return true + } + return false +} + +func (info *FileInfo)isDir() bool { + return info.parent == nil || info.stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR +} + + +func (info *FileInfo)addChanges(oldInfo *FileInfo, changes *[]Change) { + if oldInfo == nil { + // add + change := Change{ + Path: info.path(), + Kind: ChangeAdd, + } + *changes = append(*changes, change) + } + + // We make a copy so we can modify it to detect additions + // also, we only recurse on the old dir if the new info is a directory + // otherwise any previous delete/change is considered recursive + oldChildren := make(map[string]*FileInfo) + if oldInfo != nil && info.isDir() { + for k, v := range oldInfo.children { + oldChildren[k] = v + } + } + + for name, newChild := range info.children { + oldChild, _ := oldChildren[name] + if oldChild != nil { + // change? + oldStat := &oldChild.stat + newStat := &newChild.stat if oldStat.Ino != newStat.Ino || oldStat.Mode != newStat.Mode || oldStat.Uid != newStat.Uid || @@ -159,50 +194,100 @@ func ChangesDirs(newDir, oldDir string) ([]Change, error) { oldStat.Blocks != newStat.Blocks || oldStat.Mtim != newStat.Mtim || oldStat.Ctim != newStat.Ctim { - change.Kind = ChangeModify - changes = append(changes, change) + change := Change{ + Path: newChild.path(), + Kind: ChangeModify, + } + *changes = append(*changes, change) } + + // Remove from copy so we can detect deletions + delete(oldChildren, name) } - return nil - }) - if err != nil { - return nil, err + newChild.addChanges(oldChild, changes) } - err = filepath.Walk(oldDir, func(oldPath string, f os.FileInfo, err error) error { + for _, oldChild := range oldChildren { + // delete + change := Change{ + Path: oldChild.path(), + Kind: ChangeDelete, + } + *changes = append(*changes, change) + } + + +} + +func (info *FileInfo)Changes(oldInfo *FileInfo) []Change { + var changes []Change + + info.addChanges(oldInfo, &changes) + + return changes +} + + +func collectFileInfo(sourceDir string) (*FileInfo, error) { + root := &FileInfo { + name: "/", + children: make(map[string]*FileInfo), + } + + err := filepath.Walk(sourceDir, func(path string, f os.FileInfo, err error) error { if err != nil { return err } // Rebase path - relPath, err := filepath.Rel(oldDir, oldPath) + relPath, err := filepath.Rel(sourceDir, path) if err != nil { return err } relPath = filepath.Join("/", relPath) - // Skip root if relPath == "/" { return nil } - change := Change{ - Path: relPath, + parent := root.LookUp(filepath.Dir(relPath)) + if parent == nil { + return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath) } - newPath := filepath.Join(newDir, relPath) - - var newStat = &syscall.Stat_t{} - err = syscall.Lstat(newPath, newStat) - if err != nil && os.IsNotExist(err) { - change.Kind = ChangeDelete - changes = append(changes, change) + info := &FileInfo { + name: filepath.Base(relPath), + children: make(map[string]*FileInfo), + parent: parent, } + if err := syscall.Lstat(path, &info.stat); err != nil { + return err + } + + parent.children[info.name] = info + return nil }) if err != nil { return nil, err } - return changes, nil + return root, nil +} + +func ChangesDirs(newDir, oldDir string) ([]Change, error) { + oldRoot, err := collectFileInfo(oldDir) + if err != nil { + return nil, err + } + newRoot, err := collectFileInfo(newDir) + if err != nil { + return nil, err + } + + // Ignore changes in .docker-id + _ = newRoot.Remove("/.docker-id") + _ = oldRoot.Remove("/.docker-id") + + return newRoot.Changes(oldRoot), nil } From 5f068e31ed86add4efa8dcaaf1ea0dc0645ce2b4 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 15:45:58 +0200 Subject: [PATCH 038/301] Image.applyLayer: Be better at creating identical files There are some changes here that make the file metadata better match the layer files: * Set the mode of the file after the chown, as otherwise the per-group/uid specific flags and e.g. sticky bit is lost * Use lchown instead of chown * Delay mtime updates to after all other changes so that later file creation doesn't change the mtime for the parent directory * Use Futimes in combination with O_PATH|O_NOFOLLOW to set mtime on symlinks Upstream-commit: 99c7d129f422b488f478bc7887f37003dacc83e6 Component: engine --- components/engine/image.go | 55 +++++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/components/engine/image.go b/components/engine/image.go index 1a279654d5..61ca83b208 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -175,7 +175,13 @@ func (image *Image) TarLayer(compression Compression) (Archive, error) { return Tar(layerPath, compression) } +type TimeUpdate struct { + path string + time []syscall.Timeval +} + func (image *Image) applyLayer(layer, target string) error { + var updateTimes []TimeUpdate oldmask := syscall.Umask(0) defer syscall.Umask(oldmask) err := filepath.Walk(layer, func(srcPath string, f os.FileInfo, err error) error { @@ -249,11 +255,6 @@ func (image *Image) applyLayer(layer, target string) error { if err != nil { return err } - } else if srcStat.Mode&07777 != targetStat.Mode&07777 { - err = syscall.Chmod(targetPath, srcStat.Mode&07777) - if err != nil { - return err - } } } else if srcStat.Mode&syscall.S_IFLNK == syscall.S_IFLNK { // Source is symlink @@ -293,22 +294,52 @@ func (image *Image) applyLayer(layer, target string) error { return fmt.Errorf("Unknown type for file %s", srcPath) } + err = syscall.Lchown(targetPath, int(srcStat.Uid), int(srcStat.Gid)) + if err != nil { + return err + } + if srcStat.Mode&syscall.S_IFLNK != syscall.S_IFLNK { - err = syscall.Chown(targetPath, int(srcStat.Uid), int(srcStat.Gid)) + err = syscall.Chmod(targetPath, srcStat.Mode&07777) if err != nil { return err } - ts := []syscall.Timeval{ - syscall.NsecToTimeval(srcStat.Atim.Nano()), - syscall.NsecToTimeval(srcStat.Mtim.Nano()), - } - syscall.Utimes(targetPath, ts) } + ts := []syscall.Timeval{ + syscall.NsecToTimeval(srcStat.Atim.Nano()), + syscall.NsecToTimeval(srcStat.Mtim.Nano()), + } + + u := TimeUpdate { + path: targetPath, + time: ts, + } + + // Delay time updates until all other changes done, or it is + // overwritten for directories (by child changes) + updateTimes = append(updateTimes, u) } return nil }) - return err + if err != nil { + return err + } + + // We do this in reverse order so that children are updated before parents + for i := len(updateTimes) - 1; i >= 0; i-- { + update := updateTimes[i] + + O_PATH := 010000000 // Not in syscall yet + fd, err := syscall.Open(update.path, syscall.O_RDWR | O_PATH | syscall.O_NOFOLLOW, 0600) + if err != nil { + return err + } + syscall.Futimes(fd, update.time) + _ = syscall.Close(fd) + } + + return nil } func (image *Image) ensureImageDevice(devices DeviceSet) error { From 834ac3e088081dedf0e3fd7411a42eefc31bf954 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 15:49:57 +0200 Subject: [PATCH 039/301] Changes: Better metadata comparison Change the comparison to better handle files that are copied during container creation but not actually changed: * Inode - this will change during a copy * ctime - this will change during a copy (as we can't set it back) * blocksize - this will change for sparse files during copy * size for directories - this can change anytime but doesn't necessarily reflect an actual contents change * Compare mtimes at microsecond precision (as this is what utimes has) Upstream-commit: 36603e68e33dd5ab5c317c181e023f7ef7356434 Component: engine --- components/engine/changes.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/components/engine/changes.go b/components/engine/changes.go index d1b0a25b0d..53fa2d49b4 100644 --- a/components/engine/changes.go +++ b/components/engine/changes.go @@ -185,15 +185,22 @@ func (info *FileInfo)addChanges(oldInfo *FileInfo, changes *[]Change) { // change? oldStat := &oldChild.stat newStat := &newChild.stat - if oldStat.Ino != newStat.Ino || - oldStat.Mode != newStat.Mode || + // Note: We can't compare inode or ctime or blocksize here, because these change + // when copying a file into a container. However, that is not generally a problem + // because any content change will change mtime, and any status change should + // be visible when actually comparing the stat fields. The only time this + // breaks down is if some code intentionally hides a change by setting + // back mtime + oldMtime := syscall.NsecToTimeval(oldStat.Mtim.Nano()) + newMtime := syscall.NsecToTimeval(oldStat.Mtim.Nano()) + if oldStat.Mode != newStat.Mode || oldStat.Uid != newStat.Uid || oldStat.Gid != newStat.Gid || oldStat.Rdev != newStat.Rdev || - oldStat.Size != newStat.Size || - oldStat.Blocks != newStat.Blocks || - oldStat.Mtim != newStat.Mtim || - oldStat.Ctim != newStat.Ctim { + // Don't look at size for dirs, its not a good measure of change + (oldStat.Size != newStat.Size && oldStat.Mode &syscall.S_IFDIR != syscall.S_IFDIR) || + oldMtime.Sec != newMtime.Sec || + oldMtime.Usec != newMtime.Usec { change := Change{ Path: newChild.path(), Kind: ChangeModify, From ede74ca1b4ec36f98e008aa905f3f2b95754289f Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 15:56:06 +0200 Subject: [PATCH 040/301] Add Changes.ChangesLayers() This calculates the difference between a set of layers and a directory tree. Upstream-commit: ad0a6a03e3595aa04cf731cf17e90be87163389a Component: engine --- components/engine/changes.go | 95 +++++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/components/engine/changes.go b/components/engine/changes.go index 53fa2d49b4..49802d3170 100644 --- a/components/engine/changes.go +++ b/components/engine/changes.go @@ -235,11 +235,88 @@ func (info *FileInfo)Changes(oldInfo *FileInfo) []Change { } -func collectFileInfo(sourceDir string) (*FileInfo, error) { +func newRootFileInfo() *FileInfo { root := &FileInfo { name: "/", children: make(map[string]*FileInfo), } + return root +} + +func applyLayer(root *FileInfo, layer string) error { + err := filepath.Walk(layer, func(layerPath string, f os.FileInfo, err error) error { + if err != nil { + return err + } + + // Skip root + if layerPath == layer { + return nil + } + + // rebase path + relPath, err := filepath.Rel(layer, layerPath) + if err != nil { + return err + } + relPath = filepath.Join("/", relPath) + + // Skip AUFS metadata + if matched, err := filepath.Match("/.wh..wh.*", relPath); err != nil || matched { + if err != nil || !f.IsDir() { + return err + } + return filepath.SkipDir + } + + var layerStat syscall.Stat_t + err = syscall.Lstat(layerPath, &layerStat) + if err != nil { + return err + } + + file := filepath.Base(relPath) + // If there is a whiteout, then the file was removed + if strings.HasPrefix(file, ".wh.") { + originalFile := file[len(".wh."):] + deletePath := filepath.Join(filepath.Dir(relPath), originalFile) + + root.Remove(deletePath) + } else { + // Added or changed file + existing := root.LookUp(relPath) + if existing != nil { + // Changed file + existing.stat = layerStat + if !existing.isDir() { + // Changed from dir to non-dir, delete all previous files + existing.children = make(map[string]*FileInfo) + } + } else { + // Added file + parent := root.LookUp(filepath.Dir(relPath)) + if parent == nil { + return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath) + } + + info := &FileInfo { + name: filepath.Base(relPath), + children: make(map[string]*FileInfo), + parent: parent, + stat: layerStat, + } + + parent.children[info.name] = info + } + } + return nil + }) + return err +} + + +func collectFileInfo(sourceDir string) (*FileInfo, error) { + root := newRootFileInfo() err := filepath.Walk(sourceDir, func(path string, f os.FileInfo, err error) error { if err != nil { @@ -282,6 +359,22 @@ func collectFileInfo(sourceDir string) (*FileInfo, error) { return root, nil } +func ChangesLayers(newDir string, layers []string) ([]Change, error) { + newRoot, err := collectFileInfo(newDir) + if err != nil { + return nil, err + } + oldRoot := newRootFileInfo() + for i := len(layers)-1; i >= 0; i-- { + layer := layers[i] + if err = applyLayer(oldRoot, layer); err != nil { + return nil, err + } + } + + return newRoot.Changes(oldRoot), nil +} + func ChangesDirs(newDir, oldDir string) ([]Change, error) { oldRoot, err := collectFileInfo(oldDir) if err != nil { From 9e1662b046905e4b54cd921c0f19d9ad7e23622b Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 15:58:14 +0200 Subject: [PATCH 041/301] Add trivial copy-based CoW backend This creates a container by copying the corresponding files from the layers into the containers. This is not gonna be very useful on a developer setup, as there is no copy-on-write or general diskspace sharing. It also makes container instantiation slower. However, it may be useful in deployment where we don't always have a lot of containers running (long-running daemons) and where we don't do a lot of docker commits. Upstream-commit: adae6849871fad0d74945fa1731712ea784e9a88 Component: engine --- components/engine/container.go | 6 ++- components/engine/image.go | 68 ++++++++++++++++++++++++++++++++-- components/engine/runtime.go | 2 + 3 files changed, 71 insertions(+), 5 deletions(-) diff --git a/components/engine/container.go b/components/engine/container.go index 549449080c..35cd11080f 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -1208,7 +1208,11 @@ func (container *Container) GetImage() (*Image, error) { } func (container *Container) Mounted() (bool, error) { - return Mounted(container.RootfsPath()) + image, err := container.GetImage() + if err != nil { + return false, err + } + return image.Mounted(container.runtime, container.RootfsPath(), container.rwPath()) } func (container *Container) Unmount() error { diff --git a/components/engine/image.go b/components/engine/image.go index 61ca83b208..2c16aeb6d4 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -442,16 +442,38 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { return nil } +func (image *Image) Mounted(runtime *Runtime, root, rw string) (bool, error) { + method := runtime.GetMountMethod() + if method == MountMethodFilesystem { + if _, err := os.Stat(rw); err != nil { + if os.IsNotExist(err) { + err = nil + } + return false, err + } + mountedPath := path.Join(rw, ".fs-mounted") + if _, err := os.Stat(mountedPath); err != nil { + if os.IsNotExist(err) { + err = nil + } + return false, err + } + return true, nil + } else { + return Mounted(root) + } +} + func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { - if mounted, err := Mounted(root); err != nil { - return err - } else if mounted { + if mounted, _ := image.Mounted(runtime, root, rw); mounted { return fmt.Errorf("%s is already mounted", root) } + // Create the target directories if they don't exist if err := os.Mkdir(root, 0755); err != nil && !os.IsExist(err) { return err } + switch runtime.GetMountMethod() { case MountMethodNone: return fmt.Errorf("No supported Mount implementation") @@ -502,6 +524,29 @@ func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { } } + case MountMethodFilesystem: + if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { + return err + } + + layers, err := image.layers() + if err != nil { + return err + } + + for i := len(layers)-1; i >= 0; i-- { + layer := layers[i] + if err = image.applyLayer(layer, root); err != nil { + return err + } + } + + mountedPath := path.Join(rw, ".fs-mounted") + fo, err := os.Create(mountedPath) + if err != nil { + return err + } + fo.Close() } return nil } @@ -527,7 +572,11 @@ func (image *Image) Unmount(runtime *Runtime, root string, id string) error { } return devices.DeactivateDevice(id) + + case MountMethodFilesystem: + return nil } + return nil } @@ -563,6 +612,17 @@ func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, er return nil, err } return changes, nil + + case MountMethodFilesystem: + layers, err := image.layers() + if err != nil { + return nil, err + } + changes, err := ChangesLayers(root, layers) + if err != nil { + return nil, err + } + return changes, nil } return nil, fmt.Errorf("No supported Changes implementation") @@ -573,7 +633,7 @@ func (image *Image) ExportChanges(runtime *Runtime, root, rw, id string) (Archiv case MountMethodAUFS: return Tar(rw, Uncompressed) - case MountMethodDeviceMapper: + case MountMethodFilesystem, MountMethodDeviceMapper: changes, err := image.Changes(runtime, root, rw, id) if err != nil { return nil, err diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 54071e96a9..52188f1384 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -23,6 +23,7 @@ const ( MountMethodNone MountMethod = iota MountMethodAUFS MountMethodDeviceMapper + MountMethodFilesystem ) type Capabilities struct { @@ -124,6 +125,7 @@ func (runtime *Runtime) GetMountMethod() MountMethod { runtime.mountMethod = MountMethodDeviceMapper } } + runtime.mountMethod = MountMethodFilesystem } return runtime.mountMethod From 468efce93c669dfb9d685f588d0b2d4459da69f5 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 16:04:07 +0200 Subject: [PATCH 042/301] Remove accidental commit that enabled MountMethodFilesystem Upstream-commit: 5415804c9d7af6c75a147252ac17f71b9c3f0069 Component: engine --- components/engine/runtime.go | 1 - 1 file changed, 1 deletion(-) diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 52188f1384..2a0dbe6a3e 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -125,7 +125,6 @@ func (runtime *Runtime) GetMountMethod() MountMethod { runtime.mountMethod = MountMethodDeviceMapper } } - runtime.mountMethod = MountMethodFilesystem } return runtime.mountMethod From 65124d19b36f03d816605bda88a752d1e0f19dd0 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 16:35:56 +0200 Subject: [PATCH 043/301] Add CopyFile that can use btrfs reflinks if availible Upstream-commit: cda87540135a85bd8d45d4eb4853d6962114ec55 Component: engine --- components/engine/utils.go | 43 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/components/engine/utils.go b/components/engine/utils.go index 57737f7b25..44573927a1 100644 --- a/components/engine/utils.go +++ b/components/engine/utils.go @@ -1,9 +1,33 @@ package docker +/* +#include +#include +#include + +// See linux.git/fs/btrfs/ioctl.h +#define BTRFS_IOCTL_MAGIC 0x94 +#define BTRFS_IOC_CLONE _IOW(BTRFS_IOCTL_MAGIC, 9, int) + +int +btrfs_reflink(int fd_out, int fd_in) +{ + int res; + res = ioctl(fd_out, BTRFS_IOC_CLONE, fd_in); + if (res < 0) + return errno; + return 0; +} + +*/ +import "C" import ( "fmt" + "io" "io/ioutil" + "os" "strings" + "syscall" ) // Compare two Config struct. Do not compare the "Image" nor "Hostname" fields @@ -182,3 +206,22 @@ func RootIsShared() bool { // No idea, probably safe to assume so return true } + +func BtrfsReflink(fd_out, fd_in uintptr) error { + res := C.btrfs_reflink(C.int(fd_out), C.int(fd_in)) + if res != 0 { + return syscall.Errno(res) + } + return nil +} + +func CopyFile(dstFile, srcFile *os.File) error { + err := BtrfsReflink(dstFile.Fd(), srcFile.Fd()) + if err == nil { + return nil + } + + // Fall back to normal copy + _, err = io.Copy(dstFile, srcFile) + return err +} From a3c699f6e98d547262a90c646d1b5f62e7e524eb Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 16:36:32 +0200 Subject: [PATCH 044/301] applyLayer() use btrfs reflinks if availible We use the new file copy helper which uses btrfs reflinks if availible. Upstream-commit: 062a2b32e9500107841a52d3a63b9232a1cfde70 Component: engine --- components/engine/image.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/components/engine/image.go b/components/engine/image.go index 2c16aeb6d4..7819873757 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -284,12 +284,16 @@ func (image *Image) applyLayer(layer, target string) error { } dstFile := os.NewFile(uintptr(fd), targetPath) srcFile, err := os.Open(srcPath) - _, err = io.Copy(dstFile, srcFile) + if err != nil { + _ = dstFile.Close() + return err + } + err = CopyFile(dstFile, srcFile) + _ = dstFile.Close() + _ = srcFile.Close() if err != nil { return err } - _ = srcFile.Close() - _ = dstFile.Close() } else { return fmt.Errorf("Unknown type for file %s", srcPath) } From 63649a9435b72719c4e0ba1e19c44ac16fb3c7e0 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 14:39:47 +0200 Subject: [PATCH 045/301] devmapper: Fix loopback mount code Typo in the loop-control code made it always fall back to the old method of opening loopback devices. Upstream-commit: cc28829429f5f11da287ecb75ee5b3e5f05d31ad Component: engine --- components/engine/devmapper/devmapper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/devmapper/devmapper.go b/components/engine/devmapper/devmapper.go index f007091827..75742628a2 100644 --- a/components/engine/devmapper/devmapper.go +++ b/components/engine/devmapper/devmapper.go @@ -27,7 +27,7 @@ attach_loop_device(const char *filename, int *loop_fd_out) start_index = 0; fd = open("/dev/loop-control", O_RDONLY); - if (fd == 0) { + if (fd >= 0) { start_index = ioctl(fd, LOOP_CTL_GET_FREE); close(fd); From fbdbec9ab503b43407c960224e88f950b5254c36 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 14:57:22 +0200 Subject: [PATCH 046/301] image: Handle systems that don't support O_PATH when updating timestamp Older kernel can't handle O_PATH in open() so this will fail on dirs and symlinks. For dirs wa can fallback to the normal Utimes, but for symlinks there is not much to do but ignore their timestamps. Upstream-commit: ed658156133862b3f181c9d3061be24b91435095 Component: engine --- components/engine/image.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/components/engine/image.go b/components/engine/image.go index 7819873757..b3e8de3e55 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -336,11 +336,21 @@ func (image *Image) applyLayer(layer, target string) error { O_PATH := 010000000 // Not in syscall yet fd, err := syscall.Open(update.path, syscall.O_RDWR | O_PATH | syscall.O_NOFOLLOW, 0600) - if err != nil { - return err + if err == syscall.EISDIR || err == syscall.ELOOP { + // O_PATH not supported, use Utimes except on symlinks where Utimes doesn't work + if err != syscall.ELOOP { + err = syscall.Utimes(update.path, update.time) + if err != nil { + return err + } + } + } else { + if err != nil { + return err + } + syscall.Futimes(fd, update.time) + _ = syscall.Close(fd) } - syscall.Futimes(fd, update.time) - _ = syscall.Close(fd) } return nil From f2940537fb4e7645d16cc9a9a9ee75b34c872bd1 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 14:59:27 +0200 Subject: [PATCH 047/301] Image: unmount device before removing it on failures If we don't do this the remove will fail due to EBUSY Upstream-commit: 009d0f9d81bbd5e130520986ce84e8f097d88a52 Component: engine --- components/engine/image.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/engine/image.go b/components/engine/image.go index b3e8de3e55..16abf9eef0 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -414,6 +414,7 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { err = ioutil.WriteFile(path.Join(mountDir, ".docker-id"), []byte(image.ID), 0600) if err != nil { + _ = devices.UnmountDevice(image.ID, mountDir) _ = devices.RemoveDevice(image.ID) return err } @@ -432,11 +433,13 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { // part of the container changes dockerinitLayer, err := image.getDockerInitLayer() if err != nil { + _ = devices.UnmountDevice(image.ID, mountDir) _ = devices.RemoveDevice(image.ID) return err } err = image.applyLayer(dockerinitLayer, mountDir) if err != nil { + _ = devices.UnmountDevice(image.ID, mountDir) _ = devices.RemoveDevice(image.ID) return err } From d224af31e1357de6ca1c6e4f054b42a1388462d6 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 15:44:43 +0200 Subject: [PATCH 048/301] ShellQuoteArguments: Fix quoting This accidentally used two quotes to start/end each quoted string. Upstream-commit: f99f39abaa5a43e9abc19add5b9d1253a1d22485 Component: engine --- components/engine/utils/utils.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/utils/utils.go b/components/engine/utils/utils.go index 94aa0dc902..6daac53a03 100644 --- a/components/engine/utils/utils.go +++ b/components/engine/utils/utils.go @@ -1029,7 +1029,7 @@ func quote(word string, buf *bytes.Buffer) { return } - buf.WriteString("''") + buf.WriteString("'") for i := 0; i < len(word); i++ { b := word[i] @@ -1041,7 +1041,7 @@ func quote(word string, buf *bytes.Buffer) { } } - buf.WriteString("''") + buf.WriteString("'") } func ShellQuoteArguments(args []string) string { From f35010d2a6a17d404ca925d83ed5427824cd156c Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 19:20:05 +0200 Subject: [PATCH 049/301] runtime test: Ensure all containers are unmounted at nuke() Otherwise we may leave around e.g. devmapper mounts Upstream-commit: 67788723c99cda8b41e5a488b988e2a72732d684 Component: engine --- components/engine/container.go | 9 +++++++++ components/engine/runtime_test.go | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/components/engine/container.go b/components/engine/container.go index 35cd11080f..f5f7d0ef80 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -1180,6 +1180,15 @@ func (container *Container) EnsureMounted() error { return container.Mount() } +func (container *Container) EnsureUnmounted() error { + if mounted, err := container.Mounted(); err != nil { + return err + } else if !mounted { + return nil + } + return container.Unmount() +} + func (container *Container) Mount() error { image, err := container.GetImage() if err != nil { diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index ccdf4d9563..684ca005cd 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -44,6 +44,10 @@ func nuke(runtime *Runtime) error { }(container) } wg.Wait() + + for _, container := range runtime.List() { + container.EnsureUnmounted() + } return os.RemoveAll(runtime.root) } From 9f649d336c5b248ed42f1f8ea8cf8d7266274108 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 19:22:23 +0200 Subject: [PATCH 050/301] image: Unmount before removing device in error paths The device remove fails unless we unmount first Upstream-commit: 41399ac005caa4de2dbb744e48f058f6a15e2d2b Component: engine --- components/engine/image.go | 1 + 1 file changed, 1 insertion(+) diff --git a/components/engine/image.go b/components/engine/image.go index 16abf9eef0..f495e8e8fc 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -421,6 +421,7 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { err = image.applyLayer(layerPath(root), mountDir) if err != nil { + _ = devices.UnmountDevice(image.ID, mountDir) _ = devices.RemoveDevice(image.ID) return err } From 30395e320c1bebe6bdc03487af181bef06676280 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 20:38:06 +0200 Subject: [PATCH 051/301] Add DeviceSet.HasActivatedDevice() This lets you see if a device has been activated Upstream-commit: 395bce4c4174014cb3264c35a7c6f97a2cb0948f Component: engine --- components/engine/deviceset.go | 5 +++++ .../engine/devmapper/deviceset_devmapper.go | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/components/engine/deviceset.go b/components/engine/deviceset.go index 5dd608361f..cf422acb3a 100644 --- a/components/engine/deviceset.go +++ b/components/engine/deviceset.go @@ -9,6 +9,7 @@ type DeviceSet interface { UnmountDevice(hash, path string) error HasDevice(hash string) bool HasInitializedDevice(hash string) bool + HasActivatedDevice(hash string) bool Shutdown() error } @@ -61,6 +62,10 @@ func (wrapper *DeviceSetWrapper) HasInitializedDevice(hash string) bool { return wrapper.wrapped.HasInitializedDevice(wrapper.wrap(hash)) } +func (wrapper *DeviceSetWrapper) HasActivatedDevice(hash string) bool { + return wrapper.wrapped.HasActivatedDevice(wrapper.wrap(hash)) +} + func NewDeviceSetWrapper(wrapped DeviceSet, prefix string) DeviceSet { wrapper := &DeviceSetWrapper{ wrapped: wrapped, diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index 670d7621c4..bbf1fa6adc 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -851,6 +851,23 @@ func (devices *DeviceSetDM) HasInitializedDevice(hash string) bool { return info != nil && info.Initialized } +func (devices *DeviceSetDM) HasActivatedDevice(hash string) bool { + if err := devices.ensureInit(); err != nil { + return false + } + + info := devices.Devices[hash] + if info == nil { + return false + } + name := info.Name() + devinfo, _ := devices.getInfo(name) + if devinfo != nil && devinfo.Exists != 0 { + return true + } + return false +} + func (devices *DeviceSetDM) SetInitialized(hash string) error { if err := devices.ensureInit(); err != nil { return err From ceff235a4be9322caa8b0da6a0d6711149103c80 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 19:23:35 +0200 Subject: [PATCH 052/301] Image.Changes: Deactivate image device after unmounting it There is no need to keep the image device around if we were the onces creating the device. Upstream-commit: 6c7ae06435d6e288024691f1133d7a2a24fd8ef3 Component: engine --- components/engine/image.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/engine/image.go b/components/engine/image.go index f495e8e8fc..ff8b836c39 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -617,6 +617,8 @@ func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, er return nil, err } + wasActivated := devices.HasActivatedDevice(image.ID) + // We re-use rw for the temporary mount of the base image as its // not used by device-mapper otherwise err = devices.MountDevice(image.ID, rw) @@ -626,6 +628,9 @@ func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, er changes, err := ChangesDirs(root, rw) _ = devices.UnmountDevice(image.ID, rw) + if !wasActivated { + _ = devices.DeactivateDevice(image.ID) + } if err != nil { return nil, err } From 71899158fd9e9cfcc365a8aabe51d54a888e6c58 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 20:32:11 +0200 Subject: [PATCH 053/301] Tests: Clean up any old devmapper leftovers before starting tests Upstream-commit: 03320f0d1c5a687ec46cbf6d836cc91576b3b225 Component: engine --- components/engine/devmapper/devmapper.go | 17 ++++++++++++++ components/engine/runtime_test.go | 29 ++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/components/engine/devmapper/devmapper.go b/components/engine/devmapper/devmapper.go index 75742628a2..8458cb3ed9 100644 --- a/components/engine/devmapper/devmapper.go +++ b/components/engine/devmapper/devmapper.go @@ -347,3 +347,20 @@ func UdevWait(cookie uint32) error { func LogInitVerbose(level int) { C.dm_log_init_verbose(C.int(level)) } + +// Useful helper for cleanup +func RemoveDevice(name string) error { + task := TaskCreate(DeviceRemove) + if task == nil { + return fmt.Errorf("Can't create task of type DeviceRemove") + } + err := task.SetName(name) + if err != nil { + return fmt.Errorf("Can't set task name %s", name) + } + err = task.Run() + if err != nil { + return fmt.Errorf("Error running removeDevice") + } + return nil +} diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index 684ca005cd..bcbdf3f384 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -6,6 +6,7 @@ import ( "github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/devmapper" "io" + "io/ioutil" "log" "net" "os" @@ -84,6 +85,32 @@ func layerArchive(tarfile string) (io.Reader, error) { return f, nil } +// Remove any leftover device mapper devices from earlier runs of the unit tests +func cleanupDevMapper() { + infos, _ := ioutil.ReadDir("/dev/mapper") + if infos != nil { + hasPool := false + for _, info := range infos { + name := info.Name() + if strings.HasPrefix(name, "docker-unit-tests-devices-") { + if name == "docker-unit-tests-devices-pool" { + hasPool = true + } else { + if err := devmapper.RemoveDevice(name); err != nil { + panic(fmt.Errorf("Unable to remove existing device %s: %s", name, err)) + } + } + } + // We need to remove the pool last as the other devices block it + if hasPool { + if err := devmapper.RemoveDevice("docker-unit-tests-devices-pool"); err != nil { + panic(fmt.Errorf("Unable to remove existing device docker-unit-tests-devices-pool: %s", name, err)) + } + } + } + } +} + func init() { os.Setenv("TEST", "1") @@ -99,6 +126,8 @@ func init() { NetworkBridgeIface = unitTestNetworkBridge + cleanupDevMapper() + // Always start from a clean set of loopback mounts err := os.RemoveAll(unitTestStoreDevicesBase) if err != nil { From 54672e19858d73c67321c97af82cb59f6417b7c8 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 24 Sep 2013 14:16:11 +0200 Subject: [PATCH 054/301] RootIsShared: Fix root detection Column 4 is the mount position, column 3 will not always be "/" for the root. On one of my system its "/root". Upstream-commit: d263aa6ca916ba9141f341447a2387e7a6316717 Component: engine --- components/engine/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/utils.go b/components/engine/utils.go index 44573927a1..99600d1882 100644 --- a/components/engine/utils.go +++ b/components/engine/utils.go @@ -197,7 +197,7 @@ func RootIsShared() bool { if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil { for _, line := range strings.Split(string(data), "\n") { cols := strings.Split(line, " ") - if len(cols) >= 6 && cols[3] == "/" && cols[4] == "/" { + if len(cols) >= 6 && cols[4] == "/" { return strings.HasPrefix(cols[6], "shared") } } From 4c426bcdc12a0fef83ffabc2436f6ff753c2291a Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 24 Sep 2013 17:20:58 +0000 Subject: [PATCH 055/301] add a -mount-method flag Upstream-commit: c1e25d7273f7f520a0dbac675db4e7e26c8a4a9b Component: engine --- components/engine/docker/docker.go | 7 ++++--- components/engine/runtime.go | 14 +++++++++++--- components/engine/runtime_test.go | 4 ++-- components/engine/server.go | 4 ++-- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/components/engine/docker/docker.go b/components/engine/docker/docker.go index 4dcc174d5e..1f4dcc1d86 100644 --- a/components/engine/docker/docker.go +++ b/components/engine/docker/docker.go @@ -36,6 +36,7 @@ func main() { flGraphPath := flag.String("g", "/var/lib/docker", "Path to graph storage base dir.") flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.") flDns := flag.String("dns", "", "Set custom dns servers") + flMountMethod := flag.String("mount-method", "", "Set the mount method to use, default is auto. [aufs, devicemapper, filesystem]") flHosts := docker.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)} flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use") flag.Parse() @@ -65,7 +66,7 @@ func main() { flag.Usage() return } - if err := daemon(*pidfile, *flGraphPath, flHosts, *flAutoRestart, *flEnableCors, *flDns); err != nil { + if err := daemon(*pidfile, *flGraphPath, flHosts, *flAutoRestart, *flEnableCors, *flDns, *flMountMethod); err != nil { log.Fatal(err) os.Exit(-1) } @@ -116,7 +117,7 @@ func removePidFile(pidfile string) { } } -func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error { +func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart, enableCors bool, flDns, mountMethod string) error { if err := createPidFile(pidfile); err != nil { log.Fatal(err) } @@ -134,7 +135,7 @@ func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart if flDns != "" { dns = []string{flDns} } - server, err := docker.NewServer(flGraphPath, devmapper.NewDeviceSetDM(flGraphPath), autoRestart, enableCors, dns) + server, err := docker.NewServer(flGraphPath, devmapper.NewDeviceSetDM(flGraphPath), autoRestart, enableCors, dns, mountMethod) if err != nil { return err } diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 2a0dbe6a3e..57acdf0fb5 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -518,8 +518,8 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a } // FIXME: harmonize with NewGraph() -func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns []string) (*Runtime, error) { - runtime, err := NewRuntimeFromDirectory(flGraphPath, deviceSet, autoRestart) +func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns []string, mountMethod string) (*Runtime, error) { + runtime, err := NewRuntimeFromDirectory(flGraphPath, deviceSet, autoRestart, mountMethod) if err != nil { return nil, err } @@ -537,7 +537,7 @@ func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns [ return runtime, nil } -func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool) (*Runtime, error) { +func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool, mountMethod string) (*Runtime, error) { runtimeRepo := path.Join(root, "containers") if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) { @@ -577,6 +577,14 @@ func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool) deviceSet: deviceSet, } + if mountMethod == "aufs" { + runtime.mountMethod = MountMethodAUFS + } else if mountMethod == "devicemapper" { + runtime.mountMethod = MountMethodDeviceMapper + } else if mountMethod == "filesystem" { + runtime.mountMethod = MountMethodFilesystem + } + if err := runtime.restore(); err != nil { return nil, err } diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index bcbdf3f384..b4be93fc1e 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -135,7 +135,7 @@ func init() { } // Make it our Store root - if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreDevicesBase), false); err != nil { + if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreDevicesBase), false, ""); err != nil { panic(err) } else { globalRuntime = runtime @@ -504,7 +504,7 @@ func TestRestore(t *testing.T) { // Here are are simulating a docker restart - that is, reloading all containers // from scratch - runtime2, err := NewRuntimeFromDirectory(runtime1.root, runtime1.deviceSet, false) + runtime2, err := NewRuntimeFromDirectory(runtime1.root, runtime1.deviceSet, false, "") if err != nil { t.Fatal(err) } diff --git a/components/engine/server.go b/components/engine/server.go index 7f82d7adab..6b32d4e8fb 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -1294,11 +1294,11 @@ func (srv *Server) ContainerCopy(name string, resource string, out io.Writer) er } -func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors bool, dns ListOpts) (*Server, error) { +func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors bool, dns ListOpts, mountMethod string) (*Server, error) { if runtime.GOARCH != "amd64" { log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) } - runtime, err := NewRuntime(flGraphPath, deviceSet, autoRestart, dns) + runtime, err := NewRuntime(flGraphPath, deviceSet, autoRestart, dns, mountMethod) if err != nil { return nil, err } From 1812696aefad0203e6b13541dc71dbb4dd05b9fa Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 26 Sep 2013 15:08:26 +0000 Subject: [PATCH 056/301] Revert "add a -mount-method flag" This reverts commit e52d756f40c9ccf8b37ca496cb72be057c909ed7. Upstream-commit: 5e1d540209342fa2d6e2ab7117062a897ccf8fe8 Component: engine --- components/engine/docker/docker.go | 7 +++---- components/engine/runtime.go | 14 +++----------- components/engine/runtime_test.go | 4 ++-- components/engine/server.go | 4 ++-- components/engine/utils_test.go | 2 +- 5 files changed, 11 insertions(+), 20 deletions(-) diff --git a/components/engine/docker/docker.go b/components/engine/docker/docker.go index 1f4dcc1d86..4dcc174d5e 100644 --- a/components/engine/docker/docker.go +++ b/components/engine/docker/docker.go @@ -36,7 +36,6 @@ func main() { flGraphPath := flag.String("g", "/var/lib/docker", "Path to graph storage base dir.") flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.") flDns := flag.String("dns", "", "Set custom dns servers") - flMountMethod := flag.String("mount-method", "", "Set the mount method to use, default is auto. [aufs, devicemapper, filesystem]") flHosts := docker.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)} flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use") flag.Parse() @@ -66,7 +65,7 @@ func main() { flag.Usage() return } - if err := daemon(*pidfile, *flGraphPath, flHosts, *flAutoRestart, *flEnableCors, *flDns, *flMountMethod); err != nil { + if err := daemon(*pidfile, *flGraphPath, flHosts, *flAutoRestart, *flEnableCors, *flDns); err != nil { log.Fatal(err) os.Exit(-1) } @@ -117,7 +116,7 @@ func removePidFile(pidfile string) { } } -func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart, enableCors bool, flDns, mountMethod string) error { +func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error { if err := createPidFile(pidfile); err != nil { log.Fatal(err) } @@ -135,7 +134,7 @@ func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart if flDns != "" { dns = []string{flDns} } - server, err := docker.NewServer(flGraphPath, devmapper.NewDeviceSetDM(flGraphPath), autoRestart, enableCors, dns, mountMethod) + server, err := docker.NewServer(flGraphPath, devmapper.NewDeviceSetDM(flGraphPath), autoRestart, enableCors, dns) if err != nil { return err } diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 57acdf0fb5..2a0dbe6a3e 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -518,8 +518,8 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a } // FIXME: harmonize with NewGraph() -func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns []string, mountMethod string) (*Runtime, error) { - runtime, err := NewRuntimeFromDirectory(flGraphPath, deviceSet, autoRestart, mountMethod) +func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns []string) (*Runtime, error) { + runtime, err := NewRuntimeFromDirectory(flGraphPath, deviceSet, autoRestart) if err != nil { return nil, err } @@ -537,7 +537,7 @@ func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns [ return runtime, nil } -func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool, mountMethod string) (*Runtime, error) { +func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool) (*Runtime, error) { runtimeRepo := path.Join(root, "containers") if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) { @@ -577,14 +577,6 @@ func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool, deviceSet: deviceSet, } - if mountMethod == "aufs" { - runtime.mountMethod = MountMethodAUFS - } else if mountMethod == "devicemapper" { - runtime.mountMethod = MountMethodDeviceMapper - } else if mountMethod == "filesystem" { - runtime.mountMethod = MountMethodFilesystem - } - if err := runtime.restore(); err != nil { return nil, err } diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index b4be93fc1e..bcbdf3f384 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -135,7 +135,7 @@ func init() { } // Make it our Store root - if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreDevicesBase), false, ""); err != nil { + if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreDevicesBase), false); err != nil { panic(err) } else { globalRuntime = runtime @@ -504,7 +504,7 @@ func TestRestore(t *testing.T) { // Here are are simulating a docker restart - that is, reloading all containers // from scratch - runtime2, err := NewRuntimeFromDirectory(runtime1.root, runtime1.deviceSet, false, "") + runtime2, err := NewRuntimeFromDirectory(runtime1.root, runtime1.deviceSet, false) if err != nil { t.Fatal(err) } diff --git a/components/engine/server.go b/components/engine/server.go index 6b32d4e8fb..7f82d7adab 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -1294,11 +1294,11 @@ func (srv *Server) ContainerCopy(name string, resource string, out io.Writer) er } -func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors bool, dns ListOpts, mountMethod string) (*Server, error) { +func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors bool, dns ListOpts) (*Server, error) { if runtime.GOARCH != "amd64" { log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) } - runtime, err := NewRuntime(flGraphPath, deviceSet, autoRestart, dns, mountMethod) + runtime, err := NewRuntime(flGraphPath, deviceSet, autoRestart, dns) if err != nil { return nil, err } diff --git a/components/engine/utils_test.go b/components/engine/utils_test.go index f5bdac3271..87f67ff0d7 100644 --- a/components/engine/utils_test.go +++ b/components/engine/utils_test.go @@ -43,7 +43,7 @@ func newTestRuntime() (*Runtime, error) { return nil, err } - runtime, err := NewRuntimeFromDirectory(root, NewDeviceSetWrapper (globalRuntime.deviceSet, filepath.Base(root)), false) + runtime, err := NewRuntimeFromDirectory(root, NewDeviceSetWrapper(globalRuntime.deviceSet, filepath.Base(root)), false) if err != nil { return nil, err } From 715ce06407773b3d1c91509c0852b63e86db273c Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 26 Sep 2013 15:40:13 +0000 Subject: [PATCH 057/301] go fmt and aufs support removed Upstream-commit: ebfa24acb08504d8da3fcba8da897fed357f34b2 Component: engine --- components/engine/api_params.go | 14 +- components/engine/api_test.go | 1 - components/engine/archive.go | 8 +- components/engine/changes.go | 116 +++--------- components/engine/container.go | 4 +- components/engine/deviceset.go | 5 +- components/engine/image.go | 290 ++++++++---------------------- components/engine/mount.go | 29 --- components/engine/runtime.go | 34 +--- components/engine/runtime_test.go | 17 +- components/engine/utils_test.go | 2 +- 11 files changed, 124 insertions(+), 396 deletions(-) diff --git a/components/engine/api_params.go b/components/engine/api_params.go index 6403bc6a26..5f1a338057 100644 --- a/components/engine/api_params.go +++ b/components/engine/api_params.go @@ -56,13 +56,13 @@ type APIContainers struct { func (self *APIContainers) ToLegacy() APIContainersOld { return APIContainersOld{ - ID: self.ID, - Image: self.Image, - Command: self.Command, - Created: self.Created, - Status: self.Status, - Ports: displayablePorts(self.Ports), - SizeRw: self.SizeRw, + ID: self.ID, + Image: self.Image, + Command: self.Command, + Created: self.Created, + Status: self.Status, + Ports: displayablePorts(self.Ports), + SizeRw: self.SizeRw, SizeRootFs: self.SizeRootFs, } } diff --git a/components/engine/api_test.go b/components/engine/api_test.go index edff6788e1..b24aaa36f8 100644 --- a/components/engine/api_test.go +++ b/components/engine/api_test.go @@ -566,7 +566,6 @@ func TestPostCommit(t *testing.T) { srv := &Server{runtime: runtime} - // Create a container and remove a file container, err := runtime.Create( &Config{ diff --git a/components/engine/archive.go b/components/engine/archive.go index f3f7b8c59e..75b6e7e1f1 100644 --- a/components/engine/archive.go +++ b/components/engine/archive.go @@ -84,13 +84,13 @@ func Tar(path string, compression Compression) (io.Reader, error) { } func escapeName(name string) string { - escaped := make([]byte,0) + escaped := make([]byte, 0) for i, c := range []byte(name) { if i == 0 && c == '/' { continue } // all printable chars except "-" which is 0x2d - if (0x20 <= c && c <= 0x7E) && c != 0x2d { + if (0x20 <= c && c <= 0x7E) && c != 0x2d { escaped = append(escaped, c) } else { escaped = append(escaped, fmt.Sprintf("\\%03o", c)...) @@ -102,7 +102,7 @@ func escapeName(name string) string { // Tar creates an archive from the directory at `path`, only including files whose relative // paths are included in `filter`. If `filter` is nil, then all files are included. func TarFilter(path string, compression Compression, filter []string, recursive bool, createFiles []string) (io.Reader, error) { - args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path, "-T", "-",} + args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path, "-T", "-"} if filter == nil { filter = []string{"."} } @@ -142,7 +142,7 @@ func TarFilter(path string, compression Compression, filter []string, recursive } } - return CmdStream(exec.Command(args[0], args[1:]...), &files, func () { + return CmdStream(exec.Command(args[0], args[1:]...), &files, func() { if tmpDir != "" { _ = os.RemoveAll(tmpDir) } diff --git a/components/engine/changes.go b/components/engine/changes.go index 49802d3170..77bef6fb22 100644 --- a/components/engine/changes.go +++ b/components/engine/changes.go @@ -34,82 +34,10 @@ func (change *Change) String() string { return fmt.Sprintf("%s %s", kind, change.Path) } -func ChangesAUFS(layers []string, rw string) ([]Change, error) { - var changes []Change - err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error { - if err != nil { - return err - } - - // Rebase path - path, err = filepath.Rel(rw, path) - if err != nil { - return err - } - path = filepath.Join("/", path) - - // Skip root - if path == "/" { - return nil - } - - // Skip AUFS metadata - if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched { - return err - } - - change := Change{ - Path: path, - } - - // Find out what kind of modification happened - file := filepath.Base(path) - // If there is a whiteout, then the file was removed - if strings.HasPrefix(file, ".wh.") { - originalFile := file[len(".wh."):] - change.Path = filepath.Join(filepath.Dir(path), originalFile) - change.Kind = ChangeDelete - } else { - // Otherwise, the file was added - change.Kind = ChangeAdd - - // ...Unless it already existed in a top layer, in which case, it's a modification - for _, layer := range layers { - stat, err := os.Stat(filepath.Join(layer, path)) - if err != nil && !os.IsNotExist(err) { - return err - } - if err == nil { - // The file existed in the top layer, so that's a modification - - // However, if it's a directory, maybe it wasn't actually modified. - // If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar - if stat.IsDir() && f.IsDir() { - if f.Size() == stat.Size() && f.Mode() == stat.Mode() && f.ModTime() == stat.ModTime() { - // Both directories are the same, don't record the change - return nil - } - } - change.Kind = ChangeModify - break - } - } - } - - // Record change - changes = append(changes, change) - return nil - }) - if err != nil && !os.IsNotExist(err) { - return nil, err - } - return changes, nil -} - type FileInfo struct { - parent *FileInfo - name string - stat syscall.Stat_t + parent *FileInfo + name string + stat syscall.Stat_t children map[string]*FileInfo } @@ -132,20 +60,20 @@ func (root *FileInfo) LookUp(path string) *FileInfo { return parent } -func (info *FileInfo)path() string { +func (info *FileInfo) path() string { if info.parent == nil { return "/" } return filepath.Join(info.parent.path(), info.name) } -func (info *FileInfo)unlink() { +func (info *FileInfo) unlink() { if info.parent != nil { delete(info.parent.children, info.name) } } -func (info *FileInfo)Remove(path string) bool { +func (info *FileInfo) Remove(path string) bool { child := info.LookUp(path) if child != nil { child.unlink() @@ -154,12 +82,11 @@ func (info *FileInfo)Remove(path string) bool { return false } -func (info *FileInfo)isDir() bool { +func (info *FileInfo) isDir() bool { return info.parent == nil || info.stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR } - -func (info *FileInfo)addChanges(oldInfo *FileInfo, changes *[]Change) { +func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) { if oldInfo == nil { // add change := Change{ @@ -198,7 +125,7 @@ func (info *FileInfo)addChanges(oldInfo *FileInfo, changes *[]Change) { oldStat.Gid != newStat.Gid || oldStat.Rdev != newStat.Rdev || // Don't look at size for dirs, its not a good measure of change - (oldStat.Size != newStat.Size && oldStat.Mode &syscall.S_IFDIR != syscall.S_IFDIR) || + (oldStat.Size != newStat.Size && oldStat.Mode&syscall.S_IFDIR != syscall.S_IFDIR) || oldMtime.Sec != newMtime.Sec || oldMtime.Usec != newMtime.Usec { change := Change{ @@ -223,10 +150,9 @@ func (info *FileInfo)addChanges(oldInfo *FileInfo, changes *[]Change) { *changes = append(*changes, change) } - } -func (info *FileInfo)Changes(oldInfo *FileInfo) []Change { +func (info *FileInfo) Changes(oldInfo *FileInfo) []Change { var changes []Change info.addChanges(oldInfo, &changes) @@ -234,10 +160,9 @@ func (info *FileInfo)Changes(oldInfo *FileInfo) []Change { return changes } - func newRootFileInfo() *FileInfo { - root := &FileInfo { - name: "/", + root := &FileInfo{ + name: "/", children: make(map[string]*FileInfo), } return root @@ -299,11 +224,11 @@ func applyLayer(root *FileInfo, layer string) error { return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath) } - info := &FileInfo { - name: filepath.Base(relPath), + info := &FileInfo{ + name: filepath.Base(relPath), children: make(map[string]*FileInfo), - parent: parent, - stat: layerStat, + parent: parent, + stat: layerStat, } parent.children[info.name] = info @@ -314,7 +239,6 @@ func applyLayer(root *FileInfo, layer string) error { return err } - func collectFileInfo(sourceDir string) (*FileInfo, error) { root := newRootFileInfo() @@ -339,10 +263,10 @@ func collectFileInfo(sourceDir string) (*FileInfo, error) { return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath) } - info := &FileInfo { - name: filepath.Base(relPath), + info := &FileInfo{ + name: filepath.Base(relPath), children: make(map[string]*FileInfo), - parent: parent, + parent: parent, } if err := syscall.Lstat(path, &info.stat); err != nil { @@ -365,7 +289,7 @@ func ChangesLayers(newDir string, layers []string) ([]Change, error) { return nil, err } oldRoot := newRootFileInfo() - for i := len(layers)-1; i >= 0; i-- { + for i := len(layers) - 1; i >= 0; i-- { layer := layers[i] if err = applyLayer(oldRoot, layer); err != nil { return nil, err diff --git a/components/engine/container.go b/components/engine/container.go index f5f7d0ef80..6c24175fdc 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -803,10 +803,10 @@ func (container *Container) Start(hostConfig *HostConfig) error { // without exec in go we have to do this horrible shell hack... shellString := "mount --make-rprivate /; exec " + - utils.ShellQuoteArguments(params) + utils.ShellQuoteArguments(params) params = []string{ - "unshare", "-m", "--", "/bin/sh", "-c", shellString, + "unshare", "-m", "--", "/bin/sh", "-c", shellString, } } diff --git a/components/engine/deviceset.go b/components/engine/deviceset.go index cf422acb3a..8e619ca248 100644 --- a/components/engine/deviceset.go +++ b/components/engine/deviceset.go @@ -15,7 +15,7 @@ type DeviceSet interface { type DeviceSetWrapper struct { wrapped DeviceSet - prefix string + prefix string } func (wrapper *DeviceSetWrapper) wrap(hash string) string { @@ -25,7 +25,6 @@ func (wrapper *DeviceSetWrapper) wrap(hash string) string { return hash } - func (wrapper *DeviceSetWrapper) AddDevice(hash, baseHash string) error { return wrapper.wrapped.AddDevice(wrapper.wrap(hash), wrapper.wrap(baseHash)) } @@ -69,7 +68,7 @@ func (wrapper *DeviceSetWrapper) HasActivatedDevice(hash string) bool { func NewDeviceSetWrapper(wrapped DeviceSet, prefix string) DeviceSet { wrapper := &DeviceSetWrapper{ wrapped: wrapped, - prefix: prefix, + prefix: prefix, } return wrapper } diff --git a/components/engine/image.go b/components/engine/image.go index ff8b836c39..2187605d51 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -8,9 +8,7 @@ import ( "github.com/dotcloud/docker/utils" "io" "io/ioutil" - "log" "os" - "os/exec" "path" "path/filepath" "strconv" @@ -141,31 +139,6 @@ func mountPath(root string) string { return path.Join(root, "mount") } -func MountAUFS(ro []string, rw string, target string) error { - // FIXME: Now mount the layers - rwBranch := fmt.Sprintf("%v=rw", rw) - roBranches := "" - for _, layer := range ro { - roBranches += fmt.Sprintf("%v=ro+wh:", layer) - } - branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches) - - branches += ",xino=/dev/shm/aufs.xino" - - //if error, try to load aufs kernel module - if err := mount("none", target, "aufs", 0, branches); err != nil { - log.Printf("Kernel does not support AUFS, trying to load the AUFS module with modprobe...") - if err := exec.Command("modprobe", "aufs").Run(); err != nil { - return fmt.Errorf("Unable to load the AUFS module") - } - log.Printf("...module loaded.") - if err := mount("none", target, "aufs", 0, branches); err != nil { - return fmt.Errorf("Unable to mount using aufs") - } - } - return nil -} - // TarLayer returns a tar archive of the image's filesystem layer. func (image *Image) TarLayer(compression Compression) (Archive, error) { layerPath, err := image.layer() @@ -315,7 +288,7 @@ func (image *Image) applyLayer(layer, target string) error { syscall.NsecToTimeval(srcStat.Mtim.Nano()), } - u := TimeUpdate { + u := TimeUpdate{ path: targetPath, time: ts, } @@ -335,7 +308,7 @@ func (image *Image) applyLayer(layer, target string) error { update := updateTimes[i] O_PATH := 010000000 // Not in syscall yet - fd, err := syscall.Open(update.path, syscall.O_RDWR | O_PATH | syscall.O_NOFOLLOW, 0600) + fd, err := syscall.Open(update.path, syscall.O_RDWR|O_PATH|syscall.O_NOFOLLOW, 0600) if err == syscall.EISDIR || err == syscall.ELOOP { // O_PATH not supported, use Utimes except on symlinks where Utimes doesn't work if err != syscall.ELOOP { @@ -411,7 +384,6 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { return err } - err = ioutil.WriteFile(path.Join(mountDir, ".docker-id"), []byte(image.ID), 0600) if err != nil { _ = devices.UnmountDevice(image.ID, mountDir) @@ -461,25 +433,7 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { } func (image *Image) Mounted(runtime *Runtime, root, rw string) (bool, error) { - method := runtime.GetMountMethod() - if method == MountMethodFilesystem { - if _, err := os.Stat(rw); err != nil { - if os.IsNotExist(err) { - err = nil - } - return false, err - } - mountedPath := path.Join(rw, ".fs-mounted") - if _, err := os.Stat(mountedPath); err != nil { - if os.IsNotExist(err) { - err = nil - } - return false, err - } - return true, nil - } else { - return Mounted(root) - } + return Mounted(root) } func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { @@ -492,195 +446,107 @@ func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { return err } - switch runtime.GetMountMethod() { - case MountMethodNone: - return fmt.Errorf("No supported Mount implementation") + devices, err := runtime.GetDeviceSet() + if err != nil { + return err + } + err = image.ensureImageDevice(devices) + if err != nil { + return err + } - case MountMethodAUFS: - if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { - return err - } - layers, err := image.layers() + createdDevice := false + if !devices.HasDevice(id) { + utils.Debugf("Creating device %s for container based on image %s", id, image.ID) + err = devices.AddDevice(id, image.ID) if err != nil { return err } - if err := MountAUFS(layers, rw, root); err != nil { - return err - } + createdDevice = true + } - case MountMethodDeviceMapper: - devices, err := runtime.GetDeviceSet() + utils.Debugf("Mounting container %s at %s for container", id, root) + err = devices.MountDevice(id, root) + if err != nil { + return err + } + + if createdDevice { + err = ioutil.WriteFile(path.Join(root, ".docker-id"), []byte(id), 0600) if err != nil { + _ = devices.RemoveDevice(image.ID) return err } - err = image.ensureImageDevice(devices) - if err != nil { - return err - } - - createdDevice := false - if !devices.HasDevice(id) { - utils.Debugf("Creating device %s for container based on image %s", id, image.ID) - err = devices.AddDevice(id, image.ID) - if err != nil { - return err - } - createdDevice = true - } - - utils.Debugf("Mounting container %s at %s for container", id, root) - err = devices.MountDevice(id, root) - if err != nil { - return err - } - - if createdDevice { - err = ioutil.WriteFile(path.Join(root, ".docker-id"), []byte(id), 0600) - if err != nil { - _ = devices.RemoveDevice(image.ID) - return err - } - } - - case MountMethodFilesystem: - if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { - return err - } - - layers, err := image.layers() - if err != nil { - return err - } - - for i := len(layers)-1; i >= 0; i-- { - layer := layers[i] - if err = image.applyLayer(layer, root); err != nil { - return err - } - } - - mountedPath := path.Join(rw, ".fs-mounted") - fo, err := os.Create(mountedPath) - if err != nil { - return err - } - fo.Close() } return nil } func (image *Image) Unmount(runtime *Runtime, root string, id string) error { - switch runtime.GetMountMethod() { - case MountMethodNone: - return fmt.Errorf("No supported Unmount implementation") - - case MountMethodAUFS: - return Unmount(root) - - case MountMethodDeviceMapper: - // Try to deactivate the device as generally there is no use for it anymore - devices, err := runtime.GetDeviceSet() - if err != nil { - return err; - } - - err = devices.UnmountDevice(id, root) - if err != nil { - return err - } - - return devices.DeactivateDevice(id) - - case MountMethodFilesystem: - return nil + // Try to deactivate the device as generally there is no use for it anymore + devices, err := runtime.GetDeviceSet() + if err != nil { + return err } - return nil + err = devices.UnmountDevice(id, root) + if err != nil { + return err + } + + return devices.DeactivateDevice(id) } func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, error) { - switch runtime.GetMountMethod() { - case MountMethodAUFS: - layers, err := image.layers() - if err != nil { - return nil, err - } - return ChangesAUFS(layers, rw) - - case MountMethodDeviceMapper: - devices, err := runtime.GetDeviceSet() - if err != nil { - return nil, err - } - - if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { - return nil, err - } - - wasActivated := devices.HasActivatedDevice(image.ID) - - // We re-use rw for the temporary mount of the base image as its - // not used by device-mapper otherwise - err = devices.MountDevice(image.ID, rw) - if err != nil { - return nil, err - } - - changes, err := ChangesDirs(root, rw) - _ = devices.UnmountDevice(image.ID, rw) - if !wasActivated { - _ = devices.DeactivateDevice(image.ID) - } - if err != nil { - return nil, err - } - return changes, nil - - case MountMethodFilesystem: - layers, err := image.layers() - if err != nil { - return nil, err - } - changes, err := ChangesLayers(root, layers) - if err != nil { - return nil, err - } - return changes, nil + devices, err := runtime.GetDeviceSet() + if err != nil { + return nil, err } - return nil, fmt.Errorf("No supported Changes implementation") + if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { + return nil, err + } + + wasActivated := devices.HasActivatedDevice(image.ID) + + // We re-use rw for the temporary mount of the base image as its + // not used by device-mapper otherwise + err = devices.MountDevice(image.ID, rw) + if err != nil { + return nil, err + } + + changes, err := ChangesDirs(root, rw) + _ = devices.UnmountDevice(image.ID, rw) + if !wasActivated { + _ = devices.DeactivateDevice(image.ID) + } + if err != nil { + return nil, err + } + return changes, nil } func (image *Image) ExportChanges(runtime *Runtime, root, rw, id string) (Archive, error) { - switch runtime.GetMountMethod() { - case MountMethodAUFS: - return Tar(rw, Uncompressed) - - case MountMethodFilesystem, MountMethodDeviceMapper: - changes, err := image.Changes(runtime, root, rw, id) - if err != nil { - return nil, err - } - - files := make([]string, 0) - deletions := make([]string, 0) - for _, change := range changes { - if change.Kind == ChangeModify || change.Kind == ChangeAdd { - files = append(files, change.Path) - } - if change.Kind == ChangeDelete { - base := filepath.Base(change.Path) - dir := filepath.Dir(change.Path) - deletions = append(deletions, filepath.Join(dir, ".wh."+base)) - } - } - - return TarFilter(root, Uncompressed, files, false, deletions) + changes, err := image.Changes(runtime, root, rw, id) + if err != nil { + return nil, err } - return nil, fmt.Errorf("No supported Changes implementation") -} + files := make([]string, 0) + deletions := make([]string, 0) + for _, change := range changes { + if change.Kind == ChangeModify || change.Kind == ChangeAdd { + files = append(files, change.Path) + } + if change.Kind == ChangeDelete { + base := filepath.Base(change.Path) + dir := filepath.Dir(change.Path) + deletions = append(deletions, filepath.Join(dir, ".wh."+base)) + } + } + return TarFilter(root, Uncompressed, files, false, deletions) +} func (image *Image) ShortID() string { return utils.TruncateID(image.ID) diff --git a/components/engine/mount.go b/components/engine/mount.go index 541c29c13a..3e2a21df50 100644 --- a/components/engine/mount.go +++ b/components/engine/mount.go @@ -1,40 +1,11 @@ package docker import ( - "fmt" - "github.com/dotcloud/docker/utils" "os" - "os/exec" "path/filepath" "syscall" - "time" ) -func Unmount(target string) error { - if err := exec.Command("auplink", target, "flush").Run(); err != nil { - utils.Debugf("[warning]: couldn't run auplink before unmount: %s", err) - } - if err := syscall.Unmount(target, 0); err != nil { - return err - } - // Even though we just unmounted the filesystem, AUFS will prevent deleting the mntpoint - // for some time. We'll just keep retrying until it succeeds. - for retries := 0; retries < 1000; retries++ { - err := os.Remove(target) - if err == nil { - // rm mntpoint succeeded - return nil - } - if os.IsNotExist(err) { - // mntpoint doesn't exist anymore. Success. - return nil - } - // fmt.Printf("(%v) Remove %v returned: %v\n", retries, target, err) - time.Sleep(10 * time.Millisecond) - } - return fmt.Errorf("Umount: Failed to umount %v", target) -} - func Mounted(mountpoint string) (bool, error) { mntpoint, err := os.Stat(mountpoint) if err != nil { diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 2a0dbe6a3e..a92b0e5f70 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -17,14 +17,6 @@ import ( ) var defaultDns = []string{"8.8.8.8", "8.8.4.4"} -type MountMethod int - -const ( - MountMethodNone MountMethod = iota - MountMethodAUFS - MountMethodDeviceMapper - MountMethodFilesystem -) type Capabilities struct { MemoryLimit bool @@ -47,7 +39,6 @@ type Runtime struct { srv *Server Dns []string deviceSet DeviceSet - mountMethod MountMethod } var sysInitPath string @@ -109,27 +100,6 @@ func hasFilesystemSupport(fstype string) bool { return false } -func (runtime *Runtime) GetMountMethod() MountMethod { - if runtime.mountMethod == MountMethodNone { - // Try to automatically pick a method - if hasFilesystemSupport("aufs") { - utils.Debugf("Using AUFS backend.") - runtime.mountMethod = MountMethodAUFS - } else { - _ = exec.Command("modprobe", "aufs").Run() - if hasFilesystemSupport("aufs") { - utils.Debugf("Using AUFS backend.") - runtime.mountMethod = MountMethodAUFS - } else { - utils.Debugf("Using device-mapper backend.") - runtime.mountMethod = MountMethodDeviceMapper - } - } - } - - return runtime.mountMethod -} - func (runtime *Runtime) GetDeviceSet() (DeviceSet, error) { if runtime.deviceSet == nil { return nil, fmt.Errorf("No device set available") @@ -288,7 +258,7 @@ func (runtime *Runtime) Destroy(container *Container) error { if err := os.RemoveAll(container.root); err != nil { return fmt.Errorf("Unable to remove filesystem for %v: %v", container.ID, err) } - if runtime.GetMountMethod() == MountMethodDeviceMapper && runtime.deviceSet.HasDevice(container.ID) { + if runtime.deviceSet.HasDevice(container.ID) { if err := runtime.deviceSet.RemoveDevice(container.ID); err != nil { return fmt.Errorf("Unable to remove device for %v: %v", container.ID, err) } @@ -301,7 +271,7 @@ func (runtime *Runtime) DeleteImage(id string) error { if err != nil { return err } - if runtime.GetMountMethod() == MountMethodDeviceMapper && runtime.deviceSet.HasDevice(id) { + if runtime.deviceSet.HasDevice(id) { if err := runtime.deviceSet.RemoveDevice(id); err != nil { return fmt.Errorf("Unable to remove device for %v: %v", id, err) } diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index bcbdf3f384..0a9b95a411 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -3,8 +3,8 @@ package docker import ( "bytes" "fmt" - "github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/devmapper" + "github.com/dotcloud/docker/utils" "io" "io/ioutil" "log" @@ -20,13 +20,13 @@ import ( ) const ( - unitTestImageName = "docker-test-image" - unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 - unitTestNetworkBridge = "testdockbr0" - unitTestStoreBase = "/var/lib/docker/unit-tests" - unitTestStoreDevicesBase = "/var/lib/docker/unit-tests-devices" - testDaemonAddr = "127.0.0.1:4270" - testDaemonProto = "tcp" + unitTestImageName = "docker-test-image" + unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 + unitTestNetworkBridge = "testdockbr0" + unitTestStoreBase = "/var/lib/docker/unit-tests" + unitTestStoreDevicesBase = "/var/lib/docker/unit-tests-devices" + testDaemonAddr = "127.0.0.1:4270" + testDaemonProto = "tcp" ) var ( @@ -75,7 +75,6 @@ func cleanupLast(runtime *Runtime) error { return nil } - func layerArchive(tarfile string) (io.Reader, error) { // FIXME: need to close f somewhere f, err := os.Open(tarfile) diff --git a/components/engine/utils_test.go b/components/engine/utils_test.go index 87f67ff0d7..f458b1da91 100644 --- a/components/engine/utils_test.go +++ b/components/engine/utils_test.go @@ -2,11 +2,11 @@ package docker import ( "github.com/dotcloud/docker/utils" - "path/filepath" "io" "io/ioutil" "os" "path" + "path/filepath" "strings" "testing" ) From 05ac257fe90faea3619ca4e3368d0b8a7bc163e5 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 26 Sep 2013 21:12:12 +0200 Subject: [PATCH 058/301] Image: Fix time setting for old kernels This is a better fix for futimes() on kernels not supporting O_PATH. The previous fix broke when copying a device, as it tried to open it and got and error. Upstream-commit: 75e958bf48a83de5f3f80859aee96f3356d16d4b Component: engine --- components/engine/image.go | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/components/engine/image.go b/components/engine/image.go index 2187605d51..06036e1e62 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -151,6 +151,7 @@ func (image *Image) TarLayer(compression Compression) (Archive, error) { type TimeUpdate struct { path string time []syscall.Timeval + mode uint32 } func (image *Image) applyLayer(layer, target string) error { @@ -291,6 +292,7 @@ func (image *Image) applyLayer(layer, target string) error { u := TimeUpdate{ path: targetPath, time: ts, + mode: srcStat.Mode, } // Delay time updates until all other changes done, or it is @@ -308,21 +310,24 @@ func (image *Image) applyLayer(layer, target string) error { update := updateTimes[i] O_PATH := 010000000 // Not in syscall yet - fd, err := syscall.Open(update.path, syscall.O_RDWR|O_PATH|syscall.O_NOFOLLOW, 0600) - if err == syscall.EISDIR || err == syscall.ELOOP { - // O_PATH not supported, use Utimes except on symlinks where Utimes doesn't work - if err != syscall.ELOOP { - err = syscall.Utimes(update.path, update.time) - if err != nil { - return err - } + var err error = nil + if update.mode&syscall.S_IFLNK == syscall.S_IFLNK { + // Update time on the symlink via O_PATH + futimes(), if supported by the kernel + + fd, err := syscall.Open(update.path, syscall.O_RDWR|O_PATH|syscall.O_NOFOLLOW, 0600) + if err == syscall.EISDIR || err == syscall.ELOOP { + // O_PATH not supported by kernel, nothing to do, ignore + } else if err != nil { + return err + } else { + syscall.Futimes(fd, update.time) + _ = syscall.Close(fd) } } else { + err = syscall.Utimes(update.path, update.time) if err != nil { return err } - syscall.Futimes(fd, update.time) - _ = syscall.Close(fd) } } From cdec248ebe4edfa76b81a4b594ac0530bf392de6 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 11:14:31 +0200 Subject: [PATCH 059/301] Add libdevmapper wrapper Upstream-commit: 459bac712709db4d188022539f4d59524b4b3670 Component: engine --- components/engine/devmapper/devmapper.go | 349 +++++++++++++++++++++++ 1 file changed, 349 insertions(+) create mode 100644 components/engine/devmapper/devmapper.go diff --git a/components/engine/devmapper/devmapper.go b/components/engine/devmapper/devmapper.go new file mode 100644 index 0000000000..f007091827 --- /dev/null +++ b/components/engine/devmapper/devmapper.go @@ -0,0 +1,349 @@ +package devmapper + +/* +#cgo LDFLAGS: -L. -ldevmapper +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static char * +attach_loop_device(const char *filename, int *loop_fd_out) +{ + struct loop_info64 loopinfo = { 0 }; + struct stat st; + char buf[64]; + int i, loop_fd, fd, start_index; + char *loopname; + + *loop_fd_out = -1; + + start_index = 0; + fd = open("/dev/loop-control", O_RDONLY); + if (fd == 0) { + start_index = ioctl(fd, LOOP_CTL_GET_FREE); + close(fd); + + if (start_index < 0) + start_index = 0; + } + + fd = open(filename, O_RDWR); + if (fd < 0) { + return NULL; + } + + loop_fd = -1; + for (i = start_index ; loop_fd < 0 ; i++ ) { + if (sprintf(buf, "/dev/loop%d", i) < 0) { + close(fd); + return NULL; + } + + if (stat(buf, &st) || !S_ISBLK(st.st_mode)) { + close(fd); + return NULL; + } + + loop_fd = open(buf, O_RDWR); + if (loop_fd < 0 && errno == ENOENT) { + close(fd); + fprintf (stderr, "no available loopback device!"); + return NULL; + } else if (loop_fd < 0) + continue; + + if (ioctl (loop_fd, LOOP_SET_FD, (void *)(size_t)fd) < 0) { + close(loop_fd); + loop_fd = -1; + if (errno != EBUSY) { + close (fd); + fprintf (stderr, "cannot set up loopback device %s", buf); + return NULL; + } + continue; + } + + close (fd); + + strncpy((char*)loopinfo.lo_file_name, buf, LO_NAME_SIZE); + loopinfo.lo_offset = 0; + loopinfo.lo_flags = LO_FLAGS_AUTOCLEAR; + + if (ioctl(loop_fd, LOOP_SET_STATUS64, &loopinfo) < 0) { + ioctl(loop_fd, LOOP_CLR_FD, 0); + close(loop_fd); + fprintf (stderr, "cannot set up loopback device info"); + return NULL; + } + + loopname = strdup(buf); + if (loopname == NULL) { + close(loop_fd); + return NULL; + } + + *loop_fd_out = loop_fd; + return loopname; + } + return NULL; +} + +static int64_t +get_block_size(int fd) +{ + uint64_t size; + if (ioctl(fd, BLKGETSIZE64, &size) == -1) + return -1; + return (int64_t)size; +} + + +*/ +import "C" +import "unsafe" +import "fmt" +import "runtime" +import "os" + +func SetDevDir(dir string) error { + c_dir := C.CString(dir) + defer C.free(unsafe.Pointer(c_dir)) + res := C.dm_set_dev_dir(c_dir) + if res != 1 { + return fmt.Errorf("dm_set_dev_dir failed") + } + return nil +} + +func GetLibraryVersion() (string, error) { + buffer := (*C.char)(C.malloc(128)) + defer C.free(unsafe.Pointer(buffer)) + res := C.dm_get_library_version(buffer, 128) + if res != 1 { + return "", fmt.Errorf("dm_get_library_version failed") + } else { + return C.GoString(buffer), nil + } +} + +type TaskType int + +const ( + DeviceCreate TaskType = iota + DeviceReload + DeviceRemove + DeviceRemoveAll + DeviceSuspend + DeviceResume + DeviceInfo + DeviceDeps + DeviceRename + DeviceVersion + DeviceStatus + DeviceTable + DeviceWaitevent + DeviceList + DeviceClear + DeviceMknodes + DeviceListVersions + DeviceTargetMsg + DeviceSetGeometry +) + +type Task struct { + unmanaged *C.struct_dm_task +} + +type Info struct { + Exists int + Suspended int + LiveTable int + InactiveTable int + OpenCount int32 + EventNr uint32 + Major uint32 + Minor uint32 + ReadOnly int + TargetCount int32 +} + +func (t *Task) destroy() { + if t != nil { + C.dm_task_destroy(t.unmanaged) + runtime.SetFinalizer(t, nil) + } +} + +func TaskCreate(tasktype TaskType) *Task { + c_task := C.dm_task_create(C.int(int(tasktype))) + if c_task == nil { + return nil + } + task := &Task{c_task} + runtime.SetFinalizer(task, (*Task).destroy) + return task +} + +func (t *Task) Run() error { + res := C.dm_task_run(t.unmanaged) + if res != 1 { + return fmt.Errorf("dm_task_run failed") + } + return nil +} + +func (t *Task) SetName(name string) error { + c_name := C.CString(name) + defer C.free(unsafe.Pointer(c_name)) + + res := C.dm_task_set_name(t.unmanaged, c_name) + if res != 1 { + return fmt.Errorf("dm_task_set_name failed") + } + return nil +} + +func (t *Task) SetMessage(message string) error { + c_message := C.CString(message) + defer C.free(unsafe.Pointer(c_message)) + + res := C.dm_task_set_message(t.unmanaged, c_message) + if res != 1 { + return fmt.Errorf("dm_task_set_message failed") + } + return nil +} + +func (t *Task) SetSector(sector uint64) error { + res := C.dm_task_set_sector(t.unmanaged, C.uint64_t(sector)) + if res != 1 { + return fmt.Errorf("dm_task_set_add_node failed") + } + return nil +} + +func (t *Task) SetCookie(cookie *uint32, flags uint16) error { + var c_cookie C.uint32_t + c_cookie = C.uint32_t(*cookie) + res := C.dm_task_set_cookie(t.unmanaged, &c_cookie, C.uint16_t(flags)) + if res != 1 { + return fmt.Errorf("dm_task_set_add_node failed") + } + *cookie = uint32(c_cookie) + return nil +} + +func (t *Task) SetRo() error { + res := C.dm_task_set_ro(t.unmanaged) + if res != 1 { + return fmt.Errorf("dm_task_set_ro failed") + } + return nil +} + +func (t *Task) AddTarget(start uint64, size uint64, ttype string, params string) error { + c_ttype := C.CString(ttype) + defer C.free(unsafe.Pointer(c_ttype)) + + c_params := C.CString(params) + defer C.free(unsafe.Pointer(c_params)) + + res := C.dm_task_add_target(t.unmanaged, C.uint64_t(start), C.uint64_t(size), c_ttype, c_params) + if res != 1 { + return fmt.Errorf("dm_task_add_target failed") + } + return nil +} + +func (t *Task) GetDriverVersion() (string, error) { + buffer := (*C.char)(C.malloc(128)) + defer C.free(unsafe.Pointer(buffer)) + + res := C.dm_task_get_driver_version(t.unmanaged, buffer, 128) + if res != 1 { + return "", fmt.Errorf("dm_task_get_driver_version") + } else { + return C.GoString(buffer), nil + } +} + +func (t *Task) GetInfo() (*Info, error) { + c_info := C.struct_dm_info{} + res := C.dm_task_get_info(t.unmanaged, &c_info) + if res != 1 { + return nil, fmt.Errorf("dm_task_get_driver_version") + } else { + info := &Info{} + info.Exists = int(c_info.exists) + info.Suspended = int(c_info.suspended) + info.LiveTable = int(c_info.live_table) + info.InactiveTable = int(c_info.inactive_table) + info.OpenCount = int32(c_info.open_count) + info.EventNr = uint32(c_info.event_nr) + info.Major = uint32(c_info.major) + info.Minor = uint32(c_info.minor) + info.ReadOnly = int(c_info.read_only) + info.TargetCount = int32(c_info.target_count) + + return info, nil + } +} + +func (t *Task) GetNextTarget(next uintptr) (uintptr, uint64, uint64, string, string) { + nextp := unsafe.Pointer(next) + var c_start C.uint64_t + var c_length C.uint64_t + var c_target_type *C.char + var c_params *C.char + + nextp = C.dm_get_next_target(t.unmanaged, nextp, &c_start, &c_length, &c_target_type, &c_params) + + target_type := C.GoString(c_target_type) + params := C.GoString(c_params) + + return uintptr(nextp), uint64(c_start), uint64(c_length), target_type, params +} + +func AttachLoopDevice(filename string) (*os.File, error) { + c_filename := C.CString(filename) + defer C.free(unsafe.Pointer(c_filename)) + + var fd C.int + res := C.attach_loop_device(c_filename, &fd) + if res == nil { + return nil, fmt.Errorf("error loopback mounting") + } + file := os.NewFile(uintptr(fd), C.GoString(res)) + C.free(unsafe.Pointer(res)) + return file, nil +} + +func GetBlockDeviceSize(file *os.File) (uint64, error) { + fd := file.Fd() + size := C.get_block_size(C.int(fd)) + if size == -1 { + return 0, fmt.Errorf("Can't get block size") + } + return uint64(size), nil + +} + +func UdevWait(cookie uint32) error { + res := C.dm_udev_wait(C.uint32_t(cookie)) + if res != 1 { + return fmt.Errorf("Failed to wait on udev cookie %d", cookie) + } + return nil +} + +func LogInitVerbose(level int) { + C.dm_log_init_verbose(C.int(level)) +} From bf4813911d80002aefe8f50c4e59599c35c3d763 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 11:18:23 +0200 Subject: [PATCH 060/301] devmapper: Add DeviceSet device-mapper helper This is a module that uses the device-mapper create CoW snapshots You instantiate a DeviceSetDM object on a specified root (/var/lib/docker), and it will create a subdirectory there called "loopback". It will contain two sparse files which are loopback mounted into a thin-pool device-mapper device called "docker-pool". We then create a base snapshot in the pool with an empty filesystem which can be used as a base for docker snapshots. It also keeps track of the mapping between docker image ids and the snapshots in the pool. Typical use of is something like (without error checking): devices = NewDeviceSetDM("/var/lib/docker") devices.AddDevice(imageId, "") // "" is the base image id devices.MountDevice(imageId, "/mnt/image") ... extract base image to /mnt/image devices.AddDevice(containerId, imageId) devices.MountDevice(containerId, "/mnt/container") ... start container at /mnt/container Upstream-commit: 374a5e9913112c5bde590e532bc0ba5e4afeda49 Component: engine --- .../engine/devmapper/deviceset_devmapper.go | 895 ++++++++++++++++++ 1 file changed, 895 insertions(+) create mode 100644 components/engine/devmapper/deviceset_devmapper.go diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go new file mode 100644 index 0000000000..d7e122cf28 --- /dev/null +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -0,0 +1,895 @@ +package devmapper + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "os/exec" + "path" + "path/filepath" + "syscall" +) + +const defaultDataLoopbackSize int64 = 100 * 1024 * 1024 * 1024 +const defaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024 +const defaultBaseFsSize uint64 = 10 * 1024 * 1024 * 1024 + +type DevInfo struct { + Hash string `json:"-"` + DeviceId int `json:"device_id"` + Size uint64 `json:"size"` + TransactionId uint64 `json:"transaction_id"` + Initialized bool `json:"initialized"` +} + +type MetaData struct { + Devices map[string]*DevInfo `json:devices` +} + +type DeviceSetDM struct { + initialized bool + root string + MetaData + TransactionId uint64 + NewTransactionId uint64 + nextFreeDevice int +} + +func getDevName(name string) string { + return "/dev/mapper/" + name +} + +func (info *DevInfo) Name() string { + hash := info.Hash + if hash == "" { + hash = "base" + } + return fmt.Sprintf("docker-%s", hash) +} + +func (info *DevInfo) DevName() string { + return getDevName(info.Name()) +} + +func (devices *DeviceSetDM) loopbackDir() string { + return path.Join(devices.root, "loopback") +} + +func (devices *DeviceSetDM) jsonFile() string { + return path.Join(devices.loopbackDir(), "json") +} + +func (devices *DeviceSetDM) getPoolName() string { + return "docker-pool" +} + +func (devices *DeviceSetDM) getPoolDevName() string { + return getDevName(devices.getPoolName()) +} + +func (devices *DeviceSetDM) createTask(t TaskType, name string) (*Task, error) { + task := TaskCreate(t) + if task == nil { + return nil, fmt.Errorf("Can't create task of type %d", int(t)) + } + err := task.SetName(name) + if err != nil { + return nil, fmt.Errorf("Can't set task name %s", name) + } + return task, nil +} + +func (devices *DeviceSetDM) getInfo(name string) (*Info, error) { + task, err := devices.createTask(DeviceInfo, name) + if task == nil { + return nil, err + } + err = task.Run() + if err != nil { + return nil, err + } + info, err := task.GetInfo() + if err != nil { + return nil, err + } + return info, nil +} + +func (devices *DeviceSetDM) getStatus(name string) (uint64, uint64, string, string, error) { + task, err := devices.createTask(DeviceStatus, name) + if task == nil { + return 0, 0, "", "", err + } + err = task.Run() + if err != nil { + return 0, 0, "", "", err + } + + devinfo, err := task.GetInfo() + if err != nil { + return 0, 0, "", "", err + } + if devinfo.Exists == 0 { + return 0, 0, "", "", fmt.Errorf("Non existing device %s", name) + } + + var next uintptr = 0 + next, start, length, target_type, params := task.GetNextTarget(next) + + return start, length, target_type, params, nil +} + +func (devices *DeviceSetDM) setTransactionId(oldId uint64, newId uint64) error { + task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) + if task == nil { + return err + } + + err = task.SetSector(0) + if err != nil { + return fmt.Errorf("Can't set sector") + } + + message := fmt.Sprintf("set_transaction_id %d %d", oldId, newId) + err = task.SetMessage(message) + if err != nil { + return fmt.Errorf("Can't set message") + } + + err = task.Run() + if err != nil { + return fmt.Errorf("Error running setTransactionId") + } + return nil +} + +func (devices *DeviceSetDM) hasImage(name string) bool { + dirname := devices.loopbackDir() + filename := path.Join(dirname, name) + + _, err := os.Stat(filename) + return err == nil +} + +func (devices *DeviceSetDM) ensureImage(name string, size int64) (string, error) { + dirname := devices.loopbackDir() + filename := path.Join(dirname, name) + + if err := os.MkdirAll(dirname, 0700); err != nil && !os.IsExist(err) { + return "", err + } + + _, err := os.Stat(filename) + if err != nil { + if !os.IsNotExist(err) { + return "", err + } + log.Printf("Creating loopback file %s for device-manage use", filename) + file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0600) + if err != nil { + return "", err + } + err = file.Truncate(size) + if err != nil { + return "", err + } + } + return filename, nil +} + +func (devices *DeviceSetDM) createPool(dataFile *os.File, metadataFile *os.File) error { + log.Printf("Activating device-mapper pool %s", devices.getPoolName()) + task, err := devices.createTask(DeviceCreate, devices.getPoolName()) + if task == nil { + return err + } + + size, err := GetBlockDeviceSize(dataFile) + if err != nil { + return fmt.Errorf("Can't get data size") + } + + params := metadataFile.Name() + " " + dataFile.Name() + " 512 8192" + err = task.AddTarget(0, size/512, "thin-pool", params) + if err != nil { + return fmt.Errorf("Can't add target") + } + + var cookie uint32 = 0 + err = task.SetCookie(&cookie, 32) + if err != nil { + return fmt.Errorf("Can't set cookie") + } + + err = task.Run() + if err != nil { + return fmt.Errorf("Error running DeviceCreate") + } + + UdevWait(cookie) + + return nil +} + +func (devices *DeviceSetDM) suspendDevice(info *DevInfo) error { + task, err := devices.createTask(DeviceSuspend, info.Name()) + if task == nil { + return err + } + err = task.Run() + if err != nil { + return fmt.Errorf("Error running DeviceSuspend") + } + return nil +} + +func (devices *DeviceSetDM) resumeDevice(info *DevInfo) error { + task, err := devices.createTask(DeviceResume, info.Name()) + if task == nil { + return err + } + + var cookie uint32 = 0 + err = task.SetCookie(&cookie, 32) + if err != nil { + return fmt.Errorf("Can't set cookie") + } + + err = task.Run() + if err != nil { + return fmt.Errorf("Error running DeviceSuspend") + } + + UdevWait(cookie) + + return nil +} + +func (devices *DeviceSetDM) createDevice(deviceId int) error { + task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) + if task == nil { + return err + } + + err = task.SetSector(0) + if err != nil { + return fmt.Errorf("Can't set sector") + } + + message := fmt.Sprintf("create_thin %d", deviceId) + err = task.SetMessage(message) + if err != nil { + return fmt.Errorf("Can't set message") + } + + err = task.Run() + if err != nil { + return fmt.Errorf("Error running createDevice") + } + return nil +} + +func (devices *DeviceSetDM) createSnapDevice(deviceId int, baseInfo *DevInfo) error { + doSuspend := false + devinfo, _ := devices.getInfo(baseInfo.Name()) + if devinfo != nil && devinfo.Exists != 0 { + doSuspend = true + } + + if doSuspend { + err := devices.suspendDevice(baseInfo) + if err != nil { + return err + } + } + + task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) + if task == nil { + _ = devices.resumeDevice(baseInfo) + return err + } + err = task.SetSector(0) + if err != nil { + _ = devices.resumeDevice(baseInfo) + return fmt.Errorf("Can't set sector") + } + + message := fmt.Sprintf("create_snap %d %d", deviceId, baseInfo.DeviceId) + err = task.SetMessage(message) + if err != nil { + _ = devices.resumeDevice(baseInfo) + return fmt.Errorf("Can't set message") + } + + err = task.Run() + if err != nil { + _ = devices.resumeDevice(baseInfo) + return fmt.Errorf("Error running DeviceCreate") + } + + if doSuspend { + err = devices.resumeDevice(baseInfo) + if err != nil { + return err + } + } + + return nil +} + +func (devices *DeviceSetDM) deleteDevice(deviceId int) error { + task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) + if task == nil { + return err + } + + err = task.SetSector(0) + if err != nil { + return fmt.Errorf("Can't set sector") + } + + message := fmt.Sprintf("delete %d", deviceId) + err = task.SetMessage(message) + if err != nil { + return fmt.Errorf("Can't set message") + } + + err = task.Run() + if err != nil { + return fmt.Errorf("Error running deleteDevice") + } + return nil +} + +func (devices *DeviceSetDM) removeDevice(info *DevInfo) error { + task, err := devices.createTask(DeviceRemove, info.Name()) + if task == nil { + return err + } + err = task.Run() + if err != nil { + return fmt.Errorf("Error running removeDevice") + } + return nil +} + +func (devices *DeviceSetDM) activateDevice(info *DevInfo) error { + task, err := devices.createTask(DeviceCreate, info.Name()) + if task == nil { + return err + } + + params := fmt.Sprintf("%s %d", devices.getPoolDevName(), info.DeviceId) + err = task.AddTarget(0, info.Size/512, "thin", params) + if err != nil { + return fmt.Errorf("Can't add target") + } + + var cookie uint32 = 0 + err = task.SetCookie(&cookie, 32) + if err != nil { + return fmt.Errorf("Can't set cookie") + } + + err = task.Run() + if err != nil { + return fmt.Errorf("Error running DeviceCreate") + } + + UdevWait(cookie) + + return nil +} + +func (devices *DeviceSetDM) allocateDeviceId() int { + // TODO: Add smarter reuse of deleted devices + id := devices.nextFreeDevice + devices.nextFreeDevice = devices.nextFreeDevice + 1 + return id +} + +func (devices *DeviceSetDM) allocateTransactionId() uint64 { + devices.NewTransactionId = devices.NewTransactionId + 1 + return devices.NewTransactionId +} + +func (devices *DeviceSetDM) saveMetadata() error { + jsonData, err := json.Marshal(devices.MetaData) + if err != nil { + return err + } + tmpFile, err := ioutil.TempFile(filepath.Dir(devices.jsonFile()), ".json") + if err != nil { + return err + } + + n, err := tmpFile.Write(jsonData) + if err != nil { + return err + } + if n < len(jsonData) { + err = io.ErrShortWrite + } + err = tmpFile.Sync() + if err != nil { + return err + } + err = tmpFile.Close() + if err != nil { + return err + } + err = os.Rename(tmpFile.Name(), devices.jsonFile()) + if err != nil { + return err + } + + if devices.NewTransactionId != devices.TransactionId { + err = devices.setTransactionId(devices.TransactionId, devices.NewTransactionId) + if err != nil { + return err + } + devices.TransactionId = devices.NewTransactionId + } + + return nil +} + +func (devices *DeviceSetDM) registerDevice(id int, hash string, size uint64) (*DevInfo, error) { + transaction := devices.allocateTransactionId() + + info := &DevInfo{ + Hash: hash, + DeviceId: id, + Size: size, + TransactionId: transaction, + Initialized: false, + } + + devices.Devices[hash] = info + err := devices.saveMetadata() + if err != nil { + // Try to remove unused device + devices.Devices[hash] = nil + return nil, err + } + + return info, nil +} + +func (devices *DeviceSetDM) activateDeviceIfNeeded(hash string) error { + info := devices.Devices[hash] + if info == nil { + return fmt.Errorf("Unknown device %s", hash) + } + + name := info.Name() + devinfo, _ := devices.getInfo(name) + if devinfo != nil && devinfo.Exists != 0 { + return nil + } + + return devices.activateDevice(info) +} + +func (devices *DeviceSetDM) createFilesystem(info *DevInfo) error { + devname := info.DevName() + + err := exec.Command("mkfs.ext4", "-E", + "discard,lazy_itable_init=0,lazy_journal_init=0", devname).Run() + if err != nil { + err = exec.Command("mkfs.ext4", "-E", + "discard,lazy_itable_init=0", devname).Run() + } + if err != nil { + return err + } + return nil +} + +func (devices *DeviceSetDM) loadMetaData() error { + _, _, _, params, err := devices.getStatus(devices.getPoolName()) + if err != nil { + return err + } + var currentTransaction uint64 + _, err = fmt.Sscanf(params, "%d", ¤tTransaction) + if err != nil { + return err + } + + devices.TransactionId = currentTransaction + devices.NewTransactionId = devices.TransactionId + + jsonData, err := ioutil.ReadFile(devices.jsonFile()) + if err != nil && !os.IsNotExist(err) { + return err + } + + metadata := &MetaData{ + Devices: make(map[string]*DevInfo), + } + if jsonData != nil { + if err := json.Unmarshal(jsonData, metadata); err != nil { + return err + } + } + devices.MetaData = *metadata + + for hash, d := range devices.Devices { + d.Hash = hash + + if d.DeviceId >= devices.nextFreeDevice { + devices.nextFreeDevice = d.DeviceId + 1 + } + + // If the transaction id is larger than the actual one we lost the device due to some crash + if d.TransactionId > currentTransaction { + log.Printf("Removing lost device %s with id %d", hash, d.TransactionId) + delete(devices.Devices, hash) + } + } + + return nil +} + +func (devices *DeviceSetDM) createBaseLayer(dir string) error { + for pth, typ := range map[string]string{ + "/dev/pts": "dir", + "/dev/shm": "dir", + "/proc": "dir", + "/sys": "dir", + "/.dockerinit": "file", + "/etc/resolv.conf": "file", + "/etc/hosts": "file", + "/etc/hostname": "file", + // "var/run": "dir", + // "var/lock": "dir", + } { + if _, err := os.Stat(path.Join(dir, pth)); err != nil { + if os.IsNotExist(err) { + switch typ { + case "dir": + if err := os.MkdirAll(path.Join(dir, pth), 0755); err != nil { + return err + } + case "file": + if err := os.MkdirAll(path.Join(dir, path.Dir(pth)), 0755); err != nil { + return err + } + + if f, err := os.OpenFile(path.Join(dir, pth), os.O_CREATE, 0755); err != nil { + return err + } else { + f.Close() + } + } + } else { + return err + } + } + } + return nil +} + +func (devices *DeviceSetDM) setupBaseImage() error { + oldInfo := devices.Devices[""] + if oldInfo != nil && oldInfo.Initialized { + return nil + } + + if oldInfo != nil && !oldInfo.Initialized { + log.Printf("Removing uninitialized base image") + if err := devices.RemoveDevice(""); err != nil { + return err + } + } + + log.Printf("Initializing base device-manager snapshot") + + id := devices.allocateDeviceId() + + // Create initial device + err := devices.createDevice(id) + if err != nil { + return err + } + + info, err := devices.registerDevice(id, "", defaultBaseFsSize) + if err != nil { + _ = devices.deleteDevice(id) + return err + } + + log.Printf("Creating filesystem on base device-manager snapshot") + + err = devices.activateDeviceIfNeeded("") + if err != nil { + return err + } + + err = devices.createFilesystem(info) + if err != nil { + return err + } + + tmpDir := path.Join(devices.loopbackDir(), "basefs") + if err = os.MkdirAll(tmpDir, 0700); err != nil && !os.IsExist(err) { + return err + } + + err = devices.MountDevice("", tmpDir) + if err != nil { + return err + } + + err = devices.createBaseLayer(tmpDir) + if err != nil { + _ = syscall.Unmount(tmpDir, 0) + return err + } + + err = syscall.Unmount(tmpDir, 0) + if err != nil { + return err + } + + _ = os.Remove(tmpDir) + + info.Initialized = true + + err = devices.saveMetadata() + if err != nil { + info.Initialized = false + return err + } + + return nil +} + +func (devices *DeviceSetDM) initDevmapper() error { + info, err := devices.getInfo(devices.getPoolName()) + if info == nil { + return err + } + + if info.Exists != 0 { + /* Pool exists, assume everything is up */ + err = devices.loadMetaData() + if err != nil { + return err + } + err = devices.setupBaseImage() + if err != nil { + return err + } + return nil + } + + createdLoopback := false + if !devices.hasImage("data") || !devices.hasImage("metadata") { + /* If we create the loopback mounts we also need to initialize the base fs */ + createdLoopback = true + } + + data, err := devices.ensureImage("data", defaultDataLoopbackSize) + if err != nil { + return err + } + + metadata, err := devices.ensureImage("metadata", defaultMetaDataLoopbackSize) + if err != nil { + return err + } + + dataFile, err := AttachLoopDevice(data) + if err != nil { + return err + } + defer dataFile.Close() + + metadataFile, err := AttachLoopDevice(metadata) + if err != nil { + return err + } + defer metadataFile.Close() + + err = devices.createPool(dataFile, metadataFile) + if err != nil { + return err + } + + if !createdLoopback { + err = devices.loadMetaData() + if err != nil { + return err + } + } + + err = devices.setupBaseImage() + if err != nil { + return err + } + + return nil +} + +func (devices *DeviceSetDM) AddDevice(hash, baseHash string) error { + if err := devices.ensureInit(); err != nil { + return err + } + + if devices.Devices[hash] != nil { + return fmt.Errorf("hash %s already exists", hash) + } + + baseInfo := devices.Devices[baseHash] + if baseInfo == nil { + return fmt.Errorf("Unknown base hash %s", baseHash) + } + + deviceId := devices.allocateDeviceId() + + err := devices.createSnapDevice(deviceId, baseInfo) + if err != nil { + return err + } + + _, err = devices.registerDevice(deviceId, hash, baseInfo.Size) + if err != nil { + _ = devices.deleteDevice(deviceId) + return err + } + return nil +} + +func (devices *DeviceSetDM) RemoveDevice(hash string) error { + if err := devices.ensureInit(); err != nil { + return err + } + + info := devices.Devices[hash] + if info == nil { + return fmt.Errorf("hash %s doesn't exists", hash) + } + + devinfo, _ := devices.getInfo(info.Name()) + if devinfo != nil && devinfo.Exists != 0 { + err := devices.removeDevice(info) + if err != nil { + return err + } + } + + if info.Initialized { + info.Initialized = false + err := devices.saveMetadata() + if err != nil { + return err + } + } + + err := devices.deleteDevice(info.DeviceId) + if err != nil { + return err + } + + _ = devices.allocateTransactionId() + delete(devices.Devices, info.Hash) + + err = devices.saveMetadata() + if err != nil { + devices.Devices[info.Hash] = info + return err + } + + return nil +} + +func (devices *DeviceSetDM) DeactivateDevice(hash string) error { + if err := devices.ensureInit(); err != nil { + return err + } + + info := devices.Devices[hash] + if info == nil { + return fmt.Errorf("hash %s doesn't exists", hash) + } + + devinfo, err := devices.getInfo(info.Name()) + if err != nil { + return err + } + if devinfo.Exists != 0 { + err := devices.removeDevice(info) + if err != nil { + return err + } + } + + return nil +} + +func (devices *DeviceSetDM) MountDevice(hash, path string) error { + if err := devices.ensureInit(); err != nil { + return err + } + + err := devices.activateDeviceIfNeeded(hash) + if err != nil { + return err + } + + info := devices.Devices[hash] + + err = syscall.Mount(info.DevName(), path, "ext4", syscall.MS_MGC_VAL, "discard") + if err != nil && err == syscall.EINVAL { + err = syscall.Mount(info.DevName(), path, "ext4", syscall.MS_MGC_VAL, "") + } + if err != nil { + return err + } + return nil +} + +func (devices *DeviceSetDM) HasDevice(hash string) bool { + if err := devices.ensureInit(); err != nil { + return false + } + + info := devices.Devices[hash] + return info != nil +} + +func (devices *DeviceSetDM) HasInitializedDevice(hash string) bool { + if err := devices.ensureInit(); err != nil { + return false + } + + info := devices.Devices[hash] + return info != nil && info.Initialized +} + +func (devices *DeviceSetDM) SetInitialized(hash string) error { + if err := devices.ensureInit(); err != nil { + return err + } + + info := devices.Devices[hash] + if info == nil { + return fmt.Errorf("Unknown device %s", hash) + } + + info.Initialized = true + err := devices.saveMetadata() + if err != nil { + info.Initialized = false + return err + } + + return nil +} + +func (devices *DeviceSetDM) ensureInit() error { + if (!devices.initialized) { + devices.initialized = true + err := devices.initDevmapper() + if err != nil { + return err + } + } + return nil +} + +func NewDeviceSetDM(root string) *DeviceSetDM { + SetDevDir("/dev") + devices := &DeviceSetDM{ + initialized: false, + root: root, + } + devices.Devices = make(map[string]*DevInfo) + + return devices +} From 0553a0af336c8744fc4c7b0dd4cb4570f5d3e04e Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 11:25:32 +0200 Subject: [PATCH 061/301] devmapper: Add simple tool to test the DeviceSet commands Upstream-commit: 7fb3bfed03d4d2a88f0e76fd6b8425cc753f4547 Component: engine --- .../docker-device-tool/device_tool.go | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 components/engine/devmapper/docker-device-tool/device_tool.go diff --git a/components/engine/devmapper/docker-device-tool/device_tool.go b/components/engine/devmapper/docker-device-tool/device_tool.go new file mode 100644 index 0000000000..28bdf56074 --- /dev/null +++ b/components/engine/devmapper/docker-device-tool/device_tool.go @@ -0,0 +1,62 @@ +package main + +import ( + "fmt" + "github.com/dotcloud/docker/devmapper" + "os" +) + +func usage() { + fmt.Printf("Usage: %s [snap new-id base-id] | [remove id] | [mount id mountpoint]\n", os.Args[0]) + os.Exit(1) +} + +func main() { + devices := devmapper.NewDeviceSetDM("/var/lib/docker") + + if len(os.Args) < 2 { + usage() + } + + cmd := os.Args[1] + if cmd == "snap" { + if len(os.Args) < 4 { + usage() + } + + err := devices.AddDevice(os.Args[2], os.Args[3]) + if err != nil { + fmt.Println("Can't create snap device: ", err) + os.Exit(1) + } + } else if cmd == "remove" { + if len(os.Args) < 3 { + usage() + } + + err := devices.RemoveDevice(os.Args[2]) + if err != nil { + fmt.Println("Can't remove device: ", err) + os.Exit(1) + } + } else if cmd == "mount" { + if len(os.Args) < 4 { + usage() + } + + err := devices.MountDevice(os.Args[2], os.Args[3]) + if err != nil { + fmt.Println("Can't create snap device: ", err) + os.Exit(1) + } + } else { + fmt.Printf("Unknown command %s\n", cmd) + if len(os.Args) < 4 { + usage() + } + + os.Exit(1) + } + + return +} From 3681b7af0d7ffe18900b6f6eeaf5f9444b802dd2 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 10 Sep 2013 20:15:23 +0200 Subject: [PATCH 062/301] Add a separate docker-init binary This may be used for the .dockerinit case if the main binary is not statically linked. Upstream-commit: b8dc7b5f1a8111275e2b869dfb67a8689cecf9b9 Component: engine --- components/engine/docker-init/docker-init.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 components/engine/docker-init/docker-init.go diff --git a/components/engine/docker-init/docker-init.go b/components/engine/docker-init/docker-init.go new file mode 100644 index 0000000000..c368c53138 --- /dev/null +++ b/components/engine/docker-init/docker-init.go @@ -0,0 +1,16 @@ +package main + +import ( + "github.com/dotcloud/docker" +) + +var ( + GITCOMMIT string + VERSION string +) + +func main() { + // Running in init mode + docker.SysInit() + return +} From b25a04bd5f76c45cfdfcff230b38efa000c5965a Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 10 Sep 2013 20:21:15 +0200 Subject: [PATCH 063/301] Runtime: Automatically use docker-init if it exists In some builds the main docker binary is not statically linked, and as such not usable in as the .dockerinit binary, for those cases we look for a separately shipped docker-init binary and use that instead. Upstream-commit: 167601e85850aa58df96f9d0796f9c26ed2d6fa4 Component: engine --- components/engine/runtime.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/components/engine/runtime.go b/components/engine/runtime.go index d77ecca722..ad4672b040 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -10,6 +10,7 @@ import ( "os" "os/exec" "path" + "path/filepath" "sort" "strings" "time" @@ -42,7 +43,17 @@ type Runtime struct { var sysInitPath string func init() { - sysInitPath = utils.SelfPath() + selfPath := utils.SelfPath() + + // If we have a separate docker-init, use that, otherwise use the + // main docker binary + dir := filepath.Dir(selfPath) + dockerInitPath := filepath.Join(dir, "docker-init") + if _, err := os.Stat(dockerInitPath); err != nil { + sysInitPath = selfPath + } else { + sysInitPath = dockerInitPath + } } // List returns an array of all containers registered in the runtime. From 3b80b99ef6de39ce419e71739d53de8a8f1dda37 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 11:41:38 +0200 Subject: [PATCH 064/301] Image: Add runtime and container id args to Mount() We will later need the runtime to get access to the VolumeSet singleton, and the container id to have a name for the volume for the container Upstream-commit: e368c8bb01b3c52c8e4c334c3a7f32556af9d632 Component: engine --- components/engine/container.go | 2 +- components/engine/graph_test.go | 5 ++++- components/engine/image.go | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/components/engine/container.go b/components/engine/container.go index 316e0d259f..b74e9f2f0e 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -1158,7 +1158,7 @@ func (container *Container) Mount() error { if err != nil { return err } - return image.Mount(container.RootfsPath(), container.rwPath()) + return image.Mount(container.runtime, container.RootfsPath(), container.rwPath(), container.ID) } func (container *Container) Changes() ([]Change, error) { diff --git a/components/engine/graph_test.go b/components/engine/graph_test.go index 471016938d..d89e6efe9d 100644 --- a/components/engine/graph_test.go +++ b/components/engine/graph_test.go @@ -121,6 +121,9 @@ func TestRegister(t *testing.T) { } func TestMount(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + graph := tempGraph(t) defer os.RemoveAll(graph.Root) archive, err := fakeTar() @@ -144,7 +147,7 @@ func TestMount(t *testing.T) { if err := os.MkdirAll(rw, 0700); err != nil { t.Fatal(err) } - if err := image.Mount(rootfs, rw); err != nil { + if err := image.Mount(runtime, rootfs, rw, "testing"); err != nil { t.Fatal(err) } // FIXME: test for mount contents diff --git a/components/engine/image.go b/components/engine/image.go index 9f34160b80..bbf559e23a 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -170,7 +170,7 @@ func (image *Image) TarLayer(compression Compression) (Archive, error) { return Tar(layerPath, compression) } -func (image *Image) Mount(root, rw string) error { +func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { if mounted, err := Mounted(root); err != nil { return err } else if mounted { From acf340a68b49082243e44fb5b1757a54424c7a30 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 11:44:11 +0200 Subject: [PATCH 065/301] Add DeviceSet interface This interface matches the device-mapper implementation (DeviceSetDM) but is free from any dependencies. This allows core docker code to refer to a DeviceSet without having an explicit dependency on the devmapper package. This is important, because the devmapper package has external dependencies which are not wanted in the docker client app, as it needs to run with minimal dependencies in the docker image. Upstream-commit: e6216793d91a9b003814b535b0373902326753dd Component: engine --- components/engine/deviceset.go | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 components/engine/deviceset.go diff --git a/components/engine/deviceset.go b/components/engine/deviceset.go new file mode 100644 index 0000000000..01bdba411f --- /dev/null +++ b/components/engine/deviceset.go @@ -0,0 +1,11 @@ +package docker + +type DeviceSet interface { + AddDevice(hash, baseHash string) error + SetInitialized(hash string) error + DeactivateDevice(hash string) error + RemoveDevice(hash string) error + MountDevice(hash, path string) error + HasDevice(hash string) bool + HasInitializedDevice(hash string) bool +} From 6c1aee42e6fb5ab2197e174d8844e5de0e453727 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 12:03:45 +0200 Subject: [PATCH 066/301] Server: Pass in device-mapper DeviceSet to server This makes docker (but not docker-init) link to libdevmapper and will allow it to use the DeviceSet Upstream-commit: 1d36b8c7b7b0c943ccb7d69b7181b3e33566d77c Component: engine --- components/engine/docker/docker.go | 3 ++- components/engine/server.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/components/engine/docker/docker.go b/components/engine/docker/docker.go index a0021f3a87..4dcc174d5e 100644 --- a/components/engine/docker/docker.go +++ b/components/engine/docker/docker.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" "github.com/dotcloud/docker" + "github.com/dotcloud/docker/devmapper" "github.com/dotcloud/docker/utils" "io/ioutil" "log" @@ -133,7 +134,7 @@ func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart if flDns != "" { dns = []string{flDns} } - server, err := docker.NewServer(flGraphPath, autoRestart, enableCors, dns) + server, err := docker.NewServer(flGraphPath, devmapper.NewDeviceSetDM(flGraphPath), autoRestart, enableCors, dns) if err != nil { return err } diff --git a/components/engine/server.go b/components/engine/server.go index 5bfbc96924..9db3ca7298 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -1299,7 +1299,7 @@ func (srv *Server) ContainerCopy(name string, resource string, out io.Writer) er } -func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) (*Server, error) { +func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors bool, dns ListOpts) (*Server, error) { if runtime.GOARCH != "amd64" { log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) } From 4f27979b41672c88fcf3dc79fa79a75ab083c658 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 5 Sep 2013 14:40:12 +0200 Subject: [PATCH 067/301] Runtime: Add DeviceSet singleton This adds a DeviceSet singleton to the Runtime object which will be used for any DeviceMapper dependent code. Upstream-commit: ca2f7f955e697091f2b7bee9a33c6c4e106cecd0 Component: engine --- components/engine/runtime.go | 15 ++++++++++++--- components/engine/runtime_test.go | 5 +++-- components/engine/server.go | 2 +- components/engine/utils_test.go | 3 ++- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/components/engine/runtime.go b/components/engine/runtime.go index ad4672b040..91be6c79d8 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -38,6 +38,7 @@ type Runtime struct { volumes *Graph srv *Server Dns []string + deviceSet DeviceSet } var sysInitPath string @@ -75,6 +76,13 @@ func (runtime *Runtime) getContainerElement(id string) *list.Element { return nil } +func (runtime *Runtime) GetDeviceSet() (DeviceSet, error) { + if runtime.deviceSet == nil { + return nil, fmt.Errorf("No device set available") + } + return runtime.deviceSet, nil +} + // Get looks for a container by the specified ID or name, and returns it. // If the container is not found, or if an error occurs, nil is returned. func (runtime *Runtime) Get(name string) *Container { @@ -439,8 +447,8 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a } // FIXME: harmonize with NewGraph() -func NewRuntime(flGraphPath string, autoRestart bool, dns []string) (*Runtime, error) { - runtime, err := NewRuntimeFromDirectory(flGraphPath, autoRestart) +func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns []string) (*Runtime, error) { + runtime, err := NewRuntimeFromDirectory(flGraphPath, deviceSet, autoRestart) if err != nil { return nil, err } @@ -458,7 +466,7 @@ func NewRuntime(flGraphPath string, autoRestart bool, dns []string) (*Runtime, e return runtime, nil } -func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) { +func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool) (*Runtime, error) { runtimeRepo := path.Join(root, "containers") if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) { @@ -495,6 +503,7 @@ func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) { capabilities: &Capabilities{}, autoRestart: autoRestart, volumes: volumes, + deviceSet: deviceSet, } if err := runtime.restore(); err != nil { diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index f4f5d5af1e..503f519d12 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "github.com/dotcloud/docker/utils" + "github.com/dotcloud/docker/devmapper" "io" "log" "net" @@ -87,7 +88,7 @@ func init() { NetworkBridgeIface = unitTestNetworkBridge // Make it our Store root - if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false); err != nil { + if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreBase), false); err != nil { panic(err) } else { globalRuntime = runtime @@ -456,7 +457,7 @@ func TestRestore(t *testing.T) { // Here are are simulating a docker restart - that is, reloading all containers // from scratch - runtime2, err := NewRuntimeFromDirectory(runtime1.root, false) + runtime2, err := NewRuntimeFromDirectory(runtime1.root, devmapper.NewDeviceSetDM(runtime1.root), false) if err != nil { t.Fatal(err) } diff --git a/components/engine/server.go b/components/engine/server.go index 9db3ca7298..f0f6f40257 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -1303,7 +1303,7 @@ func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors if runtime.GOARCH != "amd64" { log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) } - runtime, err := NewRuntime(flGraphPath, autoRestart, dns) + runtime, err := NewRuntime(flGraphPath, deviceSet, autoRestart, dns) if err != nil { return nil, err } diff --git a/components/engine/utils_test.go b/components/engine/utils_test.go index 740a5fc1bc..bc4dd65a3c 100644 --- a/components/engine/utils_test.go +++ b/components/engine/utils_test.go @@ -2,6 +2,7 @@ package docker import ( "github.com/dotcloud/docker/utils" + "github.com/dotcloud/docker/devmapper" "io" "io/ioutil" "os" @@ -42,7 +43,7 @@ func newTestRuntime() (*Runtime, error) { return nil, err } - runtime, err := NewRuntimeFromDirectory(root, false) + runtime, err := NewRuntimeFromDirectory(root, devmapper.NewDeviceSetDM(root), false) if err != nil { return nil, err } From 03b4e52ca706dd2a02a9e0097e98d9477a63c645 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 10 Sep 2013 22:15:18 +0200 Subject: [PATCH 068/301] Runtime: Add MountMethod to allow AUFS and device-mapper to coexist Upstream-commit: 8f7361279c13660a617886a523c50ecbc6849129 Component: engine --- components/engine/runtime.go | 48 ++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 91be6c79d8..21769789f5 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -17,6 +17,13 @@ import ( ) var defaultDns = []string{"8.8.8.8", "8.8.4.4"} +type MountMethod int + +const ( + MountMethodNone MountMethod = iota + MountMethodAUFS + MountMethodDeviceMapper +) type Capabilities struct { MemoryLimit bool @@ -39,6 +46,7 @@ type Runtime struct { srv *Server Dns []string deviceSet DeviceSet + mountMethod MountMethod } var sysInitPath string @@ -76,6 +84,46 @@ func (runtime *Runtime) getContainerElement(id string) *list.Element { return nil } +func hasFilesystemSupport(fstype string) bool { + content, err := ioutil.ReadFile("/proc/filesystems") + if err != nil { + log.Printf("WARNING: Unable to read /proc/filesystems, assuming fs %s is not supported.", fstype) + return false + } + lines := strings.Split(string(content), "\n") + for _, line := range lines { + if strings.HasPrefix(line, "nodev") { + line = line[5:] + } + line = strings.TrimSpace(line) + if line == fstype { + return true + } + } + return false +} + +func (runtime *Runtime) GetMountMethod() MountMethod { + if runtime.mountMethod == MountMethodNone { + // Try to automatically pick a method + if hasFilesystemSupport("aufs") { + log.Printf("Using AUFS backend.") + runtime.mountMethod = MountMethodAUFS + } else { + _ = exec.Command("modprobe", "aufs").Run() + if hasFilesystemSupport("aufs") { + log.Printf("Using AUFS backend.") + runtime.mountMethod = MountMethodAUFS + } else { + log.Printf("Using device-mapper backend.") + runtime.mountMethod = MountMethodDeviceMapper + } + } + } + + return runtime.mountMethod +} + func (runtime *Runtime) GetDeviceSet() (DeviceSet, error) { if runtime.deviceSet == nil { return nil, fmt.Errorf("No device set available") From f4fd5894a289e13cb043d0d5440ba53d5d66be7e Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 12:55:48 +0200 Subject: [PATCH 069/301] Image: Initial support for device-mapper mounts This supports creating images from layers and mounting them for running a container. Not supported yet are: * Creating diffs between images/containers * Creating layers for new images from a device-mapper container Upstream-commit: d2ba3e200576c2bcceb81d8d3d65b86fbf49313b Component: engine --- components/engine/image.go | 269 +++++++++++++++++++++++++++++++++++-- 1 file changed, 260 insertions(+), 9 deletions(-) diff --git a/components/engine/image.go b/components/engine/image.go index bbf559e23a..07eca11269 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -15,6 +15,7 @@ import ( "path/filepath" "strconv" "strings" + "syscall" "time" ) @@ -136,6 +137,10 @@ func jsonPath(root string) string { return path.Join(root, "json") } +func mountPath(root string) string { + return path.Join(root, "mount") +} + func MountAUFS(ro []string, rw string, target string) error { // FIXME: Now mount the layers rwBranch := fmt.Sprintf("%v=rw", rw) @@ -170,25 +175,271 @@ func (image *Image) TarLayer(compression Compression) (Archive, error) { return Tar(layerPath, compression) } +func (image *Image) applyLayer(layer, target string) error { + oldmask := syscall.Umask(0) + defer syscall.Umask(oldmask) + err := filepath.Walk(layer, func(srcPath string, f os.FileInfo, err error) error { + if err != nil { + return err + } + + // Skip root + if srcPath == layer { + return nil + } + + var srcStat syscall.Stat_t + err = syscall.Lstat(srcPath, &srcStat) + if err != nil { + return err + } + + relPath, err := filepath.Rel(layer, srcPath) + if err != nil { + return err + } + + targetPath := filepath.Join(target, relPath) + + // Skip AUFS metadata + if matched, err := filepath.Match(".wh..wh.*", relPath); err != nil || matched { + if err != nil || !f.IsDir() { + return err + } + return filepath.SkipDir + } + + // Find out what kind of modification happened + file := filepath.Base(srcPath) + + // If there is a whiteout, then the file was removed + if strings.HasPrefix(file, ".wh.") { + originalFile := file[len(".wh."):] + deletePath := filepath.Join(filepath.Dir(targetPath), originalFile) + + err = os.RemoveAll(deletePath) + if err != nil { + return err + } + } else { + var targetStat = &syscall.Stat_t{} + err := syscall.Lstat(targetPath, targetStat) + if err != nil { + if !os.IsNotExist(err) { + return err + } + targetStat = nil + } + + if targetStat != nil && !(targetStat.Mode&syscall.S_IFDIR == syscall.S_IFDIR && srcStat.Mode&syscall.S_IFDIR == syscall.S_IFDIR) { + // Unless both src and dest are directories we remove the target and recreate it + // This is a bit wasteful in the case of only a mode change, but that is unlikely + // to matter much + err = os.RemoveAll(targetPath) + if err != nil { + return err + } + targetStat = nil + } + + if f.IsDir() { + // Source is a directory + if targetStat == nil { + err = syscall.Mkdir(targetPath, srcStat.Mode&07777) + if err != nil { + return err + } + } else if srcStat.Mode&07777 != targetStat.Mode&07777 { + err = syscall.Chmod(targetPath, srcStat.Mode&07777) + if err != nil { + return err + } + } + } else if srcStat.Mode&syscall.S_IFLNK == syscall.S_IFLNK { + // Source is symlink + link, err := os.Readlink(srcPath) + if err != nil { + return err + } + + err = os.Symlink(link, targetPath) + if err != nil { + return err + } + } else if srcStat.Mode&syscall.S_IFBLK == syscall.S_IFBLK || + srcStat.Mode&syscall.S_IFCHR == syscall.S_IFCHR || + srcStat.Mode&syscall.S_IFIFO == syscall.S_IFIFO || + srcStat.Mode&syscall.S_IFSOCK == syscall.S_IFSOCK { + // Source is special file + err = syscall.Mknod(targetPath, srcStat.Mode, int(srcStat.Rdev)) + if err != nil { + return err + } + } else if srcStat.Mode&syscall.S_IFREG == syscall.S_IFREG { + // Source is regular file + fd, err := syscall.Open(targetPath, syscall.O_CREAT|syscall.O_WRONLY, srcStat.Mode&07777) + if err != nil { + return err + } + dstFile := os.NewFile(uintptr(fd), targetPath) + srcFile, err := os.Open(srcPath) + _, err = io.Copy(dstFile, srcFile) + if err != nil { + return err + } + _ = srcFile.Close() + _ = dstFile.Close() + } else { + return fmt.Errorf("Unknown type for file %s", srcPath) + } + + if srcStat.Mode&syscall.S_IFLNK != syscall.S_IFLNK { + err = syscall.Chown(targetPath, int(srcStat.Uid), int(srcStat.Gid)) + if err != nil { + return err + } + ts := []syscall.Timeval{ + syscall.NsecToTimeval(srcStat.Atim.Nano()), + syscall.NsecToTimeval(srcStat.Mtim.Nano()), + } + syscall.Utimes(targetPath, ts) + } + + } + return nil + }) + return err +} + +func (image *Image) ensureImageDevice(devices DeviceSet) error { + if devices.HasInitializedDevice(image.ID) { + return nil + } + + if image.Parent != "" && !devices.HasInitializedDevice(image.Parent) { + parentImg, err := image.GetParent() + if err != nil { + return fmt.Errorf("Error while getting parent image: %v", err) + } + err = parentImg.ensureImageDevice(devices) + if err != nil { + return err + } + } + + root, err := image.root() + if err != nil { + return err + } + + mountDir := mountPath(root) + if err := os.Mkdir(mountDir, 0600); err != nil && !os.IsExist(err) { + return err + } + + mounted, err := Mounted(mountDir) + if err == nil && mounted { + log.Printf("Image %s is unexpectedly mounted, unmounting...", image.ID) + err = syscall.Unmount(mountDir, 0) + if err != nil { + return err + } + } + + if devices.HasDevice(image.ID) { + log.Printf("Found non-initialized demove-mapper device for image %s, removing", image.ID) + err = devices.RemoveDevice(image.ID) + if err != nil { + return err + } + } + + log.Printf("Creating device-mapper device for image id %s", image.ID) + + err = devices.AddDevice(image.ID, image.Parent) + if err != nil { + return err + } + + utils.Debugf("Mounting device %s at %s for image setup", image.ID, mountDir) + err = devices.MountDevice(image.ID, mountDir) + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + + utils.Debugf("Applying layer %s at %s", image.ID, mountDir) + err = image.applyLayer(layerPath(root), mountDir) + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + + utils.Debugf("Unmounting %s", mountDir) + err = syscall.Unmount(mountDir, 0) + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + + devices.SetInitialized(image.ID) + + // No need to the device-mapper device to hang around once we've written + // the image, it can be enabled on-demand when needed + devices.DeactivateDevice(image.ID) + + return nil +} + func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { if mounted, err := Mounted(root); err != nil { return err } else if mounted { return fmt.Errorf("%s is already mounted", root) } - layers, err := image.layers() - if err != nil { - return err - } // Create the target directories if they don't exist if err := os.Mkdir(root, 0755); err != nil && !os.IsExist(err) { return err } - if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { - return err - } - if err := MountAUFS(layers, rw, root); err != nil { - return err + switch runtime.GetMountMethod() { + case MountMethodNone: + return fmt.Errorf("No supported Mount implementation") + + case MountMethodAUFS: + if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { + return err + } + layers, err := image.layers() + if err != nil { + return err + } + if err := MountAUFS(layers, rw, root); err != nil { + return err + } + + case MountMethodDeviceMapper: + devices, err := runtime.GetDeviceSet() + if err != nil { + return err + } + err = image.ensureImageDevice(devices) + if err != nil { + return err + } + + if !devices.HasDevice(id) { + utils.Debugf("Creating device %s for container based on image %s", id, image.ID) + err = devices.AddDevice(id, image.ID) + if err != nil { + return err + } + } + + utils.Debugf("Mounting container %s at %s for container", id, root) + err = devices.MountDevice(id, root) + if err != nil { + return err + } } return nil } From 0134fab2c609172c5b25234f436ca7ca335c27c9 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 14:21:53 +0200 Subject: [PATCH 070/301] Image: Deactivate image device when unmounting container There is no need to keep all the device-mapper devices active, we can just activate them on demand if needed. Upstream-commit: a9ec1dbc9bec91e1c0f1b751a06680570a04e915 Component: engine --- components/engine/container.go | 7 ++++++- components/engine/image.go | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/components/engine/container.go b/components/engine/container.go index b74e9f2f0e..5bc9153993 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -1181,7 +1181,12 @@ func (container *Container) Mounted() (bool, error) { } func (container *Container) Unmount() error { - return Unmount(container.RootfsPath()) + image, err := container.GetImage() + if err != nil { + return err + } + err = image.Unmount(container.runtime, container.RootfsPath(), container.ID) + return err } // ShortID returns a shorthand version of the container's id for convenience. diff --git a/components/engine/image.go b/components/engine/image.go index 07eca11269..b306dfd956 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -444,6 +444,30 @@ func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { return nil } +func (image *Image) Unmount(runtime *Runtime, root string, id string) error { + switch runtime.GetMountMethod() { + case MountMethodNone: + return fmt.Errorf("No supported Unmount implementation") + + case MountMethodAUFS: + return Unmount(root) + + case MountMethodDeviceMapper: + err := syscall.Unmount(root, 0) + if err != nil { + return err + } + + // Try to deactivate the device as generally there is no use for it anymore + devices, err := runtime.GetDeviceSet() + if err != nil { + return err; + } + return devices.DeactivateDevice(id) + } + return nil +} + func (image *Image) Changes(rw string) ([]Change, error) { layers, err := image.layers() if err != nil { From 11ba6503a797a032811dd9a3cfef3e20945c423a Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 15:44:01 +0200 Subject: [PATCH 071/301] Image: Always create a .docker-id file in the devices we create Without this there is really no way to map back from the device-mapper devices to the actual docker image/container ids in case the json file somehow got lost Upstream-commit: 074f38d49377411cf0b805095c0d9909d4859f3c Component: engine --- components/engine/image.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/components/engine/image.go b/components/engine/image.go index b306dfd956..9d9a64cceb 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -368,6 +368,13 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { return err } + + err = ioutil.WriteFile(path.Join(mountDir, ".docker-id"), []byte(image.ID), 0600) + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + utils.Debugf("Applying layer %s at %s", image.ID, mountDir) err = image.applyLayer(layerPath(root), mountDir) if err != nil { @@ -427,12 +434,14 @@ func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { return err } + createdDevice := false if !devices.HasDevice(id) { utils.Debugf("Creating device %s for container based on image %s", id, image.ID) err = devices.AddDevice(id, image.ID) if err != nil { return err } + createdDevice = true } utils.Debugf("Mounting container %s at %s for container", id, root) @@ -440,6 +449,15 @@ func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { if err != nil { return err } + + if createdDevice { + err = ioutil.WriteFile(path.Join(root, ".docker-id"), []byte(id), 0600) + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + } + } return nil } From 8fb407ad518d6354174bf0dbab1fa79c0c50cf1a Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 5 Sep 2013 15:32:57 +0200 Subject: [PATCH 072/301] devmapper: Base the device-mapper names on the root dir name This means the default is "docker-*", but for tests we get separate prefixes for each test. Upstream-commit: 8e7cbbff504b1d5b0680d2a14821d1d7b0757ab0 Component: engine --- .../engine/devmapper/deviceset_devmapper.go | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index d7e122cf28..4544215b63 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -10,6 +10,7 @@ import ( "os/exec" "path" "path/filepath" + "strings" "syscall" ) @@ -18,11 +19,12 @@ const defaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024 const defaultBaseFsSize uint64 = 10 * 1024 * 1024 * 1024 type DevInfo struct { - Hash string `json:"-"` - DeviceId int `json:"device_id"` - Size uint64 `json:"size"` - TransactionId uint64 `json:"transaction_id"` - Initialized bool `json:"initialized"` + Hash string `json:"-"` + DeviceId int `json:"device_id"` + Size uint64 `json:"size"` + TransactionId uint64 `json:"transaction_id"` + Initialized bool `json:"initialized"` + devices *DeviceSetDM `json:"-"` } type MetaData struct { @@ -31,7 +33,8 @@ type MetaData struct { type DeviceSetDM struct { initialized bool - root string + root string + devicePrefix string MetaData TransactionId uint64 NewTransactionId uint64 @@ -47,7 +50,7 @@ func (info *DevInfo) Name() string { if hash == "" { hash = "base" } - return fmt.Sprintf("docker-%s", hash) + return fmt.Sprintf("%s-%s", info.devices.devicePrefix, hash) } func (info *DevInfo) DevName() string { @@ -63,7 +66,7 @@ func (devices *DeviceSetDM) jsonFile() string { } func (devices *DeviceSetDM) getPoolName() string { - return "docker-pool" + return fmt.Sprintf("%s-pool", devices.devicePrefix) } func (devices *DeviceSetDM) getPoolDevName() string { @@ -446,6 +449,7 @@ func (devices *DeviceSetDM) registerDevice(id int, hash string, size uint64) (*D Size: size, TransactionId: transaction, Initialized: false, + devices: devices, } devices.Devices[hash] = info @@ -520,6 +524,7 @@ func (devices *DeviceSetDM) loadMetaData() error { for hash, d := range devices.Devices { d.Hash = hash + d.devices = devices if d.DeviceId >= devices.nextFreeDevice { devices.nextFreeDevice = d.DeviceId + 1 @@ -873,7 +878,7 @@ func (devices *DeviceSetDM) SetInitialized(hash string) error { } func (devices *DeviceSetDM) ensureInit() error { - if (!devices.initialized) { + if !devices.initialized { devices.initialized = true err := devices.initDevmapper() if err != nil { @@ -885,9 +890,16 @@ func (devices *DeviceSetDM) ensureInit() error { func NewDeviceSetDM(root string) *DeviceSetDM { SetDevDir("/dev") + + base := filepath.Base(root) + if !strings.HasPrefix(base, "docker") { + base = "docker-" + base + } + devices := &DeviceSetDM{ initialized: false, - root: root, + root: root, + devicePrefix: base, } devices.Devices = make(map[string]*DevInfo) From 534a030a9746abebc68d88bffb79ad8f474d73c7 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 5 Sep 2013 18:03:49 +0200 Subject: [PATCH 073/301] Implement docker diff for device-mapper To do diffing we just compare file metadata, so this relies on things like size and mtime/ctime to catch any changes. Its *possible* to trick this by updating a file without changing the size and setting back the mtime/ctime, but that seems pretty unlikely to happen in reality, and lets us avoid comparing the actual file data. Upstream-commit: 1c5dc26a7c0a0abb7bc59174768ec309f6c5fd4f Component: engine --- components/engine/changes.go | 104 ++++++++++++++++++++++++++++++++- components/engine/container.go | 6 +- components/engine/image.go | 39 +++++++++++-- 3 files changed, 142 insertions(+), 7 deletions(-) diff --git a/components/engine/changes.go b/components/engine/changes.go index 43573cd606..00c9cc7c77 100644 --- a/components/engine/changes.go +++ b/components/engine/changes.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" "strings" + "syscall" ) type ChangeType int @@ -33,7 +34,7 @@ func (change *Change) String() string { return fmt.Sprintf("%s %s", kind, change.Path) } -func Changes(layers []string, rw string) ([]Change, error) { +func ChangesAUFS(layers []string, rw string) ([]Change, error) { var changes []Change err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error { if err != nil { @@ -104,3 +105,104 @@ func Changes(layers []string, rw string) ([]Change, error) { } return changes, nil } + +func ChangesDirs(newDir, oldDir string) ([]Change, error) { + var changes []Change + err := filepath.Walk(newDir, func(newPath string, f os.FileInfo, err error) error { + if err != nil { + return err + } + + var newStat syscall.Stat_t + err = syscall.Lstat(newPath, &newStat) + if err != nil { + return err + } + + // Rebase path + relPath, err := filepath.Rel(newDir, newPath) + if err != nil { + return err + } + relPath = filepath.Join("/", relPath) + + // Skip root + if relPath == "/" || relPath == "/.docker-id" { + return nil + } + + change := Change{ + Path: relPath, + } + + oldPath := filepath.Join(oldDir, relPath) + + var oldStat = &syscall.Stat_t{} + err = syscall.Lstat(oldPath, oldStat) + if err != nil { + if !os.IsNotExist(err) { + return err + } + oldStat = nil + } + + if oldStat == nil { + change.Kind = ChangeAdd + changes = append(changes, change) + } else { + if oldStat.Ino != newStat.Ino || + oldStat.Mode != newStat.Mode || + oldStat.Uid != newStat.Uid || + oldStat.Gid != newStat.Gid || + oldStat.Rdev != newStat.Rdev || + oldStat.Size != newStat.Size || + oldStat.Blocks != newStat.Blocks || + oldStat.Mtim != newStat.Mtim || + oldStat.Ctim != newStat.Ctim { + change.Kind = ChangeModify + changes = append(changes, change) + } + } + + return nil + }) + if err != nil { + return nil, err + } + err = filepath.Walk(oldDir, func(oldPath string, f os.FileInfo, err error) error { + if err != nil { + return err + } + + // Rebase path + relPath, err := filepath.Rel(oldDir, oldPath) + if err != nil { + return err + } + relPath = filepath.Join("/", relPath) + + // Skip root + if relPath == "/" { + return nil + } + + change := Change{ + Path: relPath, + } + + newPath := filepath.Join(newDir, relPath) + + var newStat = &syscall.Stat_t{} + err = syscall.Lstat(newPath, newStat) + if err != nil && os.IsNotExist(err) { + change.Kind = ChangeDelete + changes = append(changes, change) + } + + return nil + }) + if err != nil { + return nil, err + } + return changes, nil +} diff --git a/components/engine/container.go b/components/engine/container.go index 5bc9153993..33e0b9b48e 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -1162,11 +1162,15 @@ func (container *Container) Mount() error { } func (container *Container) Changes() ([]Change, error) { + if err := container.EnsureMounted(); err != nil { + return nil, err + } + image, err := container.GetImage() if err != nil { return nil, err } - return image.Changes(container.rwPath()) + return image.Changes(container.runtime, container.RootfsPath(), container.rwPath(), container.ID) } func (container *Container) GetImage() (*Image, error) { diff --git a/components/engine/image.go b/components/engine/image.go index 9d9a64cceb..119e07c70e 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -486,12 +486,41 @@ func (image *Image) Unmount(runtime *Runtime, root string, id string) error { return nil } -func (image *Image) Changes(rw string) ([]Change, error) { - layers, err := image.layers() - if err != nil { - return nil, err +func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, error) { + switch runtime.GetMountMethod() { + case MountMethodAUFS: + layers, err := image.layers() + if err != nil { + return nil, err + } + return ChangesAUFS(layers, rw) + + case MountMethodDeviceMapper: + devices, err := runtime.GetDeviceSet() + if err != nil { + return nil, err + } + + if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { + return nil, err + } + + // We re-use rw for the temporary mount of the base image as its + // not used by device-mapper otherwise + err = devices.MountDevice(image.ID, rw) + if err != nil { + return nil, err + } + + changes, err := ChangesDirs(root, rw) + _ = syscall.Unmount(rw, 0) + if err != nil { + return nil, err + } + return changes, nil } - return Changes(layers, rw) + + return nil, fmt.Errorf("No supported Changes implementation") } func (image *Image) ShortID() string { From 53e75b5d6054d37102fa39491ca18e2c094dd53b Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 5 Sep 2013 20:11:18 +0200 Subject: [PATCH 074/301] Archive: Fix up tar commandline arguments in TarFilter() There is no need to duplicate the compression flags for every element in the filter. Upstream-commit: b86f67126c86a07aac155f38aefd6c1ef538e24d Component: engine --- components/engine/archive.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/engine/archive.go b/components/engine/archive.go index bb019fb033..80b305a418 100644 --- a/components/engine/archive.go +++ b/components/engine/archive.go @@ -90,8 +90,9 @@ func TarFilter(path string, compression Compression, filter []string) (io.Reader if filter == nil { filter = []string{"."} } + args = append(args, "-c"+compression.Flag()) for _, f := range filter { - args = append(args, "-c"+compression.Flag(), f) + args = append(args, f) } return CmdStream(exec.Command(args[0], args[1:]...)) } From 06dfc21edb1a5adef8fcd49954c3aacd16ba0986 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 5 Sep 2013 22:10:29 +0200 Subject: [PATCH 075/301] Make TarFilter more useful There are a few changes: * Callers can specify if they want recursive behaviour or not * All file listings to tar are sent on stdin, to handle long lists better * We can pass in a list of filenames which will be created as empty files in the tarball This is exactly what we want for the creation of layer tarballs given a container fs, a set of files to add and a set of whiteout files to create. Upstream-commit: fda6ff9c2707efbd1c9d1f2bf151b9d1d082d0c6 Component: engine --- components/engine/archive.go | 82 ++++++++++++++++++++++++++++--- components/engine/archive_test.go | 6 +-- components/engine/container.go | 2 +- 3 files changed, 78 insertions(+), 12 deletions(-) diff --git a/components/engine/archive.go b/components/engine/archive.go index 80b305a418..f3f7b8c59e 100644 --- a/components/engine/archive.go +++ b/components/engine/archive.go @@ -80,21 +80,73 @@ func (compression *Compression) Extension() string { // Tar creates an archive from the directory at `path`, and returns it as a // stream of bytes. func Tar(path string, compression Compression) (io.Reader, error) { - return TarFilter(path, compression, nil) + return TarFilter(path, compression, nil, true, nil) +} + +func escapeName(name string) string { + escaped := make([]byte,0) + for i, c := range []byte(name) { + if i == 0 && c == '/' { + continue + } + // all printable chars except "-" which is 0x2d + if (0x20 <= c && c <= 0x7E) && c != 0x2d { + escaped = append(escaped, c) + } else { + escaped = append(escaped, fmt.Sprintf("\\%03o", c)...) + } + } + return string(escaped) } // Tar creates an archive from the directory at `path`, only including files whose relative // paths are included in `filter`. If `filter` is nil, then all files are included. -func TarFilter(path string, compression Compression, filter []string) (io.Reader, error) { - args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path} +func TarFilter(path string, compression Compression, filter []string, recursive bool, createFiles []string) (io.Reader, error) { + args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path, "-T", "-",} if filter == nil { filter = []string{"."} } args = append(args, "-c"+compression.Flag()) - for _, f := range filter { - args = append(args, f) + + if !recursive { + args = append(args, "--no-recursion") } - return CmdStream(exec.Command(args[0], args[1:]...)) + + files := "" + for _, f := range filter { + files = files + escapeName(f) + "\n" + } + + tmpDir := "" + + if createFiles != nil { + tmpDir, err := ioutil.TempDir("", "docker-tar") + if err != nil { + return nil, err + } + + files = files + "-C" + tmpDir + "\n" + for _, f := range createFiles { + path := filepath.Join(tmpDir, f) + err := os.MkdirAll(filepath.Dir(path), 0600) + if err != nil { + return nil, err + } + + if file, err := os.OpenFile(path, os.O_CREATE, 0600); err != nil { + return nil, err + } else { + file.Close() + } + files = files + escapeName(f) + "\n" + } + } + + return CmdStream(exec.Command(args[0], args[1:]...), &files, func () { + if tmpDir != "" { + _ = os.RemoveAll(tmpDir) + } + }) } // Untar reads a stream of bytes from `archive`, parses it as a tar archive, @@ -141,7 +193,7 @@ func Untar(archive io.Reader, path string) error { // TarUntar aborts and returns the error. func TarUntar(src string, filter []string, dst string) error { utils.Debugf("TarUntar(%s %s %s)", src, filter, dst) - archive, err := TarFilter(src, Uncompressed, filter) + archive, err := TarFilter(src, Uncompressed, filter, true, nil) if err != nil { return err } @@ -228,7 +280,18 @@ func CopyFileWithTar(src, dst string) error { // CmdStream executes a command, and returns its stdout as a stream. // If the command fails to run or doesn't complete successfully, an error // will be returned, including anything written on stderr. -func CmdStream(cmd *exec.Cmd) (io.Reader, error) { +func CmdStream(cmd *exec.Cmd, input *string, atEnd func()) (io.Reader, error) { + if input != nil { + stdin, err := cmd.StdinPipe() + if err != nil { + return nil, err + } + // Write stdin if any + go func() { + _, _ = stdin.Write([]byte(*input)) + stdin.Close() + }() + } stdout, err := cmd.StdoutPipe() if err != nil { return nil, err @@ -259,6 +322,9 @@ func CmdStream(cmd *exec.Cmd) (io.Reader, error) { } else { pipeW.Close() } + if atEnd != nil { + atEnd() + } }() // Run the command and return the pipe if err := cmd.Start(); err != nil { diff --git a/components/engine/archive_test.go b/components/engine/archive_test.go index 9a0a8e1b9e..c86b4511c4 100644 --- a/components/engine/archive_test.go +++ b/components/engine/archive_test.go @@ -14,7 +14,7 @@ import ( func TestCmdStreamLargeStderr(t *testing.T) { cmd := exec.Command("/bin/sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello") - out, err := CmdStream(cmd) + out, err := CmdStream(cmd, nil, nil) if err != nil { t.Fatalf("Failed to start command: %s", err) } @@ -35,7 +35,7 @@ func TestCmdStreamLargeStderr(t *testing.T) { func TestCmdStreamBad(t *testing.T) { badCmd := exec.Command("/bin/sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1") - out, err := CmdStream(badCmd) + out, err := CmdStream(badCmd, nil, nil) if err != nil { t.Fatalf("Failed to start command: %s", err) } @@ -50,7 +50,7 @@ func TestCmdStreamBad(t *testing.T) { func TestCmdStreamGood(t *testing.T) { cmd := exec.Command("/bin/sh", "-c", "echo hello; exit 0") - out, err := CmdStream(cmd) + out, err := CmdStream(cmd, nil, nil) if err != nil { t.Fatal(err) } diff --git a/components/engine/container.go b/components/engine/container.go index 33e0b9b48e..668a3dd8e9 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -1278,5 +1278,5 @@ func (container *Container) Copy(resource string) (Archive, error) { filter = []string{path.Base(basePath)} basePath = path.Dir(basePath) } - return TarFilter(basePath, Uncompressed, filter) + return TarFilter(basePath, Uncompressed, filter, true, nil) } From 79ab6381a58d324611aaa114d49740ed1dd49272 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 5 Sep 2013 22:14:19 +0200 Subject: [PATCH 076/301] Implement container.ExportRW() on device-mapper Upstream-commit: b0626f403b168b9020a802ec3bc4ad8c9bbc2486 Component: engine --- components/engine/container.go | 10 +++++++++- components/engine/image.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/components/engine/container.go b/components/engine/container.go index 668a3dd8e9..cd143f7236 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -1111,7 +1111,15 @@ func (container *Container) Resize(h, w int) error { } func (container *Container) ExportRw() (Archive, error) { - return Tar(container.rwPath(), Uncompressed) + if err := container.EnsureMounted(); err != nil { + return nil, err + } + + image, err := container.GetImage() + if err != nil { + return nil, err + } + return image.ExportChanges(container.runtime, container.RootfsPath(), container.rwPath(), container.ID) } func (container *Container) RwChecksum() (string, error) { diff --git a/components/engine/image.go b/components/engine/image.go index 119e07c70e..546c54a577 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -523,6 +523,37 @@ func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, er return nil, fmt.Errorf("No supported Changes implementation") } +func (image *Image) ExportChanges(runtime *Runtime, root, rw, id string) (Archive, error) { + switch runtime.GetMountMethod() { + case MountMethodAUFS: + return Tar(rw, Uncompressed) + + case MountMethodDeviceMapper: + changes, err := image.Changes(runtime, root, rw, id) + if err != nil { + return nil, err + } + + files := make([]string, 0) + deletions := make([]string, 0) + for _, change := range changes { + if change.Kind == ChangeModify || change.Kind == ChangeAdd { + files = append(files, change.Path) + } + if change.Kind == ChangeDelete { + base := filepath.Base(change.Path) + dir := filepath.Dir(change.Path) + deletions = append(deletions, filepath.Join(dir, ".wh."+base)) + } + } + + return TarFilter(root, Uncompressed, files, false, deletions) + } + + return nil, fmt.Errorf("No supported Changes implementation") +} + + func (image *Image) ShortID() string { return utils.TruncateID(image.ID) } From fef827ba7be38e79a7f3b498d816105bd737e94c Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 6 Sep 2013 12:03:21 +0200 Subject: [PATCH 077/301] Runtime: Delete corresponding devices when deleting container Upstream-commit: 30890c7763e775d38c82d3eac5c1650e149137ae Component: engine --- components/engine/runtime.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 21769789f5..220ca1af6c 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -283,6 +283,11 @@ func (runtime *Runtime) Destroy(container *Container) error { if err := os.RemoveAll(container.root); err != nil { return fmt.Errorf("Unable to remove filesystem for %v: %v", container.ID, err) } + if runtime.GetMountMethod() == MountMethodDeviceMapper { + if err := runtime.deviceSet.RemoveDevice(container.ID); err != nil { + return fmt.Errorf("Unable to remove device for %v: %v", container.ID, err) + } + } return nil } From 348d75b3b59e102f47216966978092c281624b55 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 6 Sep 2013 12:04:00 +0200 Subject: [PATCH 078/301] Delete corresponding Devices when deleting Images If an image is deleted and there is a corresponding device for that image we also delete the image. Upstream-commit: 99393cf3cfd08de769f0c37f06b912fb3771a080 Component: engine --- components/engine/runtime.go | 13 +++++++++++++ components/engine/runtime_test.go | 2 +- components/engine/server.go | 4 ++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 220ca1af6c..7d1c10a279 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -291,6 +291,19 @@ func (runtime *Runtime) Destroy(container *Container) error { return nil } +func (runtime *Runtime) DeleteImage(id string) error { + err := runtime.graph.Delete(id) + if err != nil { + return err + } + if runtime.GetMountMethod() == MountMethodDeviceMapper && runtime.deviceSet.HasDevice(id) { + if err := runtime.deviceSet.RemoveDevice(id); err != nil { + return fmt.Errorf("Unable to remove device for %v: %v", id, err) + } + } + return nil +} + func (runtime *Runtime) restore() error { wheel := "-\\|/" if os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" { diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index 503f519d12..f9a209008c 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -57,7 +57,7 @@ func cleanup(runtime *Runtime) error { } for _, image := range images { if image.ID != unitTestImageID { - runtime.graph.Delete(image.ID) + runtime.DeleteImage(image.ID) } } return nil diff --git a/components/engine/server.go b/components/engine/server.go index f0f6f40257..f3081db81a 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -1025,7 +1025,7 @@ func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi) error { if err := srv.runtime.repositories.DeleteAll(id); err != nil { return err } - err := srv.runtime.graph.Delete(id) + err := srv.runtime.DeleteImage(id) if err != nil { return err } @@ -1099,7 +1099,7 @@ func (srv *Server) ImageDelete(name string, autoPrune bool) ([]APIRmi, error) { return nil, fmt.Errorf("No such image: %s", name) } if !autoPrune { - if err := srv.runtime.graph.Delete(img.ID); err != nil { + if err := srv.runtime.DeleteImage(img.ID); err != nil { return nil, fmt.Errorf("Error deleting image %s: %s", name, err) } return nil, nil From 0a6a9342795923504f1b55d5398f46fa83cd7bef Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 6 Sep 2013 14:37:04 +0200 Subject: [PATCH 079/301] Add DeviceSetWrapper This wraps an existing DeviceSet and just adds a prefix to all ids in it. This will be useful for reusing a single DeviceSet for all the tests (but with separate ids) Upstream-commit: 381ce94ef4fe2590b0afa16c5f35a508de12e7a3 Component: engine --- components/engine/deviceset.go | 49 ++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/components/engine/deviceset.go b/components/engine/deviceset.go index 01bdba411f..2caaf153b2 100644 --- a/components/engine/deviceset.go +++ b/components/engine/deviceset.go @@ -9,3 +9,52 @@ type DeviceSet interface { HasDevice(hash string) bool HasInitializedDevice(hash string) bool } + +type DeviceSetWrapper struct { + wrapped DeviceSet + prefix string +} + +func (wrapper *DeviceSetWrapper) wrap(hash string) string { + if hash != "" { + hash = wrapper.prefix + "-" + hash + } + return hash +} + + +func (wrapper *DeviceSetWrapper) AddDevice(hash, baseHash string) error { + return wrapper.wrapped.AddDevice(wrapper.wrap(hash), wrapper.wrap(baseHash)) +} + +func (wrapper *DeviceSetWrapper) SetInitialized(hash string) error { + return wrapper.wrapped.SetInitialized(wrapper.wrap(hash)) +} + +func (wrapper *DeviceSetWrapper) DeactivateDevice(hash string) error { + return wrapper.wrapped.DeactivateDevice(wrapper.wrap(hash)) +} + +func (wrapper *DeviceSetWrapper) RemoveDevice(hash string) error { + return wrapper.wrapped.RemoveDevice(wrapper.wrap(hash)) +} + +func (wrapper *DeviceSetWrapper) MountDevice(hash, path string) error { + return wrapper.wrapped.MountDevice(wrapper.wrap(hash), path) +} + +func (wrapper *DeviceSetWrapper) HasDevice(hash string) bool { + return wrapper.wrapped.HasDevice(wrapper.wrap(hash)) +} + +func (wrapper *DeviceSetWrapper) HasInitializedDevice(hash string) bool { + return wrapper.wrapped.HasInitializedDevice(wrapper.wrap(hash)) +} + +func NewDeviceSetWrapper(wrapped DeviceSet, prefix string) DeviceSet { + wrapper := &DeviceSetWrapper{ + wrapped: wrapped, + prefix: prefix, + } + return wrapper +} From 816bf23aaff1e1d6af714cc8b11e1fe2bd7ab2f0 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 6 Sep 2013 14:38:47 +0200 Subject: [PATCH 080/301] Reuse a single DeviceSetDM for all the tests We wrap the "real" DeviceSet for each test so that we get only a single device-mapper pool and loopback mounts, but still separate out the IDs in the tests. This makes the test run much faster. Upstream-commit: 52294192b2a008b624862701cbf8491ad19b0798 Component: engine --- components/engine/runtime_test.go | 2 +- components/engine/utils_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index f9a209008c..5fa6c46bfe 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -457,7 +457,7 @@ func TestRestore(t *testing.T) { // Here are are simulating a docker restart - that is, reloading all containers // from scratch - runtime2, err := NewRuntimeFromDirectory(runtime1.root, devmapper.NewDeviceSetDM(runtime1.root), false) + runtime2, err := NewRuntimeFromDirectory(runtime1.root, runtime1.deviceSet, false) if err != nil { t.Fatal(err) } diff --git a/components/engine/utils_test.go b/components/engine/utils_test.go index bc4dd65a3c..f5bdac3271 100644 --- a/components/engine/utils_test.go +++ b/components/engine/utils_test.go @@ -2,7 +2,7 @@ package docker import ( "github.com/dotcloud/docker/utils" - "github.com/dotcloud/docker/devmapper" + "path/filepath" "io" "io/ioutil" "os" @@ -43,7 +43,7 @@ func newTestRuntime() (*Runtime, error) { return nil, err } - runtime, err := NewRuntimeFromDirectory(root, devmapper.NewDeviceSetDM(root), false) + runtime, err := NewRuntimeFromDirectory(root, NewDeviceSetWrapper (globalRuntime.deviceSet, filepath.Base(root)), false) if err != nil { return nil, err } From 7d0232a89a647c4df6dd932db9b947df946bfafe Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 6 Sep 2013 16:15:44 +0200 Subject: [PATCH 081/301] Limit the amount of prints during normal runs This removes some Debugf() calls and chages some direct prints to Debugf(). This means we don't get a bunch of spew when running the tests. Upstream-commit: 6094257b28f2e4b5e1a6616c77961b5cec0c9195 Component: engine --- components/engine/devmapper/deviceset_devmapper.go | 3 ++- components/engine/image.go | 10 +++------- components/engine/runtime.go | 6 +++--- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index 4544215b63..d163609a7f 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -1,6 +1,7 @@ package devmapper import ( + "github.com/dotcloud/docker/utils" "encoding/json" "fmt" "io" @@ -184,7 +185,7 @@ func (devices *DeviceSetDM) ensureImage(name string, size int64) (string, error) } func (devices *DeviceSetDM) createPool(dataFile *os.File, metadataFile *os.File) error { - log.Printf("Activating device-mapper pool %s", devices.getPoolName()) + utils.Debugf("Activating device-mapper pool %s", devices.getPoolName()) task, err := devices.createTask(DeviceCreate, devices.getPoolName()) if task == nil { return err diff --git a/components/engine/image.go b/components/engine/image.go index 546c54a577..081d28249a 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -339,7 +339,7 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { mounted, err := Mounted(mountDir) if err == nil && mounted { - log.Printf("Image %s is unexpectedly mounted, unmounting...", image.ID) + utils.Debugf("Image %s is unexpectedly mounted, unmounting...", image.ID) err = syscall.Unmount(mountDir, 0) if err != nil { return err @@ -347,21 +347,19 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { } if devices.HasDevice(image.ID) { - log.Printf("Found non-initialized demove-mapper device for image %s, removing", image.ID) + utils.Debugf("Found non-initialized demove-mapper device for image %s, removing", image.ID) err = devices.RemoveDevice(image.ID) if err != nil { return err } } - log.Printf("Creating device-mapper device for image id %s", image.ID) - + utils.Debugf("Creating device-mapper device for image id %s", image.ID) err = devices.AddDevice(image.ID, image.Parent) if err != nil { return err } - utils.Debugf("Mounting device %s at %s for image setup", image.ID, mountDir) err = devices.MountDevice(image.ID, mountDir) if err != nil { _ = devices.RemoveDevice(image.ID) @@ -375,14 +373,12 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { return err } - utils.Debugf("Applying layer %s at %s", image.ID, mountDir) err = image.applyLayer(layerPath(root), mountDir) if err != nil { _ = devices.RemoveDevice(image.ID) return err } - utils.Debugf("Unmounting %s", mountDir) err = syscall.Unmount(mountDir, 0) if err != nil { _ = devices.RemoveDevice(image.ID) diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 7d1c10a279..60046e29b0 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -107,15 +107,15 @@ func (runtime *Runtime) GetMountMethod() MountMethod { if runtime.mountMethod == MountMethodNone { // Try to automatically pick a method if hasFilesystemSupport("aufs") { - log.Printf("Using AUFS backend.") + utils.Debugf("Using AUFS backend.") runtime.mountMethod = MountMethodAUFS } else { _ = exec.Command("modprobe", "aufs").Run() if hasFilesystemSupport("aufs") { - log.Printf("Using AUFS backend.") + utils.Debugf("Using AUFS backend.") runtime.mountMethod = MountMethodAUFS } else { - log.Printf("Using device-mapper backend.") + utils.Debugf("Using device-mapper backend.") runtime.mountMethod = MountMethodDeviceMapper } } From 70d3d136226c37690a9a7e1ce47680a1f4a95974 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 10 Sep 2013 21:04:25 +0200 Subject: [PATCH 082/301] Allow specifying the docker client path in _DOCKER_INIT_PATH I currently need this to get the tests running, otherwise it will mount the docker.test binary inside the containers, which doesn't work due to the libdevmapper.so dependency. Upstream-commit: 76a2ab6e34e6cdd66899815aeeaac2048e5bafce Component: engine --- components/engine/runtime.go | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 60046e29b0..2b776a5e01 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -52,16 +52,21 @@ type Runtime struct { var sysInitPath string func init() { - selfPath := utils.SelfPath() - - // If we have a separate docker-init, use that, otherwise use the - // main docker binary - dir := filepath.Dir(selfPath) - dockerInitPath := filepath.Join(dir, "docker-init") - if _, err := os.Stat(dockerInitPath); err != nil { - sysInitPath = selfPath + env := os.Getenv("_DOCKER_INIT_PATH") + if env != "" { + sysInitPath = env } else { - sysInitPath = dockerInitPath + selfPath := utils.SelfPath() + + // If we have a separate docker-init, use that, otherwise use the + // main docker binary + dir := filepath.Dir(selfPath) + dockerInitPath := filepath.Join(dir, "docker-init") + if _, err := os.Stat(dockerInitPath); err != nil { + sysInitPath = selfPath + } else { + sysInitPath = dockerInitPath + } } } From 7bed559f55f753de7a0a3c335ff38863f6c3321f Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 11:46:16 +0200 Subject: [PATCH 083/301] tests: Store the loopback images for test outside unit-tests This directory is copied to each test prefix which is really slow with the large loopback mounts. Upstream-commit: 7fb60caa5d34f8fc0abeae2ce7841000665414b0 Component: engine --- components/engine/runtime_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index 5fa6c46bfe..6e5ca00518 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -23,6 +23,7 @@ const ( unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 unitTestNetworkBridge = "testdockbr0" unitTestStoreBase = "/var/lib/docker/unit-tests" + unitTestStoreDevicesBase = "/var/lib/docker/unit-tests-devices" testDaemonAddr = "127.0.0.1:4270" testDaemonProto = "tcp" ) @@ -88,7 +89,7 @@ func init() { NetworkBridgeIface = unitTestNetworkBridge // Make it our Store root - if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreBase), false); err != nil { + if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreDevicesBase), false); err != nil { panic(err) } else { globalRuntime = runtime From 16f68cf6d5d316a2da81b75de6f70320c422defd Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 12:06:55 +0200 Subject: [PATCH 084/301] Always start tests from a clean set of loopback images This way we don't get any issues with leftovers Upstream-commit: d951911b23b26cda087e823f203414a0ad184276 Component: engine --- components/engine/runtime_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index 6e5ca00518..5bf9af5fdb 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -88,6 +88,12 @@ func init() { NetworkBridgeIface = unitTestNetworkBridge + // Always start from a clean set of loopback mounts + err := os.RemoveAll(unitTestStoreDevicesBase) + if err != nil { + panic(err) + } + // Make it our Store root if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreDevicesBase), false); err != nil { panic(err) From 12fbeaaae6d8c0ed394c3fd601dd086bc71da075 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 12:39:42 +0200 Subject: [PATCH 085/301] DeviceSet: Add UnmountDevice() Right now this does nothing but add a new layer, but it means that all DeviceMounts are paired with DeviceUnmounts so that we can track (and cleanup) active mounts. Upstream-commit: 9e64ebb29549db19a84b8cb514bea60c26184779 Component: engine --- components/engine/deviceset.go | 5 +++++ .../engine/devmapper/deviceset_devmapper.go | 12 +++++++++++- components/engine/image.go | 15 ++++++++------- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/components/engine/deviceset.go b/components/engine/deviceset.go index 2caaf153b2..95a3d48f27 100644 --- a/components/engine/deviceset.go +++ b/components/engine/deviceset.go @@ -6,6 +6,7 @@ type DeviceSet interface { DeactivateDevice(hash string) error RemoveDevice(hash string) error MountDevice(hash, path string) error + UnmountDevice(hash, path string) error HasDevice(hash string) bool HasInitializedDevice(hash string) bool } @@ -43,6 +44,10 @@ func (wrapper *DeviceSetWrapper) MountDevice(hash, path string) error { return wrapper.wrapped.MountDevice(wrapper.wrap(hash), path) } +func (wrapper *DeviceSetWrapper) UnmountDevice(hash, path string) error { + return wrapper.wrapped.UnmountDevice(wrapper.wrap(hash), path) +} + func (wrapper *DeviceSetWrapper) HasDevice(hash string) bool { return wrapper.wrapped.HasDevice(wrapper.wrap(hash)) } diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index d163609a7f..e31515042c 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -637,7 +637,7 @@ func (devices *DeviceSetDM) setupBaseImage() error { return err } - err = syscall.Unmount(tmpDir, 0) + err = devices.UnmountDevice("", tmpDir) if err != nil { return err } @@ -840,6 +840,16 @@ func (devices *DeviceSetDM) MountDevice(hash, path string) error { return nil } +func (devices *DeviceSetDM) UnmountDevice(hash, path string) error { + err := syscall.Unmount(path, 0) + if err != nil { + return err + } + + return nil +} + + func (devices *DeviceSetDM) HasDevice(hash string) bool { if err := devices.ensureInit(); err != nil { return false diff --git a/components/engine/image.go b/components/engine/image.go index 081d28249a..da03fb8ff1 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -379,7 +379,7 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { return err } - err = syscall.Unmount(mountDir, 0) + err = devices.UnmountDevice(image.ID, mountDir) if err != nil { _ = devices.RemoveDevice(image.ID) return err @@ -467,16 +467,17 @@ func (image *Image) Unmount(runtime *Runtime, root string, id string) error { return Unmount(root) case MountMethodDeviceMapper: - err := syscall.Unmount(root, 0) - if err != nil { - return err - } - // Try to deactivate the device as generally there is no use for it anymore devices, err := runtime.GetDeviceSet() if err != nil { return err; } + + err = devices.UnmountDevice(id, root) + if err != nil { + return err + } + return devices.DeactivateDevice(id) } return nil @@ -509,7 +510,7 @@ func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, er } changes, err := ChangesDirs(root, rw) - _ = syscall.Unmount(rw, 0) + _ = devices.UnmountDevice(image.ID, rw) if err != nil { return nil, err } From 89fbc4edd272515b4cc6df647b309f2633683c2e Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 13:47:29 +0200 Subject: [PATCH 086/301] deviceset: Cleanup device sets on test end We unmount all mounts and deactivate all device mapper devices to make sure we're left with no leftovers after the test. Upstream-commit: ed741f7b27b1b1cf5b6f8917551ce86bc39e9c78 Component: engine --- components/engine/deviceset.go | 5 ++ .../engine/devmapper/deviceset_devmapper.go | 54 +++++++++++++++++-- components/engine/runtime_test.go | 7 +++ components/engine/z_final_test.go | 2 +- 4 files changed, 63 insertions(+), 5 deletions(-) diff --git a/components/engine/deviceset.go b/components/engine/deviceset.go index 95a3d48f27..5dd608361f 100644 --- a/components/engine/deviceset.go +++ b/components/engine/deviceset.go @@ -9,6 +9,7 @@ type DeviceSet interface { UnmountDevice(hash, path string) error HasDevice(hash string) bool HasInitializedDevice(hash string) bool + Shutdown() error } type DeviceSetWrapper struct { @@ -36,6 +37,10 @@ func (wrapper *DeviceSetWrapper) DeactivateDevice(hash string) error { return wrapper.wrapped.DeactivateDevice(wrapper.wrap(hash)) } +func (wrapper *DeviceSetWrapper) Shutdown() error { + return nil +} + func (wrapper *DeviceSetWrapper) RemoveDevice(hash string) error { return wrapper.wrapped.RemoveDevice(wrapper.wrap(hash)) } diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index e31515042c..4fcfac4464 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -40,6 +40,7 @@ type DeviceSetDM struct { TransactionId uint64 NewTransactionId uint64 nextFreeDevice int + activeMounts map[string]int } func getDevName(name string) string { @@ -348,8 +349,8 @@ func (devices *DeviceSetDM) deleteDevice(deviceId int) error { return nil } -func (devices *DeviceSetDM) removeDevice(info *DevInfo) error { - task, err := devices.createTask(DeviceRemove, info.Name()) +func (devices *DeviceSetDM) removeDevice(name string) error { + task, err := devices.createTask(DeviceRemove, name) if task == nil { return err } @@ -763,7 +764,7 @@ func (devices *DeviceSetDM) RemoveDevice(hash string) error { devinfo, _ := devices.getInfo(info.Name()) if devinfo != nil && devinfo.Exists != 0 { - err := devices.removeDevice(info) + err := devices.removeDevice(info.Name()) if err != nil { return err } @@ -809,7 +810,7 @@ func (devices *DeviceSetDM) DeactivateDevice(hash string) error { return err } if devinfo.Exists != 0 { - err := devices.removeDevice(info) + err := devices.removeDevice(info.Name()) if err != nil { return err } @@ -818,6 +819,39 @@ func (devices *DeviceSetDM) DeactivateDevice(hash string) error { return nil } +func (devices *DeviceSetDM) Shutdown() error { + if !devices.initialized { + return nil + } + + for path, count := range devices.activeMounts { + for i := count; i > 0; i-- { + err := syscall.Unmount(path, 0) + if err != nil { + fmt.Printf("Shutdown unmounting %s, error: %s\n", path, err) + } + } + delete(devices.activeMounts, path) + } + + for _, d := range devices.Devices { + if err := devices.DeactivateDevice(d.Hash); err != nil { + fmt.Printf("Shutdown deactivate %s , error: %s\n", d.Hash, err) + } + } + + + pool := devices.getPoolDevName() + devinfo, err := devices.getInfo(pool) + if err == nil && devinfo.Exists != 0 { + if err := devices.removeDevice(pool); err != nil { + fmt.Printf("Shutdown deactivate %s , error: %s\n", pool, err) + } + } + + return nil +} + func (devices *DeviceSetDM) MountDevice(hash, path string) error { if err := devices.ensureInit(); err != nil { return err @@ -837,6 +871,10 @@ func (devices *DeviceSetDM) MountDevice(hash, path string) error { if err != nil { return err } + + count := devices.activeMounts[path] + devices.activeMounts[path] = count + 1 + return nil } @@ -846,6 +884,13 @@ func (devices *DeviceSetDM) UnmountDevice(hash, path string) error { return err } + count := devices.activeMounts[path] + if count > 1 { + devices.activeMounts[path] = count - 1 + } else { + delete(devices.activeMounts, path) + } + return nil } @@ -913,6 +958,7 @@ func NewDeviceSetDM(root string) *DeviceSetDM { devicePrefix: base, } devices.Devices = make(map[string]*DevInfo) + devices.activeMounts = make(map[string]int) return devices } diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index 5bf9af5fdb..ccdf4d9563 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -64,6 +64,13 @@ func cleanup(runtime *Runtime) error { return nil } +func cleanupLast(runtime *Runtime) error { + cleanup(runtime) + runtime.deviceSet.Shutdown() + return nil +} + + func layerArchive(tarfile string) (io.Reader, error) { // FIXME: need to close f somewhere f, err := os.Open(tarfile) diff --git a/components/engine/z_final_test.go b/components/engine/z_final_test.go index 08a180baaf..c52f87cddb 100644 --- a/components/engine/z_final_test.go +++ b/components/engine/z_final_test.go @@ -11,7 +11,7 @@ func displayFdGoroutines(t *testing.T) { } func TestFinal(t *testing.T) { - cleanup(globalRuntime) + cleanupLast(globalRuntime) t.Logf("Start Fds: %d, Start Goroutines: %d", startFds, startGoroutines) displayFdGoroutines(t) } From eb4c81b5cf72a9d7ca21bdf51eab24b75235e363 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 13:48:58 +0200 Subject: [PATCH 087/301] graph test: Unmount image via image.Unmount() This helps us track the unmount Upstream-commit: 0d7ab8db03814bac19fe076a0d53b6d423616bc7 Component: engine --- components/engine/graph_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/graph_test.go b/components/engine/graph_test.go index d89e6efe9d..ecb5ffb34e 100644 --- a/components/engine/graph_test.go +++ b/components/engine/graph_test.go @@ -152,7 +152,7 @@ func TestMount(t *testing.T) { } // FIXME: test for mount contents defer func() { - if err := Unmount(rootfs); err != nil { + if err := image.Unmount(runtime, rootfs, "testing"); err != nil { t.Error(err) } }() From 0453049ffb3818d960fb2d0264a548f6c839371b Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 14:18:20 +0200 Subject: [PATCH 088/301] Runtime: Only remove device on destroy if it exists Upstream-commit: 5f8e24f8428bc5fb7076ec757ded44f965c48888 Component: engine --- components/engine/runtime.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 2b776a5e01..3bbed75295 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -288,7 +288,7 @@ func (runtime *Runtime) Destroy(container *Container) error { if err := os.RemoveAll(container.root); err != nil { return fmt.Errorf("Unable to remove filesystem for %v: %v", container.ID, err) } - if runtime.GetMountMethod() == MountMethodDeviceMapper { + if runtime.GetMountMethod() == MountMethodDeviceMapper && runtime.deviceSet.HasDevice(container.ID) { if err := runtime.deviceSet.RemoveDevice(container.ID); err != nil { return fmt.Errorf("Unable to remove device for %v: %v", container.ID, err) } From 4cb2cd0d2334ced0e1e5b347f72ac6e35522f23e Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 14:44:19 +0200 Subject: [PATCH 089/301] api_test: Fix PostContainersCreate We can't look for the created file in the rwpath, because that doesn't exist in the device-mapper world, instead look in the RootfsPath. Upstream-commit: 07227866006ea75a0c492814b808fdbb672431ee Component: engine --- components/engine/api_test.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/components/engine/api_test.go b/components/engine/api_test.go index bcf662e9e7..ae8602882f 100644 --- a/components/engine/api_test.go +++ b/components/engine/api_test.go @@ -646,13 +646,21 @@ func TestPostContainersCreate(t *testing.T) { t.Fatal(err) } - if _, err := os.Stat(path.Join(container.rwPath(), "test")); err != nil { + if err := container.EnsureMounted(); err != nil { + t.Fatalf("Unable to mount container: %s", err) + } + + if _, err := os.Stat(path.Join(container.RootfsPath(), "test")); err != nil { if os.IsNotExist(err) { utils.Debugf("Err: %s", err) t.Fatalf("The test file has not been created") } t.Fatal(err) } + + if err := container.Unmount(); err != nil { + t.Fatalf("Unable to unmount container: %s", err) + } } func TestPostContainersKill(t *testing.T) { From f3c281436d5aef64203e5eed20e1277bb0bf14ac Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 15:56:59 +0200 Subject: [PATCH 090/301] Container: Inject into the mount, not the rwPath For device-mapper setups we can't just push the file into the rwPath. Upstream-commit: c1388010731cac1c8ff5159bd72b926545a64e58 Component: engine --- components/engine/container.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/components/engine/container.go b/components/engine/container.go index cd143f7236..002e2eb31e 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -297,12 +297,17 @@ func (settings *NetworkSettings) PortMappingAPI() []APIPort { // Inject the io.Reader at the given path. Note: do not close the reader func (container *Container) Inject(file io.Reader, pth string) error { - // Make sure the directory exists - if err := os.MkdirAll(path.Join(container.rwPath(), path.Dir(pth)), 0755); err != nil { + if err := container.EnsureMounted(); err != nil { return err } + + // Make sure the directory exists + if err := os.MkdirAll(path.Join(container.RootfsPath(), path.Dir(pth)), 0755); err != nil { + return err + } + // FIXME: Handle permissions/already existing dest - dest, err := os.Create(path.Join(container.rwPath(), pth)) + dest, err := os.Create(path.Join(container.RootfsPath(), pth)) if err != nil { return err } From 95fd1df21e288ce69759dda731ff89a92faef76f Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 12 Sep 2013 15:17:39 +0200 Subject: [PATCH 091/301] Utils: Add ShellQuoteArguments Upstream-commit: 145024c6ccc7bff1eda632823702f91899667ed3 Component: engine --- components/engine/utils/utils.go | 33 ++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/components/engine/utils/utils.go b/components/engine/utils/utils.go index d417690c0c..94aa0dc902 100644 --- a/components/engine/utils/utils.go +++ b/components/engine/utils/utils.go @@ -1021,3 +1021,36 @@ type StatusError struct { func (e *StatusError) Error() string { return fmt.Sprintf("Status: %d", e.Status) } + +func quote(word string, buf *bytes.Buffer) { + // Bail out early for "simple" strings + if word != "" && !strings.ContainsAny(word, "\\'\"`${[|&;<>()~*?! \t\n") { + buf.WriteString(word) + return + } + + buf.WriteString("''") + + for i := 0; i < len(word); i++ { + b := word[i] + if b == '\'' { + // Replace literal ' with a close ', a \', and a open ' + buf.WriteString("'\\''") + } else { + buf.WriteByte(b) + } + } + + buf.WriteString("''") +} + +func ShellQuoteArguments(args []string) string { + var buf bytes.Buffer + for i, arg := range args { + if i != 0 { + buf.WriteByte(' ') + } + quote(arg, &buf) + } + return buf.String() +} From 1d1c89100331a95ca0f812b47ea920a70f3ecf09 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 12 Sep 2013 15:18:11 +0200 Subject: [PATCH 092/301] lxc: Work around lxc-start need for private mounts lxc-start requires / to be mounted private, otherwise the changes it does inside the container (both mounts and unmounts) will propagate out to the host. We work around this by starting up lxc-start in its own namespace where we set / to rprivate. Unfortunately go can't really execute any code between clone and exec, so we can't do this in a nice way. Instead we have a horrible hack that use the unshare command, the shell and the mount command... Upstream-commit: 429587779a95a4d38ec9cd66202de9729c320ef8 Component: engine --- components/engine/container.go | 17 ++++++++++++++++- components/engine/utils.go | 15 +++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/components/engine/container.go b/components/engine/container.go index 002e2eb31e..35060e873e 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -748,6 +748,7 @@ func (container *Container) Start(hostConfig *HostConfig) error { } params := []string{ + "lxc-start", "-n", container.ID, "-f", container.lxcConfigPath(), "--", @@ -796,7 +797,21 @@ func (container *Container) Start(hostConfig *HostConfig) error { params = append(params, "--", container.Path) params = append(params, container.Args...) - container.cmd = exec.Command("lxc-start", params...) + if RootIsShared() { + // lxc-start really needs / to be private, or all kinds of stuff break + // What we really want is to clone into a new namespace and then + // mount / MS_REC|MS_PRIVATE, but since we can't really clone or fork + // without exec in go we have to do this horrible shell hack... + shellString := + "mount --make-rprivate /; exec " + + utils.ShellQuoteArguments(params) + + params = []string{ + "unshare", "-m", "--", "/bin/sh", "-c", shellString, + } + } + + container.cmd = exec.Command(params[0], params[1:]...) // Setup logging of stdout and stderr to disk if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil { diff --git a/components/engine/utils.go b/components/engine/utils.go index aed8ffdd76..babca65bf1 100644 --- a/components/engine/utils.go +++ b/components/engine/utils.go @@ -2,6 +2,7 @@ package docker import ( "fmt" + "io/ioutil" "strings" ) @@ -167,3 +168,17 @@ func parseLxcOpt(opt string) (string, string, error) { } return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil } + +func RootIsShared() bool { + if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil { + for _, line := range strings.Split(string(data), "\n") { + cols := strings.Split(line, " ") + if cols[3] == "/" && cols[4] == "/" { + return strings.HasPrefix(cols[6], "shared") + } + } + } + + // No idea, probably safe to assume so + return true +} From 74b00e51f446bd5b1c01b8c4c8d410d5572492e9 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 12 Sep 2013 20:30:55 +0200 Subject: [PATCH 093/301] devmapper: Move init layer to top rather than bottom The init layer needs to be topmost to make sure certain files are always there (for instance, the ubuntu:12.10 image wrongly has /dev/shm being a symlink to /run/shm, and we need to override that). However, previously the devmapper code implemented the init layer by putting it in the base devmapper device, which meant layers above it could override these files (so that ubuntu:12.10 broke). So, instead we put the base layer in *each* images devmapper device. This is "safe" because we still have the pristine layer data in the layer directory. Also, it means we diff the container against the image with the init layer applied, so it won't show up in diffs/commits. Upstream-commit: fdbc2695fe00d522c5c1a962f9be2f802bf53943 Component: engine --- .../engine/devmapper/deviceset_devmapper.go | 62 ------------------- components/engine/image.go | 17 +++++ 2 files changed, 17 insertions(+), 62 deletions(-) diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index 4fcfac4464..670d7621c4 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -542,45 +542,6 @@ func (devices *DeviceSetDM) loadMetaData() error { return nil } -func (devices *DeviceSetDM) createBaseLayer(dir string) error { - for pth, typ := range map[string]string{ - "/dev/pts": "dir", - "/dev/shm": "dir", - "/proc": "dir", - "/sys": "dir", - "/.dockerinit": "file", - "/etc/resolv.conf": "file", - "/etc/hosts": "file", - "/etc/hostname": "file", - // "var/run": "dir", - // "var/lock": "dir", - } { - if _, err := os.Stat(path.Join(dir, pth)); err != nil { - if os.IsNotExist(err) { - switch typ { - case "dir": - if err := os.MkdirAll(path.Join(dir, pth), 0755); err != nil { - return err - } - case "file": - if err := os.MkdirAll(path.Join(dir, path.Dir(pth)), 0755); err != nil { - return err - } - - if f, err := os.OpenFile(path.Join(dir, pth), os.O_CREATE, 0755); err != nil { - return err - } else { - f.Close() - } - } - } else { - return err - } - } - } - return nil -} - func (devices *DeviceSetDM) setupBaseImage() error { oldInfo := devices.Devices[""] if oldInfo != nil && oldInfo.Initialized { @@ -622,29 +583,6 @@ func (devices *DeviceSetDM) setupBaseImage() error { return err } - tmpDir := path.Join(devices.loopbackDir(), "basefs") - if err = os.MkdirAll(tmpDir, 0700); err != nil && !os.IsExist(err) { - return err - } - - err = devices.MountDevice("", tmpDir) - if err != nil { - return err - } - - err = devices.createBaseLayer(tmpDir) - if err != nil { - _ = syscall.Unmount(tmpDir, 0) - return err - } - - err = devices.UnmountDevice("", tmpDir) - if err != nil { - return err - } - - _ = os.Remove(tmpDir) - info.Initialized = true err = devices.saveMetadata() diff --git a/components/engine/image.go b/components/engine/image.go index da03fb8ff1..1a279654d5 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -379,6 +379,23 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { return err } + // The docker init layer is conceptually above all other layers, so we apply + // it for every image. This is safe because the layer directory is the + // definition of the image, and the device-mapper device is just a cache + // of it instantiated. Diffs/commit compare the container device with the + // image device, which will then *not* pick up the init layer changes as + // part of the container changes + dockerinitLayer, err := image.getDockerInitLayer() + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + err = image.applyLayer(dockerinitLayer, mountDir) + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + err = devices.UnmountDevice(image.ID, mountDir) if err != nil { _ = devices.RemoveDevice(image.ID) From ff5de9f609cc7b05c19e0dcb478b1e5cc37219f2 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 11:08:59 +0200 Subject: [PATCH 094/301] RootIsShared() - Fix array out of bounds error This happened for me on the last (empty) line, but better safe than sorry so we make the check general. Upstream-commit: 7d566b4f761e8942cf9679e96774b320b8496b2f Component: engine --- components/engine/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/utils.go b/components/engine/utils.go index babca65bf1..57737f7b25 100644 --- a/components/engine/utils.go +++ b/components/engine/utils.go @@ -173,7 +173,7 @@ func RootIsShared() bool { if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil { for _, line := range strings.Split(string(data), "\n") { cols := strings.Split(line, " ") - if cols[3] == "/" && cols[4] == "/" { + if len(cols) >= 6 && cols[3] == "/" && cols[4] == "/" { return strings.HasPrefix(cols[6], "shared") } } From ce9d88943672d196e8dddfac69d0abf32bf339ac Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 12:56:58 +0200 Subject: [PATCH 095/301] Change how ChangesDirs() works Rather than scan the files in the old directory twice to detect the deletions we now scan both directories twice and then do all the diffing on the in-memory structure. This is more efficient, but it also lets us diff more complex things later that are not exact on-disk trees. Upstream-commit: 727e7fcccadf1d3e286f5a3c8d1aa388f6b4dab8 Component: engine --- components/engine/changes.go | 201 +++++++++++++++++++++++++---------- 1 file changed, 143 insertions(+), 58 deletions(-) diff --git a/components/engine/changes.go b/components/engine/changes.go index 00c9cc7c77..d1b0a25b0d 100644 --- a/components/engine/changes.go +++ b/components/engine/changes.go @@ -106,50 +106,85 @@ func ChangesAUFS(layers []string, rw string) ([]Change, error) { return changes, nil } -func ChangesDirs(newDir, oldDir string) ([]Change, error) { - var changes []Change - err := filepath.Walk(newDir, func(newPath string, f os.FileInfo, err error) error { - if err != nil { - return err - } +type FileInfo struct { + parent *FileInfo + name string + stat syscall.Stat_t + children map[string]*FileInfo +} - var newStat syscall.Stat_t - err = syscall.Lstat(newPath, &newStat) - if err != nil { - return err - } +func (root *FileInfo) LookUp(path string) *FileInfo { + parent := root + if path == "/" { + return root + } - // Rebase path - relPath, err := filepath.Rel(newDir, newPath) - if err != nil { - return err - } - relPath = filepath.Join("/", relPath) - - // Skip root - if relPath == "/" || relPath == "/.docker-id" { - return nil - } - - change := Change{ - Path: relPath, - } - - oldPath := filepath.Join(oldDir, relPath) - - var oldStat = &syscall.Stat_t{} - err = syscall.Lstat(oldPath, oldStat) - if err != nil { - if !os.IsNotExist(err) { - return err + pathElements := strings.Split(path, "/") + for _, elem := range pathElements { + if elem != "" { + child := parent.children[elem] + if child == nil { + return nil } - oldStat = nil + parent = child } + } + return parent +} - if oldStat == nil { - change.Kind = ChangeAdd - changes = append(changes, change) - } else { +func (info *FileInfo)path() string { + if info.parent == nil { + return "/" + } + return filepath.Join(info.parent.path(), info.name) +} + +func (info *FileInfo)unlink() { + if info.parent != nil { + delete(info.parent.children, info.name) + } +} + +func (info *FileInfo)Remove(path string) bool { + child := info.LookUp(path) + if child != nil { + child.unlink() + return true + } + return false +} + +func (info *FileInfo)isDir() bool { + return info.parent == nil || info.stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR +} + + +func (info *FileInfo)addChanges(oldInfo *FileInfo, changes *[]Change) { + if oldInfo == nil { + // add + change := Change{ + Path: info.path(), + Kind: ChangeAdd, + } + *changes = append(*changes, change) + } + + // We make a copy so we can modify it to detect additions + // also, we only recurse on the old dir if the new info is a directory + // otherwise any previous delete/change is considered recursive + oldChildren := make(map[string]*FileInfo) + if oldInfo != nil && info.isDir() { + for k, v := range oldInfo.children { + oldChildren[k] = v + } + } + + for name, newChild := range info.children { + oldChild, _ := oldChildren[name] + if oldChild != nil { + // change? + oldStat := &oldChild.stat + newStat := &newChild.stat if oldStat.Ino != newStat.Ino || oldStat.Mode != newStat.Mode || oldStat.Uid != newStat.Uid || @@ -159,50 +194,100 @@ func ChangesDirs(newDir, oldDir string) ([]Change, error) { oldStat.Blocks != newStat.Blocks || oldStat.Mtim != newStat.Mtim || oldStat.Ctim != newStat.Ctim { - change.Kind = ChangeModify - changes = append(changes, change) + change := Change{ + Path: newChild.path(), + Kind: ChangeModify, + } + *changes = append(*changes, change) } + + // Remove from copy so we can detect deletions + delete(oldChildren, name) } - return nil - }) - if err != nil { - return nil, err + newChild.addChanges(oldChild, changes) } - err = filepath.Walk(oldDir, func(oldPath string, f os.FileInfo, err error) error { + for _, oldChild := range oldChildren { + // delete + change := Change{ + Path: oldChild.path(), + Kind: ChangeDelete, + } + *changes = append(*changes, change) + } + + +} + +func (info *FileInfo)Changes(oldInfo *FileInfo) []Change { + var changes []Change + + info.addChanges(oldInfo, &changes) + + return changes +} + + +func collectFileInfo(sourceDir string) (*FileInfo, error) { + root := &FileInfo { + name: "/", + children: make(map[string]*FileInfo), + } + + err := filepath.Walk(sourceDir, func(path string, f os.FileInfo, err error) error { if err != nil { return err } // Rebase path - relPath, err := filepath.Rel(oldDir, oldPath) + relPath, err := filepath.Rel(sourceDir, path) if err != nil { return err } relPath = filepath.Join("/", relPath) - // Skip root if relPath == "/" { return nil } - change := Change{ - Path: relPath, + parent := root.LookUp(filepath.Dir(relPath)) + if parent == nil { + return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath) } - newPath := filepath.Join(newDir, relPath) - - var newStat = &syscall.Stat_t{} - err = syscall.Lstat(newPath, newStat) - if err != nil && os.IsNotExist(err) { - change.Kind = ChangeDelete - changes = append(changes, change) + info := &FileInfo { + name: filepath.Base(relPath), + children: make(map[string]*FileInfo), + parent: parent, } + if err := syscall.Lstat(path, &info.stat); err != nil { + return err + } + + parent.children[info.name] = info + return nil }) if err != nil { return nil, err } - return changes, nil + return root, nil +} + +func ChangesDirs(newDir, oldDir string) ([]Change, error) { + oldRoot, err := collectFileInfo(oldDir) + if err != nil { + return nil, err + } + newRoot, err := collectFileInfo(newDir) + if err != nil { + return nil, err + } + + // Ignore changes in .docker-id + _ = newRoot.Remove("/.docker-id") + _ = oldRoot.Remove("/.docker-id") + + return newRoot.Changes(oldRoot), nil } From 84b4aeca212329d415e00ce0a4429c309fc1eac0 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 15:45:58 +0200 Subject: [PATCH 096/301] Image.applyLayer: Be better at creating identical files There are some changes here that make the file metadata better match the layer files: * Set the mode of the file after the chown, as otherwise the per-group/uid specific flags and e.g. sticky bit is lost * Use lchown instead of chown * Delay mtime updates to after all other changes so that later file creation doesn't change the mtime for the parent directory * Use Futimes in combination with O_PATH|O_NOFOLLOW to set mtime on symlinks Upstream-commit: 5d2ace3424516bd7cc8d4a57fcaddd00fa1c4b5d Component: engine --- components/engine/image.go | 55 +++++++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/components/engine/image.go b/components/engine/image.go index 1a279654d5..61ca83b208 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -175,7 +175,13 @@ func (image *Image) TarLayer(compression Compression) (Archive, error) { return Tar(layerPath, compression) } +type TimeUpdate struct { + path string + time []syscall.Timeval +} + func (image *Image) applyLayer(layer, target string) error { + var updateTimes []TimeUpdate oldmask := syscall.Umask(0) defer syscall.Umask(oldmask) err := filepath.Walk(layer, func(srcPath string, f os.FileInfo, err error) error { @@ -249,11 +255,6 @@ func (image *Image) applyLayer(layer, target string) error { if err != nil { return err } - } else if srcStat.Mode&07777 != targetStat.Mode&07777 { - err = syscall.Chmod(targetPath, srcStat.Mode&07777) - if err != nil { - return err - } } } else if srcStat.Mode&syscall.S_IFLNK == syscall.S_IFLNK { // Source is symlink @@ -293,22 +294,52 @@ func (image *Image) applyLayer(layer, target string) error { return fmt.Errorf("Unknown type for file %s", srcPath) } + err = syscall.Lchown(targetPath, int(srcStat.Uid), int(srcStat.Gid)) + if err != nil { + return err + } + if srcStat.Mode&syscall.S_IFLNK != syscall.S_IFLNK { - err = syscall.Chown(targetPath, int(srcStat.Uid), int(srcStat.Gid)) + err = syscall.Chmod(targetPath, srcStat.Mode&07777) if err != nil { return err } - ts := []syscall.Timeval{ - syscall.NsecToTimeval(srcStat.Atim.Nano()), - syscall.NsecToTimeval(srcStat.Mtim.Nano()), - } - syscall.Utimes(targetPath, ts) } + ts := []syscall.Timeval{ + syscall.NsecToTimeval(srcStat.Atim.Nano()), + syscall.NsecToTimeval(srcStat.Mtim.Nano()), + } + + u := TimeUpdate { + path: targetPath, + time: ts, + } + + // Delay time updates until all other changes done, or it is + // overwritten for directories (by child changes) + updateTimes = append(updateTimes, u) } return nil }) - return err + if err != nil { + return err + } + + // We do this in reverse order so that children are updated before parents + for i := len(updateTimes) - 1; i >= 0; i-- { + update := updateTimes[i] + + O_PATH := 010000000 // Not in syscall yet + fd, err := syscall.Open(update.path, syscall.O_RDWR | O_PATH | syscall.O_NOFOLLOW, 0600) + if err != nil { + return err + } + syscall.Futimes(fd, update.time) + _ = syscall.Close(fd) + } + + return nil } func (image *Image) ensureImageDevice(devices DeviceSet) error { From 3953036d64bf862e29dcc90c6af9dc1fce18999f Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 15:49:57 +0200 Subject: [PATCH 097/301] Changes: Better metadata comparison Change the comparison to better handle files that are copied during container creation but not actually changed: * Inode - this will change during a copy * ctime - this will change during a copy (as we can't set it back) * blocksize - this will change for sparse files during copy * size for directories - this can change anytime but doesn't necessarily reflect an actual contents change * Compare mtimes at microsecond precision (as this is what utimes has) Upstream-commit: ad402763e160ded1924c9b154983d81614e90deb Component: engine --- components/engine/changes.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/components/engine/changes.go b/components/engine/changes.go index d1b0a25b0d..53fa2d49b4 100644 --- a/components/engine/changes.go +++ b/components/engine/changes.go @@ -185,15 +185,22 @@ func (info *FileInfo)addChanges(oldInfo *FileInfo, changes *[]Change) { // change? oldStat := &oldChild.stat newStat := &newChild.stat - if oldStat.Ino != newStat.Ino || - oldStat.Mode != newStat.Mode || + // Note: We can't compare inode or ctime or blocksize here, because these change + // when copying a file into a container. However, that is not generally a problem + // because any content change will change mtime, and any status change should + // be visible when actually comparing the stat fields. The only time this + // breaks down is if some code intentionally hides a change by setting + // back mtime + oldMtime := syscall.NsecToTimeval(oldStat.Mtim.Nano()) + newMtime := syscall.NsecToTimeval(oldStat.Mtim.Nano()) + if oldStat.Mode != newStat.Mode || oldStat.Uid != newStat.Uid || oldStat.Gid != newStat.Gid || oldStat.Rdev != newStat.Rdev || - oldStat.Size != newStat.Size || - oldStat.Blocks != newStat.Blocks || - oldStat.Mtim != newStat.Mtim || - oldStat.Ctim != newStat.Ctim { + // Don't look at size for dirs, its not a good measure of change + (oldStat.Size != newStat.Size && oldStat.Mode &syscall.S_IFDIR != syscall.S_IFDIR) || + oldMtime.Sec != newMtime.Sec || + oldMtime.Usec != newMtime.Usec { change := Change{ Path: newChild.path(), Kind: ChangeModify, From 6dae654f9c117525d6c9b0f647df338e1ea85978 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 15:56:06 +0200 Subject: [PATCH 098/301] Add Changes.ChangesLayers() This calculates the difference between a set of layers and a directory tree. Upstream-commit: 60f552cac3d165dbe4c8d65c1ab82d31700dcc5d Component: engine --- components/engine/changes.go | 95 +++++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/components/engine/changes.go b/components/engine/changes.go index 53fa2d49b4..49802d3170 100644 --- a/components/engine/changes.go +++ b/components/engine/changes.go @@ -235,11 +235,88 @@ func (info *FileInfo)Changes(oldInfo *FileInfo) []Change { } -func collectFileInfo(sourceDir string) (*FileInfo, error) { +func newRootFileInfo() *FileInfo { root := &FileInfo { name: "/", children: make(map[string]*FileInfo), } + return root +} + +func applyLayer(root *FileInfo, layer string) error { + err := filepath.Walk(layer, func(layerPath string, f os.FileInfo, err error) error { + if err != nil { + return err + } + + // Skip root + if layerPath == layer { + return nil + } + + // rebase path + relPath, err := filepath.Rel(layer, layerPath) + if err != nil { + return err + } + relPath = filepath.Join("/", relPath) + + // Skip AUFS metadata + if matched, err := filepath.Match("/.wh..wh.*", relPath); err != nil || matched { + if err != nil || !f.IsDir() { + return err + } + return filepath.SkipDir + } + + var layerStat syscall.Stat_t + err = syscall.Lstat(layerPath, &layerStat) + if err != nil { + return err + } + + file := filepath.Base(relPath) + // If there is a whiteout, then the file was removed + if strings.HasPrefix(file, ".wh.") { + originalFile := file[len(".wh."):] + deletePath := filepath.Join(filepath.Dir(relPath), originalFile) + + root.Remove(deletePath) + } else { + // Added or changed file + existing := root.LookUp(relPath) + if existing != nil { + // Changed file + existing.stat = layerStat + if !existing.isDir() { + // Changed from dir to non-dir, delete all previous files + existing.children = make(map[string]*FileInfo) + } + } else { + // Added file + parent := root.LookUp(filepath.Dir(relPath)) + if parent == nil { + return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath) + } + + info := &FileInfo { + name: filepath.Base(relPath), + children: make(map[string]*FileInfo), + parent: parent, + stat: layerStat, + } + + parent.children[info.name] = info + } + } + return nil + }) + return err +} + + +func collectFileInfo(sourceDir string) (*FileInfo, error) { + root := newRootFileInfo() err := filepath.Walk(sourceDir, func(path string, f os.FileInfo, err error) error { if err != nil { @@ -282,6 +359,22 @@ func collectFileInfo(sourceDir string) (*FileInfo, error) { return root, nil } +func ChangesLayers(newDir string, layers []string) ([]Change, error) { + newRoot, err := collectFileInfo(newDir) + if err != nil { + return nil, err + } + oldRoot := newRootFileInfo() + for i := len(layers)-1; i >= 0; i-- { + layer := layers[i] + if err = applyLayer(oldRoot, layer); err != nil { + return nil, err + } + } + + return newRoot.Changes(oldRoot), nil +} + func ChangesDirs(newDir, oldDir string) ([]Change, error) { oldRoot, err := collectFileInfo(oldDir) if err != nil { From 9f8a6e47d8a0a8e1fd7dd48b95ea8ee5720af9d3 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 15:58:14 +0200 Subject: [PATCH 099/301] Add trivial copy-based CoW backend This creates a container by copying the corresponding files from the layers into the containers. This is not gonna be very useful on a developer setup, as there is no copy-on-write or general diskspace sharing. It also makes container instantiation slower. However, it may be useful in deployment where we don't always have a lot of containers running (long-running daemons) and where we don't do a lot of docker commits. Upstream-commit: 43a7d3d0e9e2fafcdc90cb84855e1bb3869d2729 Component: engine --- components/engine/container.go | 6 ++- components/engine/image.go | 68 ++++++++++++++++++++++++++++++++-- components/engine/runtime.go | 2 + 3 files changed, 71 insertions(+), 5 deletions(-) diff --git a/components/engine/container.go b/components/engine/container.go index 35060e873e..e9fb4890b7 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -1209,7 +1209,11 @@ func (container *Container) GetImage() (*Image, error) { } func (container *Container) Mounted() (bool, error) { - return Mounted(container.RootfsPath()) + image, err := container.GetImage() + if err != nil { + return false, err + } + return image.Mounted(container.runtime, container.RootfsPath(), container.rwPath()) } func (container *Container) Unmount() error { diff --git a/components/engine/image.go b/components/engine/image.go index 61ca83b208..2c16aeb6d4 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -442,16 +442,38 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { return nil } +func (image *Image) Mounted(runtime *Runtime, root, rw string) (bool, error) { + method := runtime.GetMountMethod() + if method == MountMethodFilesystem { + if _, err := os.Stat(rw); err != nil { + if os.IsNotExist(err) { + err = nil + } + return false, err + } + mountedPath := path.Join(rw, ".fs-mounted") + if _, err := os.Stat(mountedPath); err != nil { + if os.IsNotExist(err) { + err = nil + } + return false, err + } + return true, nil + } else { + return Mounted(root) + } +} + func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { - if mounted, err := Mounted(root); err != nil { - return err - } else if mounted { + if mounted, _ := image.Mounted(runtime, root, rw); mounted { return fmt.Errorf("%s is already mounted", root) } + // Create the target directories if they don't exist if err := os.Mkdir(root, 0755); err != nil && !os.IsExist(err) { return err } + switch runtime.GetMountMethod() { case MountMethodNone: return fmt.Errorf("No supported Mount implementation") @@ -502,6 +524,29 @@ func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { } } + case MountMethodFilesystem: + if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { + return err + } + + layers, err := image.layers() + if err != nil { + return err + } + + for i := len(layers)-1; i >= 0; i-- { + layer := layers[i] + if err = image.applyLayer(layer, root); err != nil { + return err + } + } + + mountedPath := path.Join(rw, ".fs-mounted") + fo, err := os.Create(mountedPath) + if err != nil { + return err + } + fo.Close() } return nil } @@ -527,7 +572,11 @@ func (image *Image) Unmount(runtime *Runtime, root string, id string) error { } return devices.DeactivateDevice(id) + + case MountMethodFilesystem: + return nil } + return nil } @@ -563,6 +612,17 @@ func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, er return nil, err } return changes, nil + + case MountMethodFilesystem: + layers, err := image.layers() + if err != nil { + return nil, err + } + changes, err := ChangesLayers(root, layers) + if err != nil { + return nil, err + } + return changes, nil } return nil, fmt.Errorf("No supported Changes implementation") @@ -573,7 +633,7 @@ func (image *Image) ExportChanges(runtime *Runtime, root, rw, id string) (Archiv case MountMethodAUFS: return Tar(rw, Uncompressed) - case MountMethodDeviceMapper: + case MountMethodFilesystem, MountMethodDeviceMapper: changes, err := image.Changes(runtime, root, rw, id) if err != nil { return nil, err diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 3bbed75295..28263ad7cb 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -23,6 +23,7 @@ const ( MountMethodNone MountMethod = iota MountMethodAUFS MountMethodDeviceMapper + MountMethodFilesystem ) type Capabilities struct { @@ -124,6 +125,7 @@ func (runtime *Runtime) GetMountMethod() MountMethod { runtime.mountMethod = MountMethodDeviceMapper } } + runtime.mountMethod = MountMethodFilesystem } return runtime.mountMethod From 19c4006853374df3ba879fb6ce4c50ea8d8ee898 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 16:04:07 +0200 Subject: [PATCH 100/301] Remove accidental commit that enabled MountMethodFilesystem Upstream-commit: 91c69fd3532eaa92cfc21c6331699a995fdbe2a6 Component: engine --- components/engine/runtime.go | 1 - 1 file changed, 1 deletion(-) diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 28263ad7cb..10e937e21a 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -125,7 +125,6 @@ func (runtime *Runtime) GetMountMethod() MountMethod { runtime.mountMethod = MountMethodDeviceMapper } } - runtime.mountMethod = MountMethodFilesystem } return runtime.mountMethod From 08576d6eafefdf16578c18e0febddb7f2b665c91 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 16:35:56 +0200 Subject: [PATCH 101/301] Add CopyFile that can use btrfs reflinks if availible Upstream-commit: 86421e8b5e84452d66e6b16b5cb1a5a438f8fa1f Component: engine --- components/engine/utils.go | 43 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/components/engine/utils.go b/components/engine/utils.go index 57737f7b25..44573927a1 100644 --- a/components/engine/utils.go +++ b/components/engine/utils.go @@ -1,9 +1,33 @@ package docker +/* +#include +#include +#include + +// See linux.git/fs/btrfs/ioctl.h +#define BTRFS_IOCTL_MAGIC 0x94 +#define BTRFS_IOC_CLONE _IOW(BTRFS_IOCTL_MAGIC, 9, int) + +int +btrfs_reflink(int fd_out, int fd_in) +{ + int res; + res = ioctl(fd_out, BTRFS_IOC_CLONE, fd_in); + if (res < 0) + return errno; + return 0; +} + +*/ +import "C" import ( "fmt" + "io" "io/ioutil" + "os" "strings" + "syscall" ) // Compare two Config struct. Do not compare the "Image" nor "Hostname" fields @@ -182,3 +206,22 @@ func RootIsShared() bool { // No idea, probably safe to assume so return true } + +func BtrfsReflink(fd_out, fd_in uintptr) error { + res := C.btrfs_reflink(C.int(fd_out), C.int(fd_in)) + if res != 0 { + return syscall.Errno(res) + } + return nil +} + +func CopyFile(dstFile, srcFile *os.File) error { + err := BtrfsReflink(dstFile.Fd(), srcFile.Fd()) + if err == nil { + return nil + } + + // Fall back to normal copy + _, err = io.Copy(dstFile, srcFile) + return err +} From 91d411052afd190f156da86a8bb2570d2540274c Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 16:36:32 +0200 Subject: [PATCH 102/301] applyLayer() use btrfs reflinks if availible We use the new file copy helper which uses btrfs reflinks if availible. Upstream-commit: 1a082ed245aaf55cc92e884c988865b195ea0cb7 Component: engine --- components/engine/image.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/components/engine/image.go b/components/engine/image.go index 2c16aeb6d4..7819873757 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -284,12 +284,16 @@ func (image *Image) applyLayer(layer, target string) error { } dstFile := os.NewFile(uintptr(fd), targetPath) srcFile, err := os.Open(srcPath) - _, err = io.Copy(dstFile, srcFile) + if err != nil { + _ = dstFile.Close() + return err + } + err = CopyFile(dstFile, srcFile) + _ = dstFile.Close() + _ = srcFile.Close() if err != nil { return err } - _ = srcFile.Close() - _ = dstFile.Close() } else { return fmt.Errorf("Unknown type for file %s", srcPath) } From 2af8abef3d232213cc6a5aaca90e7c96a1f422b7 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 14:39:47 +0200 Subject: [PATCH 103/301] devmapper: Fix loopback mount code Typo in the loop-control code made it always fall back to the old method of opening loopback devices. Upstream-commit: bbc72c85f7733fefda499cb0e96f1dda2ce90625 Component: engine --- components/engine/devmapper/devmapper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/devmapper/devmapper.go b/components/engine/devmapper/devmapper.go index f007091827..75742628a2 100644 --- a/components/engine/devmapper/devmapper.go +++ b/components/engine/devmapper/devmapper.go @@ -27,7 +27,7 @@ attach_loop_device(const char *filename, int *loop_fd_out) start_index = 0; fd = open("/dev/loop-control", O_RDONLY); - if (fd == 0) { + if (fd >= 0) { start_index = ioctl(fd, LOOP_CTL_GET_FREE); close(fd); From 6101a0a475e062e4d537bf8880f4b3eb2b9d1cfe Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 14:57:22 +0200 Subject: [PATCH 104/301] image: Handle systems that don't support O_PATH when updating timestamp Older kernel can't handle O_PATH in open() so this will fail on dirs and symlinks. For dirs wa can fallback to the normal Utimes, but for symlinks there is not much to do but ignore their timestamps. Upstream-commit: 2c71710b74829787a0c78f7e02f45d31935a996f Component: engine --- components/engine/image.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/components/engine/image.go b/components/engine/image.go index 7819873757..b3e8de3e55 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -336,11 +336,21 @@ func (image *Image) applyLayer(layer, target string) error { O_PATH := 010000000 // Not in syscall yet fd, err := syscall.Open(update.path, syscall.O_RDWR | O_PATH | syscall.O_NOFOLLOW, 0600) - if err != nil { - return err + if err == syscall.EISDIR || err == syscall.ELOOP { + // O_PATH not supported, use Utimes except on symlinks where Utimes doesn't work + if err != syscall.ELOOP { + err = syscall.Utimes(update.path, update.time) + if err != nil { + return err + } + } + } else { + if err != nil { + return err + } + syscall.Futimes(fd, update.time) + _ = syscall.Close(fd) } - syscall.Futimes(fd, update.time) - _ = syscall.Close(fd) } return nil From 17aec56f2ad6f04a3ee0d58a725651fee43a394b Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 14:59:27 +0200 Subject: [PATCH 105/301] Image: unmount device before removing it on failures If we don't do this the remove will fail due to EBUSY Upstream-commit: ecdbdfdaea5497c34a0299e7efd97b8a44bd7314 Component: engine --- components/engine/image.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/engine/image.go b/components/engine/image.go index b3e8de3e55..16abf9eef0 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -414,6 +414,7 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { err = ioutil.WriteFile(path.Join(mountDir, ".docker-id"), []byte(image.ID), 0600) if err != nil { + _ = devices.UnmountDevice(image.ID, mountDir) _ = devices.RemoveDevice(image.ID) return err } @@ -432,11 +433,13 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { // part of the container changes dockerinitLayer, err := image.getDockerInitLayer() if err != nil { + _ = devices.UnmountDevice(image.ID, mountDir) _ = devices.RemoveDevice(image.ID) return err } err = image.applyLayer(dockerinitLayer, mountDir) if err != nil { + _ = devices.UnmountDevice(image.ID, mountDir) _ = devices.RemoveDevice(image.ID) return err } From c5a71d7b11782adcfaedb684d8fa020f91fe77cb Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 15:44:43 +0200 Subject: [PATCH 106/301] ShellQuoteArguments: Fix quoting This accidentally used two quotes to start/end each quoted string. Upstream-commit: a7e876e3571965de64615d2012e63fae9c3ebbf8 Component: engine --- components/engine/utils/utils.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/utils/utils.go b/components/engine/utils/utils.go index 94aa0dc902..6daac53a03 100644 --- a/components/engine/utils/utils.go +++ b/components/engine/utils/utils.go @@ -1029,7 +1029,7 @@ func quote(word string, buf *bytes.Buffer) { return } - buf.WriteString("''") + buf.WriteString("'") for i := 0; i < len(word); i++ { b := word[i] @@ -1041,7 +1041,7 @@ func quote(word string, buf *bytes.Buffer) { } } - buf.WriteString("''") + buf.WriteString("'") } func ShellQuoteArguments(args []string) string { From 5fc36fa5bfe3ec07c5d2860c27836cf553523f80 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 19:20:05 +0200 Subject: [PATCH 107/301] runtime test: Ensure all containers are unmounted at nuke() Otherwise we may leave around e.g. devmapper mounts Upstream-commit: b0a9147fd5f0197931b73e18163a12b04ad8e826 Component: engine --- components/engine/container.go | 9 +++++++++ components/engine/runtime_test.go | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/components/engine/container.go b/components/engine/container.go index e9fb4890b7..461004767d 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -1181,6 +1181,15 @@ func (container *Container) EnsureMounted() error { return container.Mount() } +func (container *Container) EnsureUnmounted() error { + if mounted, err := container.Mounted(); err != nil { + return err + } else if !mounted { + return nil + } + return container.Unmount() +} + func (container *Container) Mount() error { image, err := container.GetImage() if err != nil { diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index ccdf4d9563..684ca005cd 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -44,6 +44,10 @@ func nuke(runtime *Runtime) error { }(container) } wg.Wait() + + for _, container := range runtime.List() { + container.EnsureUnmounted() + } return os.RemoveAll(runtime.root) } From 890bafe58d78a576385b3f96d0f44b56bcb59883 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 19:22:23 +0200 Subject: [PATCH 108/301] image: Unmount before removing device in error paths The device remove fails unless we unmount first Upstream-commit: 6f57e8025a0a03da8052edc30157f7677f65f208 Component: engine --- components/engine/image.go | 1 + 1 file changed, 1 insertion(+) diff --git a/components/engine/image.go b/components/engine/image.go index 16abf9eef0..f495e8e8fc 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -421,6 +421,7 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { err = image.applyLayer(layerPath(root), mountDir) if err != nil { + _ = devices.UnmountDevice(image.ID, mountDir) _ = devices.RemoveDevice(image.ID) return err } From 64c3059ad85f2a1470c8895e423b2a835027a13b Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 20:38:06 +0200 Subject: [PATCH 109/301] Add DeviceSet.HasActivatedDevice() This lets you see if a device has been activated Upstream-commit: 6ec5585501c6029ef10a474597503511d5074e10 Component: engine --- components/engine/deviceset.go | 5 +++++ .../engine/devmapper/deviceset_devmapper.go | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/components/engine/deviceset.go b/components/engine/deviceset.go index 5dd608361f..cf422acb3a 100644 --- a/components/engine/deviceset.go +++ b/components/engine/deviceset.go @@ -9,6 +9,7 @@ type DeviceSet interface { UnmountDevice(hash, path string) error HasDevice(hash string) bool HasInitializedDevice(hash string) bool + HasActivatedDevice(hash string) bool Shutdown() error } @@ -61,6 +62,10 @@ func (wrapper *DeviceSetWrapper) HasInitializedDevice(hash string) bool { return wrapper.wrapped.HasInitializedDevice(wrapper.wrap(hash)) } +func (wrapper *DeviceSetWrapper) HasActivatedDevice(hash string) bool { + return wrapper.wrapped.HasActivatedDevice(wrapper.wrap(hash)) +} + func NewDeviceSetWrapper(wrapped DeviceSet, prefix string) DeviceSet { wrapper := &DeviceSetWrapper{ wrapped: wrapped, diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index 670d7621c4..bbf1fa6adc 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -851,6 +851,23 @@ func (devices *DeviceSetDM) HasInitializedDevice(hash string) bool { return info != nil && info.Initialized } +func (devices *DeviceSetDM) HasActivatedDevice(hash string) bool { + if err := devices.ensureInit(); err != nil { + return false + } + + info := devices.Devices[hash] + if info == nil { + return false + } + name := info.Name() + devinfo, _ := devices.getInfo(name) + if devinfo != nil && devinfo.Exists != 0 { + return true + } + return false +} + func (devices *DeviceSetDM) SetInitialized(hash string) error { if err := devices.ensureInit(); err != nil { return err From 1461717d2e9b080131252030e128bc0a9c78e932 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 19:23:35 +0200 Subject: [PATCH 110/301] Image.Changes: Deactivate image device after unmounting it There is no need to keep the image device around if we were the onces creating the device. Upstream-commit: 071cc18b58408eaa71575e0233a056475196b199 Component: engine --- components/engine/image.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/engine/image.go b/components/engine/image.go index f495e8e8fc..ff8b836c39 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -617,6 +617,8 @@ func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, er return nil, err } + wasActivated := devices.HasActivatedDevice(image.ID) + // We re-use rw for the temporary mount of the base image as its // not used by device-mapper otherwise err = devices.MountDevice(image.ID, rw) @@ -626,6 +628,9 @@ func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, er changes, err := ChangesDirs(root, rw) _ = devices.UnmountDevice(image.ID, rw) + if !wasActivated { + _ = devices.DeactivateDevice(image.ID) + } if err != nil { return nil, err } From 1d7e574dd4113db435333d55332b22dcbfceebce Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 20:32:11 +0200 Subject: [PATCH 111/301] Tests: Clean up any old devmapper leftovers before starting tests Upstream-commit: be6fef02544d9bc31321b2a21d039e261dc02bd8 Component: engine --- components/engine/devmapper/devmapper.go | 17 ++++++++++++++ components/engine/runtime_test.go | 29 ++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/components/engine/devmapper/devmapper.go b/components/engine/devmapper/devmapper.go index 75742628a2..8458cb3ed9 100644 --- a/components/engine/devmapper/devmapper.go +++ b/components/engine/devmapper/devmapper.go @@ -347,3 +347,20 @@ func UdevWait(cookie uint32) error { func LogInitVerbose(level int) { C.dm_log_init_verbose(C.int(level)) } + +// Useful helper for cleanup +func RemoveDevice(name string) error { + task := TaskCreate(DeviceRemove) + if task == nil { + return fmt.Errorf("Can't create task of type DeviceRemove") + } + err := task.SetName(name) + if err != nil { + return fmt.Errorf("Can't set task name %s", name) + } + err = task.Run() + if err != nil { + return fmt.Errorf("Error running removeDevice") + } + return nil +} diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index 684ca005cd..bcbdf3f384 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -6,6 +6,7 @@ import ( "github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/devmapper" "io" + "io/ioutil" "log" "net" "os" @@ -84,6 +85,32 @@ func layerArchive(tarfile string) (io.Reader, error) { return f, nil } +// Remove any leftover device mapper devices from earlier runs of the unit tests +func cleanupDevMapper() { + infos, _ := ioutil.ReadDir("/dev/mapper") + if infos != nil { + hasPool := false + for _, info := range infos { + name := info.Name() + if strings.HasPrefix(name, "docker-unit-tests-devices-") { + if name == "docker-unit-tests-devices-pool" { + hasPool = true + } else { + if err := devmapper.RemoveDevice(name); err != nil { + panic(fmt.Errorf("Unable to remove existing device %s: %s", name, err)) + } + } + } + // We need to remove the pool last as the other devices block it + if hasPool { + if err := devmapper.RemoveDevice("docker-unit-tests-devices-pool"); err != nil { + panic(fmt.Errorf("Unable to remove existing device docker-unit-tests-devices-pool: %s", name, err)) + } + } + } + } +} + func init() { os.Setenv("TEST", "1") @@ -99,6 +126,8 @@ func init() { NetworkBridgeIface = unitTestNetworkBridge + cleanupDevMapper() + // Always start from a clean set of loopback mounts err := os.RemoveAll(unitTestStoreDevicesBase) if err != nil { From 8ed20571fc454994d2f9ad2e076676deedc6a865 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 24 Sep 2013 14:16:11 +0200 Subject: [PATCH 112/301] RootIsShared: Fix root detection Column 4 is the mount position, column 3 will not always be "/" for the root. On one of my system its "/root". Upstream-commit: 0484b2c3254238a6c534f7d417c12c10b694f0d0 Component: engine --- components/engine/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/utils.go b/components/engine/utils.go index 44573927a1..99600d1882 100644 --- a/components/engine/utils.go +++ b/components/engine/utils.go @@ -197,7 +197,7 @@ func RootIsShared() bool { if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil { for _, line := range strings.Split(string(data), "\n") { cols := strings.Split(line, " ") - if len(cols) >= 6 && cols[3] == "/" && cols[4] == "/" { + if len(cols) >= 6 && cols[4] == "/" { return strings.HasPrefix(cols[6], "shared") } } From 0728d1e89aea58de87642fcea9d436a1f252e841 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 24 Sep 2013 17:20:58 +0000 Subject: [PATCH 113/301] add a -mount-method flag Upstream-commit: aeb89ffbbac8c5625ad4dbba66938e2ef3f5c638 Component: engine --- components/engine/docker/docker.go | 7 ++++--- components/engine/runtime.go | 14 +++++++++++--- components/engine/runtime_test.go | 4 ++-- components/engine/server.go | 4 ++-- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/components/engine/docker/docker.go b/components/engine/docker/docker.go index 4dcc174d5e..1f4dcc1d86 100644 --- a/components/engine/docker/docker.go +++ b/components/engine/docker/docker.go @@ -36,6 +36,7 @@ func main() { flGraphPath := flag.String("g", "/var/lib/docker", "Path to graph storage base dir.") flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.") flDns := flag.String("dns", "", "Set custom dns servers") + flMountMethod := flag.String("mount-method", "", "Set the mount method to use, default is auto. [aufs, devicemapper, filesystem]") flHosts := docker.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)} flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use") flag.Parse() @@ -65,7 +66,7 @@ func main() { flag.Usage() return } - if err := daemon(*pidfile, *flGraphPath, flHosts, *flAutoRestart, *flEnableCors, *flDns); err != nil { + if err := daemon(*pidfile, *flGraphPath, flHosts, *flAutoRestart, *flEnableCors, *flDns, *flMountMethod); err != nil { log.Fatal(err) os.Exit(-1) } @@ -116,7 +117,7 @@ func removePidFile(pidfile string) { } } -func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error { +func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart, enableCors bool, flDns, mountMethod string) error { if err := createPidFile(pidfile); err != nil { log.Fatal(err) } @@ -134,7 +135,7 @@ func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart if flDns != "" { dns = []string{flDns} } - server, err := docker.NewServer(flGraphPath, devmapper.NewDeviceSetDM(flGraphPath), autoRestart, enableCors, dns) + server, err := docker.NewServer(flGraphPath, devmapper.NewDeviceSetDM(flGraphPath), autoRestart, enableCors, dns, mountMethod) if err != nil { return err } diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 10e937e21a..7b2455f304 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -519,8 +519,8 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a } // FIXME: harmonize with NewGraph() -func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns []string) (*Runtime, error) { - runtime, err := NewRuntimeFromDirectory(flGraphPath, deviceSet, autoRestart) +func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns []string, mountMethod string) (*Runtime, error) { + runtime, err := NewRuntimeFromDirectory(flGraphPath, deviceSet, autoRestart, mountMethod) if err != nil { return nil, err } @@ -538,7 +538,7 @@ func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns [ return runtime, nil } -func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool) (*Runtime, error) { +func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool, mountMethod string) (*Runtime, error) { runtimeRepo := path.Join(root, "containers") if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) { @@ -578,6 +578,14 @@ func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool) deviceSet: deviceSet, } + if mountMethod == "aufs" { + runtime.mountMethod = MountMethodAUFS + } else if mountMethod == "devicemapper" { + runtime.mountMethod = MountMethodDeviceMapper + } else if mountMethod == "filesystem" { + runtime.mountMethod = MountMethodFilesystem + } + if err := runtime.restore(); err != nil { return nil, err } diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index bcbdf3f384..b4be93fc1e 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -135,7 +135,7 @@ func init() { } // Make it our Store root - if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreDevicesBase), false); err != nil { + if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreDevicesBase), false, ""); err != nil { panic(err) } else { globalRuntime = runtime @@ -504,7 +504,7 @@ func TestRestore(t *testing.T) { // Here are are simulating a docker restart - that is, reloading all containers // from scratch - runtime2, err := NewRuntimeFromDirectory(runtime1.root, runtime1.deviceSet, false) + runtime2, err := NewRuntimeFromDirectory(runtime1.root, runtime1.deviceSet, false, "") if err != nil { t.Fatal(err) } diff --git a/components/engine/server.go b/components/engine/server.go index f3081db81a..d468234ca2 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -1299,11 +1299,11 @@ func (srv *Server) ContainerCopy(name string, resource string, out io.Writer) er } -func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors bool, dns ListOpts) (*Server, error) { +func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors bool, dns ListOpts, mountMethod string) (*Server, error) { if runtime.GOARCH != "amd64" { log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) } - runtime, err := NewRuntime(flGraphPath, deviceSet, autoRestart, dns) + runtime, err := NewRuntime(flGraphPath, deviceSet, autoRestart, dns, mountMethod) if err != nil { return nil, err } From bc0e976e1116c28aab4a4f2d67d4320732b5a750 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 26 Sep 2013 15:08:26 +0000 Subject: [PATCH 114/301] Revert "add a -mount-method flag" This reverts commit e52d756f40c9ccf8b37ca496cb72be057c909ed7. Upstream-commit: 72a08a5458d5d8248dec431c82241a7b18b2657b Component: engine --- components/engine/docker/docker.go | 7 +++---- components/engine/runtime.go | 14 +++----------- components/engine/runtime_test.go | 4 ++-- components/engine/server.go | 4 ++-- components/engine/utils_test.go | 2 +- 5 files changed, 11 insertions(+), 20 deletions(-) diff --git a/components/engine/docker/docker.go b/components/engine/docker/docker.go index 1f4dcc1d86..4dcc174d5e 100644 --- a/components/engine/docker/docker.go +++ b/components/engine/docker/docker.go @@ -36,7 +36,6 @@ func main() { flGraphPath := flag.String("g", "/var/lib/docker", "Path to graph storage base dir.") flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.") flDns := flag.String("dns", "", "Set custom dns servers") - flMountMethod := flag.String("mount-method", "", "Set the mount method to use, default is auto. [aufs, devicemapper, filesystem]") flHosts := docker.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)} flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use") flag.Parse() @@ -66,7 +65,7 @@ func main() { flag.Usage() return } - if err := daemon(*pidfile, *flGraphPath, flHosts, *flAutoRestart, *flEnableCors, *flDns, *flMountMethod); err != nil { + if err := daemon(*pidfile, *flGraphPath, flHosts, *flAutoRestart, *flEnableCors, *flDns); err != nil { log.Fatal(err) os.Exit(-1) } @@ -117,7 +116,7 @@ func removePidFile(pidfile string) { } } -func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart, enableCors bool, flDns, mountMethod string) error { +func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error { if err := createPidFile(pidfile); err != nil { log.Fatal(err) } @@ -135,7 +134,7 @@ func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart if flDns != "" { dns = []string{flDns} } - server, err := docker.NewServer(flGraphPath, devmapper.NewDeviceSetDM(flGraphPath), autoRestart, enableCors, dns, mountMethod) + server, err := docker.NewServer(flGraphPath, devmapper.NewDeviceSetDM(flGraphPath), autoRestart, enableCors, dns) if err != nil { return err } diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 7b2455f304..10e937e21a 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -519,8 +519,8 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a } // FIXME: harmonize with NewGraph() -func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns []string, mountMethod string) (*Runtime, error) { - runtime, err := NewRuntimeFromDirectory(flGraphPath, deviceSet, autoRestart, mountMethod) +func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns []string) (*Runtime, error) { + runtime, err := NewRuntimeFromDirectory(flGraphPath, deviceSet, autoRestart) if err != nil { return nil, err } @@ -538,7 +538,7 @@ func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns [ return runtime, nil } -func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool, mountMethod string) (*Runtime, error) { +func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool) (*Runtime, error) { runtimeRepo := path.Join(root, "containers") if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) { @@ -578,14 +578,6 @@ func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool, deviceSet: deviceSet, } - if mountMethod == "aufs" { - runtime.mountMethod = MountMethodAUFS - } else if mountMethod == "devicemapper" { - runtime.mountMethod = MountMethodDeviceMapper - } else if mountMethod == "filesystem" { - runtime.mountMethod = MountMethodFilesystem - } - if err := runtime.restore(); err != nil { return nil, err } diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index b4be93fc1e..bcbdf3f384 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -135,7 +135,7 @@ func init() { } // Make it our Store root - if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreDevicesBase), false, ""); err != nil { + if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreDevicesBase), false); err != nil { panic(err) } else { globalRuntime = runtime @@ -504,7 +504,7 @@ func TestRestore(t *testing.T) { // Here are are simulating a docker restart - that is, reloading all containers // from scratch - runtime2, err := NewRuntimeFromDirectory(runtime1.root, runtime1.deviceSet, false, "") + runtime2, err := NewRuntimeFromDirectory(runtime1.root, runtime1.deviceSet, false) if err != nil { t.Fatal(err) } diff --git a/components/engine/server.go b/components/engine/server.go index d468234ca2..f3081db81a 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -1299,11 +1299,11 @@ func (srv *Server) ContainerCopy(name string, resource string, out io.Writer) er } -func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors bool, dns ListOpts, mountMethod string) (*Server, error) { +func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors bool, dns ListOpts) (*Server, error) { if runtime.GOARCH != "amd64" { log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) } - runtime, err := NewRuntime(flGraphPath, deviceSet, autoRestart, dns, mountMethod) + runtime, err := NewRuntime(flGraphPath, deviceSet, autoRestart, dns) if err != nil { return nil, err } diff --git a/components/engine/utils_test.go b/components/engine/utils_test.go index f5bdac3271..87f67ff0d7 100644 --- a/components/engine/utils_test.go +++ b/components/engine/utils_test.go @@ -43,7 +43,7 @@ func newTestRuntime() (*Runtime, error) { return nil, err } - runtime, err := NewRuntimeFromDirectory(root, NewDeviceSetWrapper (globalRuntime.deviceSet, filepath.Base(root)), false) + runtime, err := NewRuntimeFromDirectory(root, NewDeviceSetWrapper(globalRuntime.deviceSet, filepath.Base(root)), false) if err != nil { return nil, err } From 6420d1eea7ebbb8d86534d61975d1f643ce09133 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 26 Sep 2013 15:40:13 +0000 Subject: [PATCH 115/301] go fmt and aufs support removed Upstream-commit: 152302e379eaa5b2dfc27d901934da0a18b46beb Component: engine --- components/engine/api_params.go | 14 +- components/engine/archive.go | 8 +- components/engine/changes.go | 116 +++--------- components/engine/container.go | 4 +- components/engine/deviceset.go | 5 +- components/engine/image.go | 290 ++++++++---------------------- components/engine/mount.go | 29 --- components/engine/runtime.go | 34 +--- components/engine/runtime_test.go | 17 +- components/engine/utils_test.go | 2 +- 10 files changed, 124 insertions(+), 395 deletions(-) diff --git a/components/engine/api_params.go b/components/engine/api_params.go index 6403bc6a26..5f1a338057 100644 --- a/components/engine/api_params.go +++ b/components/engine/api_params.go @@ -56,13 +56,13 @@ type APIContainers struct { func (self *APIContainers) ToLegacy() APIContainersOld { return APIContainersOld{ - ID: self.ID, - Image: self.Image, - Command: self.Command, - Created: self.Created, - Status: self.Status, - Ports: displayablePorts(self.Ports), - SizeRw: self.SizeRw, + ID: self.ID, + Image: self.Image, + Command: self.Command, + Created: self.Created, + Status: self.Status, + Ports: displayablePorts(self.Ports), + SizeRw: self.SizeRw, SizeRootFs: self.SizeRootFs, } } diff --git a/components/engine/archive.go b/components/engine/archive.go index f3f7b8c59e..75b6e7e1f1 100644 --- a/components/engine/archive.go +++ b/components/engine/archive.go @@ -84,13 +84,13 @@ func Tar(path string, compression Compression) (io.Reader, error) { } func escapeName(name string) string { - escaped := make([]byte,0) + escaped := make([]byte, 0) for i, c := range []byte(name) { if i == 0 && c == '/' { continue } // all printable chars except "-" which is 0x2d - if (0x20 <= c && c <= 0x7E) && c != 0x2d { + if (0x20 <= c && c <= 0x7E) && c != 0x2d { escaped = append(escaped, c) } else { escaped = append(escaped, fmt.Sprintf("\\%03o", c)...) @@ -102,7 +102,7 @@ func escapeName(name string) string { // Tar creates an archive from the directory at `path`, only including files whose relative // paths are included in `filter`. If `filter` is nil, then all files are included. func TarFilter(path string, compression Compression, filter []string, recursive bool, createFiles []string) (io.Reader, error) { - args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path, "-T", "-",} + args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path, "-T", "-"} if filter == nil { filter = []string{"."} } @@ -142,7 +142,7 @@ func TarFilter(path string, compression Compression, filter []string, recursive } } - return CmdStream(exec.Command(args[0], args[1:]...), &files, func () { + return CmdStream(exec.Command(args[0], args[1:]...), &files, func() { if tmpDir != "" { _ = os.RemoveAll(tmpDir) } diff --git a/components/engine/changes.go b/components/engine/changes.go index 49802d3170..77bef6fb22 100644 --- a/components/engine/changes.go +++ b/components/engine/changes.go @@ -34,82 +34,10 @@ func (change *Change) String() string { return fmt.Sprintf("%s %s", kind, change.Path) } -func ChangesAUFS(layers []string, rw string) ([]Change, error) { - var changes []Change - err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error { - if err != nil { - return err - } - - // Rebase path - path, err = filepath.Rel(rw, path) - if err != nil { - return err - } - path = filepath.Join("/", path) - - // Skip root - if path == "/" { - return nil - } - - // Skip AUFS metadata - if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched { - return err - } - - change := Change{ - Path: path, - } - - // Find out what kind of modification happened - file := filepath.Base(path) - // If there is a whiteout, then the file was removed - if strings.HasPrefix(file, ".wh.") { - originalFile := file[len(".wh."):] - change.Path = filepath.Join(filepath.Dir(path), originalFile) - change.Kind = ChangeDelete - } else { - // Otherwise, the file was added - change.Kind = ChangeAdd - - // ...Unless it already existed in a top layer, in which case, it's a modification - for _, layer := range layers { - stat, err := os.Stat(filepath.Join(layer, path)) - if err != nil && !os.IsNotExist(err) { - return err - } - if err == nil { - // The file existed in the top layer, so that's a modification - - // However, if it's a directory, maybe it wasn't actually modified. - // If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar - if stat.IsDir() && f.IsDir() { - if f.Size() == stat.Size() && f.Mode() == stat.Mode() && f.ModTime() == stat.ModTime() { - // Both directories are the same, don't record the change - return nil - } - } - change.Kind = ChangeModify - break - } - } - } - - // Record change - changes = append(changes, change) - return nil - }) - if err != nil && !os.IsNotExist(err) { - return nil, err - } - return changes, nil -} - type FileInfo struct { - parent *FileInfo - name string - stat syscall.Stat_t + parent *FileInfo + name string + stat syscall.Stat_t children map[string]*FileInfo } @@ -132,20 +60,20 @@ func (root *FileInfo) LookUp(path string) *FileInfo { return parent } -func (info *FileInfo)path() string { +func (info *FileInfo) path() string { if info.parent == nil { return "/" } return filepath.Join(info.parent.path(), info.name) } -func (info *FileInfo)unlink() { +func (info *FileInfo) unlink() { if info.parent != nil { delete(info.parent.children, info.name) } } -func (info *FileInfo)Remove(path string) bool { +func (info *FileInfo) Remove(path string) bool { child := info.LookUp(path) if child != nil { child.unlink() @@ -154,12 +82,11 @@ func (info *FileInfo)Remove(path string) bool { return false } -func (info *FileInfo)isDir() bool { +func (info *FileInfo) isDir() bool { return info.parent == nil || info.stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR } - -func (info *FileInfo)addChanges(oldInfo *FileInfo, changes *[]Change) { +func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) { if oldInfo == nil { // add change := Change{ @@ -198,7 +125,7 @@ func (info *FileInfo)addChanges(oldInfo *FileInfo, changes *[]Change) { oldStat.Gid != newStat.Gid || oldStat.Rdev != newStat.Rdev || // Don't look at size for dirs, its not a good measure of change - (oldStat.Size != newStat.Size && oldStat.Mode &syscall.S_IFDIR != syscall.S_IFDIR) || + (oldStat.Size != newStat.Size && oldStat.Mode&syscall.S_IFDIR != syscall.S_IFDIR) || oldMtime.Sec != newMtime.Sec || oldMtime.Usec != newMtime.Usec { change := Change{ @@ -223,10 +150,9 @@ func (info *FileInfo)addChanges(oldInfo *FileInfo, changes *[]Change) { *changes = append(*changes, change) } - } -func (info *FileInfo)Changes(oldInfo *FileInfo) []Change { +func (info *FileInfo) Changes(oldInfo *FileInfo) []Change { var changes []Change info.addChanges(oldInfo, &changes) @@ -234,10 +160,9 @@ func (info *FileInfo)Changes(oldInfo *FileInfo) []Change { return changes } - func newRootFileInfo() *FileInfo { - root := &FileInfo { - name: "/", + root := &FileInfo{ + name: "/", children: make(map[string]*FileInfo), } return root @@ -299,11 +224,11 @@ func applyLayer(root *FileInfo, layer string) error { return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath) } - info := &FileInfo { - name: filepath.Base(relPath), + info := &FileInfo{ + name: filepath.Base(relPath), children: make(map[string]*FileInfo), - parent: parent, - stat: layerStat, + parent: parent, + stat: layerStat, } parent.children[info.name] = info @@ -314,7 +239,6 @@ func applyLayer(root *FileInfo, layer string) error { return err } - func collectFileInfo(sourceDir string) (*FileInfo, error) { root := newRootFileInfo() @@ -339,10 +263,10 @@ func collectFileInfo(sourceDir string) (*FileInfo, error) { return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath) } - info := &FileInfo { - name: filepath.Base(relPath), + info := &FileInfo{ + name: filepath.Base(relPath), children: make(map[string]*FileInfo), - parent: parent, + parent: parent, } if err := syscall.Lstat(path, &info.stat); err != nil { @@ -365,7 +289,7 @@ func ChangesLayers(newDir string, layers []string) ([]Change, error) { return nil, err } oldRoot := newRootFileInfo() - for i := len(layers)-1; i >= 0; i-- { + for i := len(layers) - 1; i >= 0; i-- { layer := layers[i] if err = applyLayer(oldRoot, layer); err != nil { return nil, err diff --git a/components/engine/container.go b/components/engine/container.go index 461004767d..54fe6dcb7f 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -804,10 +804,10 @@ func (container *Container) Start(hostConfig *HostConfig) error { // without exec in go we have to do this horrible shell hack... shellString := "mount --make-rprivate /; exec " + - utils.ShellQuoteArguments(params) + utils.ShellQuoteArguments(params) params = []string{ - "unshare", "-m", "--", "/bin/sh", "-c", shellString, + "unshare", "-m", "--", "/bin/sh", "-c", shellString, } } diff --git a/components/engine/deviceset.go b/components/engine/deviceset.go index cf422acb3a..8e619ca248 100644 --- a/components/engine/deviceset.go +++ b/components/engine/deviceset.go @@ -15,7 +15,7 @@ type DeviceSet interface { type DeviceSetWrapper struct { wrapped DeviceSet - prefix string + prefix string } func (wrapper *DeviceSetWrapper) wrap(hash string) string { @@ -25,7 +25,6 @@ func (wrapper *DeviceSetWrapper) wrap(hash string) string { return hash } - func (wrapper *DeviceSetWrapper) AddDevice(hash, baseHash string) error { return wrapper.wrapped.AddDevice(wrapper.wrap(hash), wrapper.wrap(baseHash)) } @@ -69,7 +68,7 @@ func (wrapper *DeviceSetWrapper) HasActivatedDevice(hash string) bool { func NewDeviceSetWrapper(wrapped DeviceSet, prefix string) DeviceSet { wrapper := &DeviceSetWrapper{ wrapped: wrapped, - prefix: prefix, + prefix: prefix, } return wrapper } diff --git a/components/engine/image.go b/components/engine/image.go index ff8b836c39..2187605d51 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -8,9 +8,7 @@ import ( "github.com/dotcloud/docker/utils" "io" "io/ioutil" - "log" "os" - "os/exec" "path" "path/filepath" "strconv" @@ -141,31 +139,6 @@ func mountPath(root string) string { return path.Join(root, "mount") } -func MountAUFS(ro []string, rw string, target string) error { - // FIXME: Now mount the layers - rwBranch := fmt.Sprintf("%v=rw", rw) - roBranches := "" - for _, layer := range ro { - roBranches += fmt.Sprintf("%v=ro+wh:", layer) - } - branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches) - - branches += ",xino=/dev/shm/aufs.xino" - - //if error, try to load aufs kernel module - if err := mount("none", target, "aufs", 0, branches); err != nil { - log.Printf("Kernel does not support AUFS, trying to load the AUFS module with modprobe...") - if err := exec.Command("modprobe", "aufs").Run(); err != nil { - return fmt.Errorf("Unable to load the AUFS module") - } - log.Printf("...module loaded.") - if err := mount("none", target, "aufs", 0, branches); err != nil { - return fmt.Errorf("Unable to mount using aufs") - } - } - return nil -} - // TarLayer returns a tar archive of the image's filesystem layer. func (image *Image) TarLayer(compression Compression) (Archive, error) { layerPath, err := image.layer() @@ -315,7 +288,7 @@ func (image *Image) applyLayer(layer, target string) error { syscall.NsecToTimeval(srcStat.Mtim.Nano()), } - u := TimeUpdate { + u := TimeUpdate{ path: targetPath, time: ts, } @@ -335,7 +308,7 @@ func (image *Image) applyLayer(layer, target string) error { update := updateTimes[i] O_PATH := 010000000 // Not in syscall yet - fd, err := syscall.Open(update.path, syscall.O_RDWR | O_PATH | syscall.O_NOFOLLOW, 0600) + fd, err := syscall.Open(update.path, syscall.O_RDWR|O_PATH|syscall.O_NOFOLLOW, 0600) if err == syscall.EISDIR || err == syscall.ELOOP { // O_PATH not supported, use Utimes except on symlinks where Utimes doesn't work if err != syscall.ELOOP { @@ -411,7 +384,6 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { return err } - err = ioutil.WriteFile(path.Join(mountDir, ".docker-id"), []byte(image.ID), 0600) if err != nil { _ = devices.UnmountDevice(image.ID, mountDir) @@ -461,25 +433,7 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { } func (image *Image) Mounted(runtime *Runtime, root, rw string) (bool, error) { - method := runtime.GetMountMethod() - if method == MountMethodFilesystem { - if _, err := os.Stat(rw); err != nil { - if os.IsNotExist(err) { - err = nil - } - return false, err - } - mountedPath := path.Join(rw, ".fs-mounted") - if _, err := os.Stat(mountedPath); err != nil { - if os.IsNotExist(err) { - err = nil - } - return false, err - } - return true, nil - } else { - return Mounted(root) - } + return Mounted(root) } func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { @@ -492,195 +446,107 @@ func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { return err } - switch runtime.GetMountMethod() { - case MountMethodNone: - return fmt.Errorf("No supported Mount implementation") + devices, err := runtime.GetDeviceSet() + if err != nil { + return err + } + err = image.ensureImageDevice(devices) + if err != nil { + return err + } - case MountMethodAUFS: - if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { - return err - } - layers, err := image.layers() + createdDevice := false + if !devices.HasDevice(id) { + utils.Debugf("Creating device %s for container based on image %s", id, image.ID) + err = devices.AddDevice(id, image.ID) if err != nil { return err } - if err := MountAUFS(layers, rw, root); err != nil { - return err - } + createdDevice = true + } - case MountMethodDeviceMapper: - devices, err := runtime.GetDeviceSet() + utils.Debugf("Mounting container %s at %s for container", id, root) + err = devices.MountDevice(id, root) + if err != nil { + return err + } + + if createdDevice { + err = ioutil.WriteFile(path.Join(root, ".docker-id"), []byte(id), 0600) if err != nil { + _ = devices.RemoveDevice(image.ID) return err } - err = image.ensureImageDevice(devices) - if err != nil { - return err - } - - createdDevice := false - if !devices.HasDevice(id) { - utils.Debugf("Creating device %s for container based on image %s", id, image.ID) - err = devices.AddDevice(id, image.ID) - if err != nil { - return err - } - createdDevice = true - } - - utils.Debugf("Mounting container %s at %s for container", id, root) - err = devices.MountDevice(id, root) - if err != nil { - return err - } - - if createdDevice { - err = ioutil.WriteFile(path.Join(root, ".docker-id"), []byte(id), 0600) - if err != nil { - _ = devices.RemoveDevice(image.ID) - return err - } - } - - case MountMethodFilesystem: - if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { - return err - } - - layers, err := image.layers() - if err != nil { - return err - } - - for i := len(layers)-1; i >= 0; i-- { - layer := layers[i] - if err = image.applyLayer(layer, root); err != nil { - return err - } - } - - mountedPath := path.Join(rw, ".fs-mounted") - fo, err := os.Create(mountedPath) - if err != nil { - return err - } - fo.Close() } return nil } func (image *Image) Unmount(runtime *Runtime, root string, id string) error { - switch runtime.GetMountMethod() { - case MountMethodNone: - return fmt.Errorf("No supported Unmount implementation") - - case MountMethodAUFS: - return Unmount(root) - - case MountMethodDeviceMapper: - // Try to deactivate the device as generally there is no use for it anymore - devices, err := runtime.GetDeviceSet() - if err != nil { - return err; - } - - err = devices.UnmountDevice(id, root) - if err != nil { - return err - } - - return devices.DeactivateDevice(id) - - case MountMethodFilesystem: - return nil + // Try to deactivate the device as generally there is no use for it anymore + devices, err := runtime.GetDeviceSet() + if err != nil { + return err } - return nil + err = devices.UnmountDevice(id, root) + if err != nil { + return err + } + + return devices.DeactivateDevice(id) } func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, error) { - switch runtime.GetMountMethod() { - case MountMethodAUFS: - layers, err := image.layers() - if err != nil { - return nil, err - } - return ChangesAUFS(layers, rw) - - case MountMethodDeviceMapper: - devices, err := runtime.GetDeviceSet() - if err != nil { - return nil, err - } - - if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { - return nil, err - } - - wasActivated := devices.HasActivatedDevice(image.ID) - - // We re-use rw for the temporary mount of the base image as its - // not used by device-mapper otherwise - err = devices.MountDevice(image.ID, rw) - if err != nil { - return nil, err - } - - changes, err := ChangesDirs(root, rw) - _ = devices.UnmountDevice(image.ID, rw) - if !wasActivated { - _ = devices.DeactivateDevice(image.ID) - } - if err != nil { - return nil, err - } - return changes, nil - - case MountMethodFilesystem: - layers, err := image.layers() - if err != nil { - return nil, err - } - changes, err := ChangesLayers(root, layers) - if err != nil { - return nil, err - } - return changes, nil + devices, err := runtime.GetDeviceSet() + if err != nil { + return nil, err } - return nil, fmt.Errorf("No supported Changes implementation") + if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { + return nil, err + } + + wasActivated := devices.HasActivatedDevice(image.ID) + + // We re-use rw for the temporary mount of the base image as its + // not used by device-mapper otherwise + err = devices.MountDevice(image.ID, rw) + if err != nil { + return nil, err + } + + changes, err := ChangesDirs(root, rw) + _ = devices.UnmountDevice(image.ID, rw) + if !wasActivated { + _ = devices.DeactivateDevice(image.ID) + } + if err != nil { + return nil, err + } + return changes, nil } func (image *Image) ExportChanges(runtime *Runtime, root, rw, id string) (Archive, error) { - switch runtime.GetMountMethod() { - case MountMethodAUFS: - return Tar(rw, Uncompressed) - - case MountMethodFilesystem, MountMethodDeviceMapper: - changes, err := image.Changes(runtime, root, rw, id) - if err != nil { - return nil, err - } - - files := make([]string, 0) - deletions := make([]string, 0) - for _, change := range changes { - if change.Kind == ChangeModify || change.Kind == ChangeAdd { - files = append(files, change.Path) - } - if change.Kind == ChangeDelete { - base := filepath.Base(change.Path) - dir := filepath.Dir(change.Path) - deletions = append(deletions, filepath.Join(dir, ".wh."+base)) - } - } - - return TarFilter(root, Uncompressed, files, false, deletions) + changes, err := image.Changes(runtime, root, rw, id) + if err != nil { + return nil, err } - return nil, fmt.Errorf("No supported Changes implementation") -} + files := make([]string, 0) + deletions := make([]string, 0) + for _, change := range changes { + if change.Kind == ChangeModify || change.Kind == ChangeAdd { + files = append(files, change.Path) + } + if change.Kind == ChangeDelete { + base := filepath.Base(change.Path) + dir := filepath.Dir(change.Path) + deletions = append(deletions, filepath.Join(dir, ".wh."+base)) + } + } + return TarFilter(root, Uncompressed, files, false, deletions) +} func (image *Image) ShortID() string { return utils.TruncateID(image.ID) diff --git a/components/engine/mount.go b/components/engine/mount.go index 541c29c13a..3e2a21df50 100644 --- a/components/engine/mount.go +++ b/components/engine/mount.go @@ -1,40 +1,11 @@ package docker import ( - "fmt" - "github.com/dotcloud/docker/utils" "os" - "os/exec" "path/filepath" "syscall" - "time" ) -func Unmount(target string) error { - if err := exec.Command("auplink", target, "flush").Run(); err != nil { - utils.Debugf("[warning]: couldn't run auplink before unmount: %s", err) - } - if err := syscall.Unmount(target, 0); err != nil { - return err - } - // Even though we just unmounted the filesystem, AUFS will prevent deleting the mntpoint - // for some time. We'll just keep retrying until it succeeds. - for retries := 0; retries < 1000; retries++ { - err := os.Remove(target) - if err == nil { - // rm mntpoint succeeded - return nil - } - if os.IsNotExist(err) { - // mntpoint doesn't exist anymore. Success. - return nil - } - // fmt.Printf("(%v) Remove %v returned: %v\n", retries, target, err) - time.Sleep(10 * time.Millisecond) - } - return fmt.Errorf("Umount: Failed to umount %v", target) -} - func Mounted(mountpoint string) (bool, error) { mntpoint, err := os.Stat(mountpoint) if err != nil { diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 10e937e21a..b88f6d98dd 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -17,14 +17,6 @@ import ( ) var defaultDns = []string{"8.8.8.8", "8.8.4.4"} -type MountMethod int - -const ( - MountMethodNone MountMethod = iota - MountMethodAUFS - MountMethodDeviceMapper - MountMethodFilesystem -) type Capabilities struct { MemoryLimit bool @@ -47,7 +39,6 @@ type Runtime struct { srv *Server Dns []string deviceSet DeviceSet - mountMethod MountMethod } var sysInitPath string @@ -109,27 +100,6 @@ func hasFilesystemSupport(fstype string) bool { return false } -func (runtime *Runtime) GetMountMethod() MountMethod { - if runtime.mountMethod == MountMethodNone { - // Try to automatically pick a method - if hasFilesystemSupport("aufs") { - utils.Debugf("Using AUFS backend.") - runtime.mountMethod = MountMethodAUFS - } else { - _ = exec.Command("modprobe", "aufs").Run() - if hasFilesystemSupport("aufs") { - utils.Debugf("Using AUFS backend.") - runtime.mountMethod = MountMethodAUFS - } else { - utils.Debugf("Using device-mapper backend.") - runtime.mountMethod = MountMethodDeviceMapper - } - } - } - - return runtime.mountMethod -} - func (runtime *Runtime) GetDeviceSet() (DeviceSet, error) { if runtime.deviceSet == nil { return nil, fmt.Errorf("No device set available") @@ -289,7 +259,7 @@ func (runtime *Runtime) Destroy(container *Container) error { if err := os.RemoveAll(container.root); err != nil { return fmt.Errorf("Unable to remove filesystem for %v: %v", container.ID, err) } - if runtime.GetMountMethod() == MountMethodDeviceMapper && runtime.deviceSet.HasDevice(container.ID) { + if runtime.deviceSet.HasDevice(container.ID) { if err := runtime.deviceSet.RemoveDevice(container.ID); err != nil { return fmt.Errorf("Unable to remove device for %v: %v", container.ID, err) } @@ -302,7 +272,7 @@ func (runtime *Runtime) DeleteImage(id string) error { if err != nil { return err } - if runtime.GetMountMethod() == MountMethodDeviceMapper && runtime.deviceSet.HasDevice(id) { + if runtime.deviceSet.HasDevice(id) { if err := runtime.deviceSet.RemoveDevice(id); err != nil { return fmt.Errorf("Unable to remove device for %v: %v", id, err) } diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index bcbdf3f384..0a9b95a411 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -3,8 +3,8 @@ package docker import ( "bytes" "fmt" - "github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/devmapper" + "github.com/dotcloud/docker/utils" "io" "io/ioutil" "log" @@ -20,13 +20,13 @@ import ( ) const ( - unitTestImageName = "docker-test-image" - unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 - unitTestNetworkBridge = "testdockbr0" - unitTestStoreBase = "/var/lib/docker/unit-tests" - unitTestStoreDevicesBase = "/var/lib/docker/unit-tests-devices" - testDaemonAddr = "127.0.0.1:4270" - testDaemonProto = "tcp" + unitTestImageName = "docker-test-image" + unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 + unitTestNetworkBridge = "testdockbr0" + unitTestStoreBase = "/var/lib/docker/unit-tests" + unitTestStoreDevicesBase = "/var/lib/docker/unit-tests-devices" + testDaemonAddr = "127.0.0.1:4270" + testDaemonProto = "tcp" ) var ( @@ -75,7 +75,6 @@ func cleanupLast(runtime *Runtime) error { return nil } - func layerArchive(tarfile string) (io.Reader, error) { // FIXME: need to close f somewhere f, err := os.Open(tarfile) diff --git a/components/engine/utils_test.go b/components/engine/utils_test.go index 87f67ff0d7..f458b1da91 100644 --- a/components/engine/utils_test.go +++ b/components/engine/utils_test.go @@ -2,11 +2,11 @@ package docker import ( "github.com/dotcloud/docker/utils" - "path/filepath" "io" "io/ioutil" "os" "path" + "path/filepath" "strings" "testing" ) From 615792fa5b3351cc250db4b68cdf8b412c77a6e5 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 26 Sep 2013 21:12:12 +0200 Subject: [PATCH 116/301] Image: Fix time setting for old kernels This is a better fix for futimes() on kernels not supporting O_PATH. The previous fix broke when copying a device, as it tried to open it and got and error. Upstream-commit: 55e1782d6623b59af3f1aea1eb9646a36bbb5579 Component: engine --- components/engine/image.go | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/components/engine/image.go b/components/engine/image.go index 2187605d51..06036e1e62 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -151,6 +151,7 @@ func (image *Image) TarLayer(compression Compression) (Archive, error) { type TimeUpdate struct { path string time []syscall.Timeval + mode uint32 } func (image *Image) applyLayer(layer, target string) error { @@ -291,6 +292,7 @@ func (image *Image) applyLayer(layer, target string) error { u := TimeUpdate{ path: targetPath, time: ts, + mode: srcStat.Mode, } // Delay time updates until all other changes done, or it is @@ -308,21 +310,24 @@ func (image *Image) applyLayer(layer, target string) error { update := updateTimes[i] O_PATH := 010000000 // Not in syscall yet - fd, err := syscall.Open(update.path, syscall.O_RDWR|O_PATH|syscall.O_NOFOLLOW, 0600) - if err == syscall.EISDIR || err == syscall.ELOOP { - // O_PATH not supported, use Utimes except on symlinks where Utimes doesn't work - if err != syscall.ELOOP { - err = syscall.Utimes(update.path, update.time) - if err != nil { - return err - } + var err error = nil + if update.mode&syscall.S_IFLNK == syscall.S_IFLNK { + // Update time on the symlink via O_PATH + futimes(), if supported by the kernel + + fd, err := syscall.Open(update.path, syscall.O_RDWR|O_PATH|syscall.O_NOFOLLOW, 0600) + if err == syscall.EISDIR || err == syscall.ELOOP { + // O_PATH not supported by kernel, nothing to do, ignore + } else if err != nil { + return err + } else { + syscall.Futimes(fd, update.time) + _ = syscall.Close(fd) } } else { + err = syscall.Utimes(update.path, update.time) if err != nil { return err } - syscall.Futimes(fd, update.time) - _ = syscall.Close(fd) } } From 705988989e3e05ad51b6dd4937471475b99e6841 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 3 Oct 2013 15:47:38 +0000 Subject: [PATCH 117/301] disable: don't create device nodes manually if udev is not availabile as we don't have it in dind Upstream-commit: 55189307d0ec6992f76572fae675bd371cc36cff Component: engine --- components/engine/devmapper/deviceset_devmapper.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index bbf1fa6adc..58da0db97d 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -204,7 +204,7 @@ func (devices *DeviceSetDM) createPool(dataFile *os.File, metadataFile *os.File) } var cookie uint32 = 0 - err = task.SetCookie(&cookie, 32) + err = task.SetCookie(&cookie, 0) if err != nil { return fmt.Errorf("Can't set cookie") } @@ -238,7 +238,7 @@ func (devices *DeviceSetDM) resumeDevice(info *DevInfo) error { } var cookie uint32 = 0 - err = task.SetCookie(&cookie, 32) + err = task.SetCookie(&cookie, 0) if err != nil { return fmt.Errorf("Can't set cookie") } @@ -374,7 +374,7 @@ func (devices *DeviceSetDM) activateDevice(info *DevInfo) error { } var cookie uint32 = 0 - err = task.SetCookie(&cookie, 32) + err = task.SetCookie(&cookie, 0) if err != nil { return fmt.Errorf("Can't set cookie") } From 788a1fc55811c971b72a024eb15456bed4a75de9 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 30 Sep 2013 21:14:54 -0600 Subject: [PATCH 118/301] Update Dockerfile and hack to support compiling device-mapper code statically (using go1.2rc1) Upstream-commit: 06d0843a61843e6b16a56d518e21032a5652098b Component: engine --- components/engine/Dockerfile | 13 +++++++++++-- components/engine/hack/PACKAGERS.md | 27 ++++++++++++--------------- components/engine/hack/make.sh | 4 ++-- components/engine/hack/make/binary | 2 +- components/engine/hack/make/test | 4 +++- 5 files changed, 29 insertions(+), 21 deletions(-) diff --git a/components/engine/Dockerfile b/components/engine/Dockerfile index abb8a02828..1c29ed7615 100644 --- a/components/engine/Dockerfile +++ b/components/engine/Dockerfile @@ -36,13 +36,22 @@ run apt-get install -y -q mercurial run apt-get install -y -q build-essential # Install Go from source (for eventual cross-compiling) -env CGO_ENABLED 0 -run curl -s https://go.googlecode.com/files/go1.1.2.src.tar.gz | tar -v -C / -xz && mv /go /goroot +run curl -s https://go.googlecode.com/files/go1.2rc1.src.tar.gz | tar -v -C / -xz && mv /go /goroot run cd /goroot/src && ./make.bash env GOROOT /goroot env PATH $PATH:/goroot/bin env GOPATH /go:/go/src/github.com/dotcloud/docker/vendor +# Create Go cache with tag netgo (for static compilation of Go while preserving CGO support) +run go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std + +# Get lvm2 source for compiling statically +run git clone git://git.fedorahosted.org/git/lvm2.git /lvm2 +run cd /lvm2 && git checkout v2_02_102 +# can't use git clone -b because it's not supported by git versions before 1.7.10 +run cd /lvm2 && ./configure --enable-static_link && make && make install_device-mapper +# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags + # Ubuntu stuff run apt-get install -y -q ruby1.9.3 rubygems libffi-dev run gem install --no-rdoc --no-ri fpm diff --git a/components/engine/hack/PACKAGERS.md b/components/engine/hack/PACKAGERS.md index 776ed47472..44f71aa94f 100644 --- a/components/engine/hack/PACKAGERS.md +++ b/components/engine/hack/PACKAGERS.md @@ -32,14 +32,14 @@ the process. ## System build dependencies -To build docker, you will need the following system dependencies +To build docker, you will need the following system dependencies: * An amd64 machine * A recent version of git and mercurial -* Go version 1.1.2 +* Go version 1.2rc1 +* A copy of libdevmapper.a (statically compiled), and associated headers * A clean checkout of the source must be added to a valid Go [workspace](http://golang.org/doc/code.html#Workspaces) -under the path *src/github.com/dotcloud/docker*. See - +under the path *src/github.com/dotcloud/docker*. ## Go dependencies @@ -55,15 +55,13 @@ NOTE: if you''re not able to package the exact version (to the exact commit) of please get in touch so we can remediate! Who knows what discrepancies can be caused by even the slightest deviation. We promise to do our best to make everybody happy. +## Disabling CGO for the net package -## Disabling CGO - -Make sure to disable CGO on your system, and then recompile the standard library on the build -machine: +Make sure to disable CGO on your system for the net package using `-tags netgo`, +and then recompile the standard library on the build machine: ```bash -export CGO_ENABLED=0 -cd /tmp && echo 'package main' > t.go && go test -a -i -v +go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std ``` ## Building Docker @@ -71,7 +69,7 @@ cd /tmp && echo 'package main' > t.go && go test -a -i -v To build the docker binary, run the following command with the source checkout as the working directory: -``` +```bash ./hack/make.sh binary ``` @@ -80,9 +78,9 @@ This will create a static binary under *./bundles/$VERSION/binary/docker-$VERSIO You are encouraged to use ./hack/make.sh without modification. If you must absolutely write your own script (are you really, really sure you need to? make.sh is really not that complicated), -then please take care the respect the following: +then please take care to respect the following: -* In *./hack/make.sh*: $LDFLAGS, $VERSION and $GITCOMMIT +* In *./hack/make.sh*: $LDFLAGS, $BUILDFLAGS, $VERSION and $GITCOMMIT * In *./hack/make/binary*: the exact build command to run You may be tempted to tweak these settings. In particular, being a rigorous maintainer, you may want @@ -106,7 +104,6 @@ dependencies to be installed (see below). The test suite will also download a small test container, so you will need internet connectivity. - ## Runtime dependencies To run properly, docker needs the following software to be installed at runtime: @@ -115,7 +112,7 @@ To run properly, docker needs the following software to be installed at runtime: * iproute2 version 3.5 or later (build after 2012-05-21), and specifically the "ip" utility. * iptables version 1.4 or later * The lxc utility scripts (http://lxc.sourceforge.net) version 0.8 or later. -* Git version 1.7 or later +* Git version 1.7 or later ## Kernel dependencies diff --git a/components/engine/hack/make.sh b/components/engine/hack/make.sh index 98b62ea6ae..62863bb8d8 100755 --- a/components/engine/hack/make.sh +++ b/components/engine/hack/make.sh @@ -44,8 +44,8 @@ if [ -n "$(git status --porcelain)" ]; then fi # Use these flags when compiling the tests and final binary -LDFLAGS="-X main.GITCOMMIT $GITCOMMIT -X main.VERSION $VERSION -d -w" - +LDFLAGS='-X main.GITCOMMIT "'$GITCOMMIT'" -X main.VERSION "'$VERSION'" -w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' +BUILDFLAGS='-tags netgo' bundle() { bundlescript=$1 diff --git a/components/engine/hack/make/binary b/components/engine/hack/make/binary index cff9f5c733..3301c4ad7f 100644 --- a/components/engine/hack/make/binary +++ b/components/engine/hack/make/binary @@ -2,6 +2,6 @@ DEST=$1 -if go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS" ./docker; then +if go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS" $BUILDFLAGS ./docker; then echo "Created binary: $DEST/docker-$VERSION" fi diff --git a/components/engine/hack/make/test b/components/engine/hack/make/test index 9334c8b313..86e77c5913 100644 --- a/components/engine/hack/make/test +++ b/components/engine/hack/make/test @@ -1,3 +1,5 @@ +#!/bin/sh + DEST=$1 set -e @@ -9,7 +11,7 @@ bundle_test() { for test_dir in $(find_test_dirs); do ( set -x cd $test_dir - go test -v -ldflags "$LDFLAGS" + go test -v -ldflags "$LDFLAGS" $BUILDFLAGS ) done } 2>&1 | tee $DEST/test.log } From 7dc11f73bea561c646a957fa37f404377df64a19 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 2 Oct 2013 20:18:15 -0700 Subject: [PATCH 119/301] Random improvments Upstream-commit: 8b2f4aab232880cb0d7faa18ae12adb33f483b2c Component: engine --- components/engine/Dockerfile | 59 +-- components/engine/api.go | 1 + components/engine/deviceset.go | 3 +- .../engine/devmapper/deviceset_devmapper.go | 379 ++++++++---------- components/engine/devmapper/devmapper.go | 128 +++--- components/engine/docker/docker.go | 2 +- components/engine/hack/make/test | 2 +- components/engine/image.go | 46 +-- components/engine/server.go | 14 +- components/engine/utils/utils.go | 10 +- 10 files changed, 300 insertions(+), 344 deletions(-) diff --git a/components/engine/Dockerfile b/components/engine/Dockerfile index 1c29ed7615..cf0a04b8ba 100644 --- a/components/engine/Dockerfile +++ b/components/engine/Dockerfile @@ -24,54 +24,57 @@ # docker-version 0.6.1 -from ubuntu:12.04 -maintainer Solomon Hykes +from ubuntu:12.10 +maintainer Solomon Hykes # Build dependencies -run echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' > /etc/apt/sources.list -run apt-get update -run apt-get install -y -q curl -run apt-get install -y -q git -run apt-get install -y -q mercurial -run apt-get install -y -q build-essential +run apt-get update +run apt-get install -y -q curl +run apt-get install -y -q git +run apt-get install -y -q mercurial +run apt-get install -y -q build-essential # Install Go from source (for eventual cross-compiling) -run curl -s https://go.googlecode.com/files/go1.2rc1.src.tar.gz | tar -v -C / -xz && mv /go /goroot -run cd /goroot/src && ./make.bash -env GOROOT /goroot -env PATH $PATH:/goroot/bin -env GOPATH /go:/go/src/github.com/dotcloud/docker/vendor +run curl -s https://go.googlecode.com/files/go1.2rc1.src.tar.gz | tar -v -C / -xz && mv /go /goroot +run cd /goroot/src && ./make.bash +env GOROOT /goroot +env PATH $PATH:/goroot/bin +env GOPATH /go:/go/src/github.com/dotcloud/docker/vendor # Create Go cache with tag netgo (for static compilation of Go while preserving CGO support) -run go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std +run go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std # Get lvm2 source for compiling statically -run git clone git://git.fedorahosted.org/git/lvm2.git /lvm2 -run cd /lvm2 && git checkout v2_02_102 +run git clone git://git.fedorahosted.org/git/lvm2.git /lvm2 +run cd /lvm2 && git checkout v2_02_102 + # can't use git clone -b because it's not supported by git versions before 1.7.10 run cd /lvm2 && ./configure --enable-static_link && make && make install_device-mapper # see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags # Ubuntu stuff -run apt-get install -y -q ruby1.9.3 rubygems libffi-dev -run gem install --no-rdoc --no-ri fpm -run apt-get install -y -q reprepro dpkg-sig +run apt-get install -y -q ruby1.9.3 rubygems libffi-dev +run gem install --no-rdoc --no-ri fpm +run apt-get install -y -q reprepro dpkg-sig # Install s3cmd 1.0.1 (earlier versions don't support env variables in the config) -run apt-get install -y -q python-pip -run pip install s3cmd -run pip install python-magic -run /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY\n' > /.s3cfg +run apt-get install -y -q python-pip +run pip install s3cmd +run pip install python-magic +run /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY\n' > /.s3cfg # Runtime dependencies -run apt-get install -y -q iptables -run apt-get install -y -q lxc +run apt-get install -y -q iptables +run dpkg-divert --local --rename --add /sbin/initctl && \ + ln -s /bin/true /sbin/initctl && \ + apt-get install -y -q lxc -volume /var/lib/docker -workdir /go/src/github.com/dotcloud/docker +volume /var/lib/docker +workdir /go/src/github.com/dotcloud/docker # Wrap all commands in the "docker-in-docker" script to allow nested containers entrypoint ["hack/dind"] # Upload docker source -add . /go/src/github.com/dotcloud/docker +add . /go/src/github.com/dotcloud/docker + diff --git a/components/engine/api.go b/components/engine/api.go index bc0c436966..339ae68d48 100644 --- a/components/engine/api.go +++ b/components/engine/api.go @@ -620,6 +620,7 @@ func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r } name := vars["name"] if err := srv.ContainerStart(name, hostConfig); err != nil { + utils.Debugf("error ContainerStart: %s", err) return err } w.WriteHeader(http.StatusNoContent) diff --git a/components/engine/deviceset.go b/components/engine/deviceset.go index 8e619ca248..7aca816f43 100644 --- a/components/engine/deviceset.go +++ b/components/engine/deviceset.go @@ -66,9 +66,8 @@ func (wrapper *DeviceSetWrapper) HasActivatedDevice(hash string) bool { } func NewDeviceSetWrapper(wrapped DeviceSet, prefix string) DeviceSet { - wrapper := &DeviceSetWrapper{ + return &DeviceSetWrapper{ wrapped: wrapped, prefix: prefix, } - return wrapper } diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index 58da0db97d..bad3b7a017 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -1,12 +1,11 @@ package devmapper import ( - "github.com/dotcloud/docker/utils" "encoding/json" "fmt" + "github.com/dotcloud/docker/utils" "io" "io/ioutil" - "log" "os" "os/exec" "path" @@ -15,9 +14,11 @@ import ( "syscall" ) -const defaultDataLoopbackSize int64 = 100 * 1024 * 1024 * 1024 -const defaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024 -const defaultBaseFsSize uint64 = 10 * 1024 * 1024 * 1024 +const ( + defaultDataLoopbackSize int64 = 100 * 1024 * 1024 * 1024 + defaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024 + defaultBaseFsSize uint64 = 10 * 1024 * 1024 * 1024 +) type DevInfo struct { Hash string `json:"-"` @@ -33,14 +34,14 @@ type MetaData struct { } type DeviceSetDM struct { - initialized bool - root string - devicePrefix string MetaData + initialized bool + root string + devicePrefix string TransactionId uint64 NewTransactionId uint64 nextFreeDevice int - activeMounts map[string]int + activeMounts map[string]int } func getDevName(name string) string { @@ -68,7 +69,7 @@ func (devices *DeviceSetDM) jsonFile() string { } func (devices *DeviceSetDM) getPoolName() string { - return fmt.Sprintf("%s-pool", devices.devicePrefix) + return devices.devicePrefix + "-pool" } func (devices *DeviceSetDM) getPoolDevName() string { @@ -80,8 +81,7 @@ func (devices *DeviceSetDM) createTask(t TaskType, name string) (*Task, error) { if task == nil { return nil, fmt.Errorf("Can't create task of type %d", int(t)) } - err := task.SetName(name) - if err != nil { + if err := task.SetName(name); err != nil { return nil, fmt.Errorf("Can't set task name %s", name) } return task, nil @@ -92,60 +92,53 @@ func (devices *DeviceSetDM) getInfo(name string) (*Info, error) { if task == nil { return nil, err } - err = task.Run() - if err != nil { + if err := task.Run(); err != nil { return nil, err } - info, err := task.GetInfo() - if err != nil { - return nil, err - } - return info, nil + return task.GetInfo() } func (devices *DeviceSetDM) getStatus(name string) (uint64, uint64, string, string, error) { task, err := devices.createTask(DeviceStatus, name) if task == nil { + utils.Debugf("getStatus: Error createTask: %s", err) return 0, 0, "", "", err } - err = task.Run() - if err != nil { + if err := task.Run(); err != nil { + utils.Debugf("getStatus: Error Run: %s", err) return 0, 0, "", "", err } devinfo, err := task.GetInfo() if err != nil { + utils.Debugf("getStatus: Error GetInfo: %s", err) return 0, 0, "", "", err } if devinfo.Exists == 0 { + utils.Debugf("getStatus: Non existing device %s", name) return 0, 0, "", "", fmt.Errorf("Non existing device %s", name) } - var next uintptr = 0 - next, start, length, target_type, params := task.GetNextTarget(next) - + _, start, length, target_type, params := task.GetNextTarget(0) return start, length, target_type, params, nil } func (devices *DeviceSetDM) setTransactionId(oldId uint64, newId uint64) error { task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) if task == nil { + utils.Debugf("\n--->Err: %s\n", err) return err } - err = task.SetSector(0) - if err != nil { + if err := task.SetSector(0); err != nil { return fmt.Errorf("Can't set sector") } - message := fmt.Sprintf("set_transaction_id %d %d", oldId, newId) - err = task.SetMessage(message) - if err != nil { + if err := task.SetMessage(fmt.Sprintf("set_transaction_id %d %d", oldId, newId)); err != nil { return fmt.Errorf("Can't set message") } - err = task.Run() - if err != nil { + if err := task.Run(); err != nil { return fmt.Errorf("Error running setTransactionId") } return nil @@ -167,18 +160,17 @@ func (devices *DeviceSetDM) ensureImage(name string, size int64) (string, error) return "", err } - _, err := os.Stat(filename) - if err != nil { + if _, err := os.Stat(filename); err != nil { if !os.IsNotExist(err) { return "", err } - log.Printf("Creating loopback file %s for device-manage use", filename) + utils.Debugf("Creating loopback file %s for device-manage use", filename) file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0600) if err != nil { return "", err } - err = file.Truncate(size) - if err != nil { + + if err = file.Truncate(size); err != nil { return "", err } } @@ -189,6 +181,7 @@ func (devices *DeviceSetDM) createPool(dataFile *os.File, metadataFile *os.File) utils.Debugf("Activating device-mapper pool %s", devices.getPoolName()) task, err := devices.createTask(DeviceCreate, devices.getPoolName()) if task == nil { + utils.Debugf("\n--->Err: %s\n", err) return err } @@ -198,19 +191,16 @@ func (devices *DeviceSetDM) createPool(dataFile *os.File, metadataFile *os.File) } params := metadataFile.Name() + " " + dataFile.Name() + " 512 8192" - err = task.AddTarget(0, size/512, "thin-pool", params) - if err != nil { + if err := task.AddTarget(0, size/512, "thin-pool", params); err != nil { return fmt.Errorf("Can't add target") } var cookie uint32 = 0 - err = task.SetCookie(&cookie, 0) - if err != nil { + if err := task.SetCookie(&cookie, 0); err != nil { return fmt.Errorf("Can't set cookie") } - err = task.Run() - if err != nil { + if err := task.Run(); err != nil { return fmt.Errorf("Error running DeviceCreate") } @@ -222,10 +212,10 @@ func (devices *DeviceSetDM) createPool(dataFile *os.File, metadataFile *os.File) func (devices *DeviceSetDM) suspendDevice(info *DevInfo) error { task, err := devices.createTask(DeviceSuspend, info.Name()) if task == nil { + utils.Debugf("\n--->Err: %s\n", err) return err } - err = task.Run() - if err != nil { + if err := task.Run(); err != nil { return fmt.Errorf("Error running DeviceSuspend") } return nil @@ -234,17 +224,16 @@ func (devices *DeviceSetDM) suspendDevice(info *DevInfo) error { func (devices *DeviceSetDM) resumeDevice(info *DevInfo) error { task, err := devices.createTask(DeviceResume, info.Name()) if task == nil { + utils.Debugf("\n--->Err: %s\n", err) return err } var cookie uint32 = 0 - err = task.SetCookie(&cookie, 0) - if err != nil { + if err := task.SetCookie(&cookie, 0); err != nil { return fmt.Errorf("Can't set cookie") } - err = task.Run() - if err != nil { + if err := task.Run(); err != nil { return fmt.Errorf("Error running DeviceSuspend") } @@ -256,68 +245,60 @@ func (devices *DeviceSetDM) resumeDevice(info *DevInfo) error { func (devices *DeviceSetDM) createDevice(deviceId int) error { task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) if task == nil { + utils.Debugf("\n--->Err: %s\n", err) return err } - err = task.SetSector(0) - if err != nil { + if err := task.SetSector(0); err != nil { return fmt.Errorf("Can't set sector") } - message := fmt.Sprintf("create_thin %d", deviceId) - err = task.SetMessage(message) - if err != nil { + if err := task.SetMessage(fmt.Sprintf("create_thin %d", deviceId)); err != nil { return fmt.Errorf("Can't set message") } - err = task.Run() - if err != nil { + if err := task.Run(); err != nil { return fmt.Errorf("Error running createDevice") } return nil } func (devices *DeviceSetDM) createSnapDevice(deviceId int, baseInfo *DevInfo) error { - doSuspend := false devinfo, _ := devices.getInfo(baseInfo.Name()) - if devinfo != nil && devinfo.Exists != 0 { - doSuspend = true - } + doSuspend := devinfo != nil && devinfo.Exists != 0 if doSuspend { - err := devices.suspendDevice(baseInfo) - if err != nil { + if err := devices.suspendDevice(baseInfo); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } } task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) if task == nil { - _ = devices.resumeDevice(baseInfo) + devices.resumeDevice(baseInfo) + utils.Debugf("\n--->Err: %s\n", err) return err } - err = task.SetSector(0) - if err != nil { - _ = devices.resumeDevice(baseInfo) + + if err := task.SetSector(0); err != nil { + devices.resumeDevice(baseInfo) return fmt.Errorf("Can't set sector") } - message := fmt.Sprintf("create_snap %d %d", deviceId, baseInfo.DeviceId) - err = task.SetMessage(message) - if err != nil { - _ = devices.resumeDevice(baseInfo) + if err := task.SetMessage(fmt.Sprintf("create_snap %d %d", deviceId, baseInfo.DeviceId)); err != nil { + devices.resumeDevice(baseInfo) return fmt.Errorf("Can't set message") } - err = task.Run() - if err != nil { - _ = devices.resumeDevice(baseInfo) + if err := task.Run(); err != nil { + devices.resumeDevice(baseInfo) return fmt.Errorf("Error running DeviceCreate") } if doSuspend { - err = devices.resumeDevice(baseInfo) - if err != nil { + if err := devices.resumeDevice(baseInfo); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } } @@ -328,22 +309,19 @@ func (devices *DeviceSetDM) createSnapDevice(deviceId int, baseInfo *DevInfo) er func (devices *DeviceSetDM) deleteDevice(deviceId int) error { task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) if task == nil { + utils.Debugf("\n--->Err: %s\n", err) return err } - err = task.SetSector(0) - if err != nil { + if err := task.SetSector(0); err != nil { return fmt.Errorf("Can't set sector") } - message := fmt.Sprintf("delete %d", deviceId) - err = task.SetMessage(message) - if err != nil { + if err := task.SetMessage(fmt.Sprintf("delete %d", deviceId)); err != nil { return fmt.Errorf("Can't set message") } - err = task.Run() - if err != nil { + if err := task.Run(); err != nil { return fmt.Errorf("Error running deleteDevice") } return nil @@ -352,10 +330,10 @@ func (devices *DeviceSetDM) deleteDevice(deviceId int) error { func (devices *DeviceSetDM) removeDevice(name string) error { task, err := devices.createTask(DeviceRemove, name) if task == nil { + utils.Debugf("\n--->Err: %s\n", err) return err } - err = task.Run() - if err != nil { + if err = task.Run(); err != nil { return fmt.Errorf("Error running removeDevice") } return nil @@ -364,23 +342,21 @@ func (devices *DeviceSetDM) removeDevice(name string) error { func (devices *DeviceSetDM) activateDevice(info *DevInfo) error { task, err := devices.createTask(DeviceCreate, info.Name()) if task == nil { + utils.Debugf("\n--->Err: %s\n", err) return err } params := fmt.Sprintf("%s %d", devices.getPoolDevName(), info.DeviceId) - err = task.AddTarget(0, info.Size/512, "thin", params) - if err != nil { + if err := task.AddTarget(0, info.Size/512, "thin", params); err != nil { return fmt.Errorf("Can't add target") } var cookie uint32 = 0 - err = task.SetCookie(&cookie, 0) - if err != nil { + if err := task.SetCookie(&cookie, 0); err != nil { return fmt.Errorf("Can't set cookie") } - err = task.Run() - if err != nil { + if err := task.Run(); err != nil { return fmt.Errorf("Error running DeviceCreate") } @@ -404,61 +380,60 @@ func (devices *DeviceSetDM) allocateTransactionId() uint64 { func (devices *DeviceSetDM) saveMetadata() error { jsonData, err := json.Marshal(devices.MetaData) if err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } tmpFile, err := ioutil.TempFile(filepath.Dir(devices.jsonFile()), ".json") if err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } n, err := tmpFile.Write(jsonData) if err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } if n < len(jsonData) { - err = io.ErrShortWrite + return io.ErrShortWrite } - err = tmpFile.Sync() - if err != nil { + if err := tmpFile.Sync(); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } - err = tmpFile.Close() - if err != nil { + if err := tmpFile.Close(); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } - err = os.Rename(tmpFile.Name(), devices.jsonFile()) - if err != nil { + if err := os.Rename(tmpFile.Name(), devices.jsonFile()); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } if devices.NewTransactionId != devices.TransactionId { - err = devices.setTransactionId(devices.TransactionId, devices.NewTransactionId) - if err != nil { + if err = devices.setTransactionId(devices.TransactionId, devices.NewTransactionId); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } devices.TransactionId = devices.NewTransactionId } - return nil } func (devices *DeviceSetDM) registerDevice(id int, hash string, size uint64) (*DevInfo, error) { - transaction := devices.allocateTransactionId() - info := &DevInfo{ Hash: hash, DeviceId: id, Size: size, - TransactionId: transaction, + TransactionId: devices.allocateTransactionId(), Initialized: false, devices: devices, } devices.Devices[hash] = info - err := devices.saveMetadata() - if err != nil { + if err := devices.saveMetadata(); err != nil { // Try to remove unused device - devices.Devices[hash] = nil + delete(devices.Devices, hash) return nil, err } @@ -471,9 +446,7 @@ func (devices *DeviceSetDM) activateDeviceIfNeeded(hash string) error { return fmt.Errorf("Unknown device %s", hash) } - name := info.Name() - devinfo, _ := devices.getInfo(name) - if devinfo != nil && devinfo.Exists != 0 { + if devinfo, _ := devices.getInfo(info.Name()); devinfo != nil && devinfo.Exists != 0 { return nil } @@ -483,13 +456,12 @@ func (devices *DeviceSetDM) activateDeviceIfNeeded(hash string) error { func (devices *DeviceSetDM) createFilesystem(info *DevInfo) error { devname := info.DevName() - err := exec.Command("mkfs.ext4", "-E", - "discard,lazy_itable_init=0,lazy_journal_init=0", devname).Run() + err := exec.Command("mkfs.ext4", "-E", "discard,lazy_itable_init=0,lazy_journal_init=0", devname).Run() if err != nil { - err = exec.Command("mkfs.ext4", "-E", - "discard,lazy_itable_init=0", devname).Run() + err = exec.Command("mkfs.ext4", "-E", "discard,lazy_itable_init=0", devname).Run() } if err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } return nil @@ -498,31 +470,29 @@ func (devices *DeviceSetDM) createFilesystem(info *DevInfo) error { func (devices *DeviceSetDM) loadMetaData() error { _, _, _, params, err := devices.getStatus(devices.getPoolName()) if err != nil { - return err - } - var currentTransaction uint64 - _, err = fmt.Sscanf(params, "%d", ¤tTransaction) - if err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } - devices.TransactionId = currentTransaction + if _, err := fmt.Sscanf(params, "%d", &devices.TransactionId); err != nil { + utils.Debugf("\n--->Err: %s\n", err) + return err + } devices.NewTransactionId = devices.TransactionId jsonData, err := ioutil.ReadFile(devices.jsonFile()) if err != nil && !os.IsNotExist(err) { + utils.Debugf("\n--->Err: %s\n", err) return err } - metadata := &MetaData{ - Devices: make(map[string]*DevInfo), - } + devices.MetaData.Devices = make(map[string]*DevInfo) if jsonData != nil { - if err := json.Unmarshal(jsonData, metadata); err != nil { + if err := json.Unmarshal(jsonData, &devices.MetaData); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } } - devices.MetaData = *metadata for hash, d := range devices.Devices { d.Hash = hash @@ -533,12 +503,11 @@ func (devices *DeviceSetDM) loadMetaData() error { } // If the transaction id is larger than the actual one we lost the device due to some crash - if d.TransactionId > currentTransaction { - log.Printf("Removing lost device %s with id %d", hash, d.TransactionId) + if d.TransactionId > devices.TransactionId { + utils.Debugf("Removing lost device %s with id %d", hash, d.TransactionId) delete(devices.Devices, hash) } } - return nil } @@ -549,45 +518,46 @@ func (devices *DeviceSetDM) setupBaseImage() error { } if oldInfo != nil && !oldInfo.Initialized { - log.Printf("Removing uninitialized base image") + utils.Debugf("Removing uninitialized base image") if err := devices.RemoveDevice(""); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } } - log.Printf("Initializing base device-manager snapshot") + utils.Debugf("Initializing base device-manager snapshot") id := devices.allocateDeviceId() // Create initial device - err := devices.createDevice(id) - if err != nil { + if err := devices.createDevice(id); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } info, err := devices.registerDevice(id, "", defaultBaseFsSize) if err != nil { _ = devices.deleteDevice(id) + utils.Debugf("\n--->Err: %s\n", err) return err } - log.Printf("Creating filesystem on base device-manager snapshot") + utils.Debugf("Creating filesystem on base device-manager snapshot") - err = devices.activateDeviceIfNeeded("") - if err != nil { + if err = devices.activateDeviceIfNeeded(""); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } - err = devices.createFilesystem(info) - if err != nil { + if err := devices.createFilesystem(info); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } info.Initialized = true - - err = devices.saveMetadata() - if err != nil { + if err = devices.saveMetadata(); err != nil { info.Initialized = false + utils.Debugf("\n--->Err: %s\n", err) return err } @@ -597,64 +567,67 @@ func (devices *DeviceSetDM) setupBaseImage() error { func (devices *DeviceSetDM) initDevmapper() error { info, err := devices.getInfo(devices.getPoolName()) if info == nil { + utils.Debugf("Error device getInfo: %s", err) return err } + utils.Debugf("initDevmapper(). Pool exists: %v", info.Exists) if info.Exists != 0 { /* Pool exists, assume everything is up */ - err = devices.loadMetaData() - if err != nil { + if err := devices.loadMetaData(); err != nil { + utils.Debugf("Error device loadMetaData: %s\n", err) return err } - err = devices.setupBaseImage() - if err != nil { + if err := devices.setupBaseImage(); err != nil { + utils.Debugf("Error device setupBaseImage: %s\n", err) return err } return nil } - createdLoopback := false - if !devices.hasImage("data") || !devices.hasImage("metadata") { - /* If we create the loopback mounts we also need to initialize the base fs */ - createdLoopback = true - } + /* If we create the loopback mounts we also need to initialize the base fs */ + createdLoopback := !devices.hasImage("data") || !devices.hasImage("metadata") data, err := devices.ensureImage("data", defaultDataLoopbackSize) if err != nil { + utils.Debugf("Error device ensureImage (data): %s\n", err) return err } metadata, err := devices.ensureImage("metadata", defaultMetaDataLoopbackSize) if err != nil { + utils.Debugf("Error device ensureImage (metadata): %s\n", err) return err } dataFile, err := AttachLoopDevice(data) if err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } defer dataFile.Close() metadataFile, err := AttachLoopDevice(metadata) if err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } defer metadataFile.Close() - err = devices.createPool(dataFile, metadataFile) - if err != nil { + if err := devices.createPool(dataFile, metadataFile); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } if !createdLoopback { - err = devices.loadMetaData() - if err != nil { + if err = devices.loadMetaData(); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } } - err = devices.setupBaseImage() - if err != nil { + if err := devices.setupBaseImage(); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } @@ -663,6 +636,7 @@ func (devices *DeviceSetDM) initDevmapper() error { func (devices *DeviceSetDM) AddDevice(hash, baseHash string) error { if err := devices.ensureInit(); err != nil { + utils.Debugf("Error init: %s\n", err) return err } @@ -672,19 +646,20 @@ func (devices *DeviceSetDM) AddDevice(hash, baseHash string) error { baseInfo := devices.Devices[baseHash] if baseInfo == nil { + utils.Debugf("Base Hash not found") return fmt.Errorf("Unknown base hash %s", baseHash) } deviceId := devices.allocateDeviceId() - err := devices.createSnapDevice(deviceId, baseInfo) - if err != nil { + if err := devices.createSnapDevice(deviceId, baseInfo); err != nil { + utils.Debugf("Error creating snap device: %s\n", err) return err } - _, err = devices.registerDevice(deviceId, hash, baseInfo.Size) - if err != nil { - _ = devices.deleteDevice(deviceId) + if _, err := devices.registerDevice(deviceId, hash, baseInfo.Size); err != nil { + devices.deleteDevice(deviceId) + utils.Debugf("Error registering device: %s\n", err) return err } return nil @@ -692,6 +667,7 @@ func (devices *DeviceSetDM) AddDevice(hash, baseHash string) error { func (devices *DeviceSetDM) RemoveDevice(hash string) error { if err := devices.ensureInit(); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } @@ -702,31 +678,31 @@ func (devices *DeviceSetDM) RemoveDevice(hash string) error { devinfo, _ := devices.getInfo(info.Name()) if devinfo != nil && devinfo.Exists != 0 { - err := devices.removeDevice(info.Name()) - if err != nil { + if err := devices.removeDevice(info.Name()); err != nil { + utils.Debugf("Error removing device: %s\n", err) return err } } if info.Initialized { info.Initialized = false - err := devices.saveMetadata() - if err != nil { + if err := devices.saveMetadata(); err != nil { + utils.Debugf("Error saving meta data: %s\n", err) return err } } - err := devices.deleteDevice(info.DeviceId) - if err != nil { + if err := devices.deleteDevice(info.DeviceId); err != nil { + utils.Debugf("Error deleting device: %s\n", err) return err } - _ = devices.allocateTransactionId() + devices.allocateTransactionId() delete(devices.Devices, info.Hash) - err = devices.saveMetadata() - if err != nil { + if err := devices.saveMetadata(); err != nil { devices.Devices[info.Hash] = info + utils.Debugf("Error saving meta data: %s\n", err) return err } @@ -735,6 +711,7 @@ func (devices *DeviceSetDM) RemoveDevice(hash string) error { func (devices *DeviceSetDM) DeactivateDevice(hash string) error { if err := devices.ensureInit(); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } @@ -745,11 +722,12 @@ func (devices *DeviceSetDM) DeactivateDevice(hash string) error { devinfo, err := devices.getInfo(info.Name()) if err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } if devinfo.Exists != 0 { - err := devices.removeDevice(info.Name()) - if err != nil { + if err := devices.removeDevice(info.Name()); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } } @@ -764,9 +742,8 @@ func (devices *DeviceSetDM) Shutdown() error { for path, count := range devices.activeMounts { for i := count; i > 0; i-- { - err := syscall.Unmount(path, 0) - if err != nil { - fmt.Printf("Shutdown unmounting %s, error: %s\n", path, err) + if err := syscall.Unmount(path, 0); err != nil { + utils.Debugf("Shutdown unmounting %s, error: %s\n", path, err) } } delete(devices.activeMounts, path) @@ -774,16 +751,14 @@ func (devices *DeviceSetDM) Shutdown() error { for _, d := range devices.Devices { if err := devices.DeactivateDevice(d.Hash); err != nil { - fmt.Printf("Shutdown deactivate %s , error: %s\n", d.Hash, err) + utils.Debugf("Shutdown deactivate %s , error: %s\n", d.Hash, err) } } - pool := devices.getPoolDevName() - devinfo, err := devices.getInfo(pool) - if err == nil && devinfo.Exists != 0 { + if devinfo, err := devices.getInfo(pool); err == nil && devinfo.Exists != 0 { if err := devices.removeDevice(pool); err != nil { - fmt.Printf("Shutdown deactivate %s , error: %s\n", pool, err) + utils.Debugf("Shutdown deactivate %s , error: %s\n", pool, err) } } @@ -792,21 +767,23 @@ func (devices *DeviceSetDM) Shutdown() error { func (devices *DeviceSetDM) MountDevice(hash, path string) error { if err := devices.ensureInit(); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } - err := devices.activateDeviceIfNeeded(hash) - if err != nil { + if err := devices.activateDeviceIfNeeded(hash); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } info := devices.Devices[hash] - err = syscall.Mount(info.DevName(), path, "ext4", syscall.MS_MGC_VAL, "discard") + err := syscall.Mount(info.DevName(), path, "ext4", syscall.MS_MGC_VAL, "discard") if err != nil && err == syscall.EINVAL { err = syscall.Mount(info.DevName(), path, "ext4", syscall.MS_MGC_VAL, "") } if err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } @@ -817,13 +794,12 @@ func (devices *DeviceSetDM) MountDevice(hash, path string) error { } func (devices *DeviceSetDM) UnmountDevice(hash, path string) error { - err := syscall.Unmount(path, 0) - if err != nil { + if err := syscall.Unmount(path, 0); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } - count := devices.activeMounts[path] - if count > 1 { + if count := devices.activeMounts[path]; count > 1 { devices.activeMounts[path] = count - 1 } else { delete(devices.activeMounts, path) @@ -832,14 +808,11 @@ func (devices *DeviceSetDM) UnmountDevice(hash, path string) error { return nil } - func (devices *DeviceSetDM) HasDevice(hash string) bool { if err := devices.ensureInit(); err != nil { return false } - - info := devices.Devices[hash] - return info != nil + return devices.Devices[hash] != nil } func (devices *DeviceSetDM) HasInitializedDevice(hash string) bool { @@ -860,16 +833,13 @@ func (devices *DeviceSetDM) HasActivatedDevice(hash string) bool { if info == nil { return false } - name := info.Name() - devinfo, _ := devices.getInfo(name) - if devinfo != nil && devinfo.Exists != 0 { - return true - } - return false + devinfo, _ := devices.getInfo(info.Name()) + return devinfo != nil && devinfo.Exists != 0 } func (devices *DeviceSetDM) SetInitialized(hash string) error { if err := devices.ensureInit(); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } @@ -879,9 +849,9 @@ func (devices *DeviceSetDM) SetInitialized(hash string) error { } info.Initialized = true - err := devices.saveMetadata() - if err != nil { + if err := devices.saveMetadata(); err != nil { info.Initialized = false + utils.Debugf("\n--->Err: %s\n", err) return err } @@ -889,10 +859,11 @@ func (devices *DeviceSetDM) SetInitialized(hash string) error { } func (devices *DeviceSetDM) ensureInit() error { + utils.Debugf("ensureInit(). Initialized: %v", devices.initialized) if !devices.initialized { devices.initialized = true - err := devices.initDevmapper() - if err != nil { + if err := devices.initDevmapper(); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } } @@ -907,13 +878,11 @@ func NewDeviceSetDM(root string) *DeviceSetDM { base = "docker-" + base } - devices := &DeviceSetDM{ - initialized: false, - root: root, + return &DeviceSetDM{ + initialized: false, + root: root, devicePrefix: base, + MetaData: MetaData{Devices: make(map[string]*DevInfo)}, + activeMounts: make(map[string]int), } - devices.Devices = make(map[string]*DevInfo) - devices.activeMounts = make(map[string]int) - - return devices } diff --git a/components/engine/devmapper/devmapper.go b/components/engine/devmapper/devmapper.go index 8458cb3ed9..3799fd2df9 100644 --- a/components/engine/devmapper/devmapper.go +++ b/components/engine/devmapper/devmapper.go @@ -108,30 +108,32 @@ get_block_size(int fd) */ import "C" -import "unsafe" -import "fmt" -import "runtime" -import "os" + +import ( + "fmt" + "github.com/dotcloud/docker/utils" + "os" + "runtime" + "unsafe" +) func SetDevDir(dir string) error { c_dir := C.CString(dir) defer C.free(unsafe.Pointer(c_dir)) - res := C.dm_set_dev_dir(c_dir) - if res != 1 { + + if res := C.dm_set_dev_dir(c_dir); res != 1 { + utils.Debugf("Error dm_set_dev_dir") return fmt.Errorf("dm_set_dev_dir failed") } return nil } func GetLibraryVersion() (string, error) { - buffer := (*C.char)(C.malloc(128)) - defer C.free(unsafe.Pointer(buffer)) - res := C.dm_get_library_version(buffer, 128) - if res != 1 { + buffer := make([]byte, 128) + if res := C.dm_get_library_version((*C.char)(unsafe.Pointer(&buffer)), 128); res != 1 { return "", fmt.Errorf("dm_get_library_version failed") - } else { - return C.GoString(buffer), nil } + return string(buffer), nil } type TaskType int @@ -183,18 +185,17 @@ func (t *Task) destroy() { } func TaskCreate(tasktype TaskType) *Task { - c_task := C.dm_task_create(C.int(int(tasktype))) + c_task := C.dm_task_create(C.int(tasktype)) if c_task == nil { return nil } - task := &Task{c_task} + task := &Task{unmanaged: c_task} runtime.SetFinalizer(task, (*Task).destroy) return task } func (t *Task) Run() error { - res := C.dm_task_run(t.unmanaged) - if res != 1 { + if res := C.dm_task_run(t.unmanaged); res != 1 { return fmt.Errorf("dm_task_run failed") } return nil @@ -204,8 +205,10 @@ func (t *Task) SetName(name string) error { c_name := C.CString(name) defer C.free(unsafe.Pointer(c_name)) - res := C.dm_task_set_name(t.unmanaged, c_name) - if res != 1 { + if res := C.dm_task_set_name(t.unmanaged, c_name); res != 1 { + if os.Getenv("DEBUG") != "" { + C.perror(C.CString(fmt.Sprintf("[debug] Error dm_task_set_name(%s, %#v)", name, t.unmanaged))) + } return fmt.Errorf("dm_task_set_name failed") } return nil @@ -215,26 +218,22 @@ func (t *Task) SetMessage(message string) error { c_message := C.CString(message) defer C.free(unsafe.Pointer(c_message)) - res := C.dm_task_set_message(t.unmanaged, c_message) - if res != 1 { + if res := C.dm_task_set_message(t.unmanaged, c_message); res != 1 { return fmt.Errorf("dm_task_set_message failed") } return nil } func (t *Task) SetSector(sector uint64) error { - res := C.dm_task_set_sector(t.unmanaged, C.uint64_t(sector)) - if res != 1 { + if res := C.dm_task_set_sector(t.unmanaged, C.uint64_t(sector)); res != 1 { return fmt.Errorf("dm_task_set_add_node failed") } return nil } func (t *Task) SetCookie(cookie *uint32, flags uint16) error { - var c_cookie C.uint32_t - c_cookie = C.uint32_t(*cookie) - res := C.dm_task_set_cookie(t.unmanaged, &c_cookie, C.uint16_t(flags)) - if res != 1 { + c_cookie := C.uint32_t(*cookie) + if res := C.dm_task_set_cookie(t.unmanaged, &c_cookie, C.uint16_t(flags)); res != 1 { return fmt.Errorf("dm_task_set_add_node failed") } *cookie = uint32(c_cookie) @@ -242,8 +241,7 @@ func (t *Task) SetCookie(cookie *uint32, flags uint16) error { } func (t *Task) SetRo() error { - res := C.dm_task_set_ro(t.unmanaged) - if res != 1 { + if res := C.dm_task_set_ro(t.unmanaged); res != 1 { return fmt.Errorf("dm_task_set_ro failed") } return nil @@ -256,8 +254,7 @@ func (t *Task) AddTarget(start uint64, size uint64, ttype string, params string) c_params := C.CString(params) defer C.free(unsafe.Pointer(c_params)) - res := C.dm_task_add_target(t.unmanaged, C.uint64_t(start), C.uint64_t(size), c_ttype, c_params) - if res != 1 { + if res := C.dm_task_add_target(t.unmanaged, C.uint64_t(start), C.uint64_t(size), c_ttype, c_params); res != 1 { return fmt.Errorf("dm_task_add_target failed") } return nil @@ -267,49 +264,39 @@ func (t *Task) GetDriverVersion() (string, error) { buffer := (*C.char)(C.malloc(128)) defer C.free(unsafe.Pointer(buffer)) - res := C.dm_task_get_driver_version(t.unmanaged, buffer, 128) - if res != 1 { + if res := C.dm_task_get_driver_version(t.unmanaged, buffer, 128); res != 1 { return "", fmt.Errorf("dm_task_get_driver_version") - } else { - return C.GoString(buffer), nil } + return C.GoString(buffer), nil } func (t *Task) GetInfo() (*Info, error) { c_info := C.struct_dm_info{} - res := C.dm_task_get_info(t.unmanaged, &c_info) - if res != 1 { + if res := C.dm_task_get_info(t.unmanaged, &c_info); res != 1 { return nil, fmt.Errorf("dm_task_get_driver_version") - } else { - info := &Info{} - info.Exists = int(c_info.exists) - info.Suspended = int(c_info.suspended) - info.LiveTable = int(c_info.live_table) - info.InactiveTable = int(c_info.inactive_table) - info.OpenCount = int32(c_info.open_count) - info.EventNr = uint32(c_info.event_nr) - info.Major = uint32(c_info.major) - info.Minor = uint32(c_info.minor) - info.ReadOnly = int(c_info.read_only) - info.TargetCount = int32(c_info.target_count) - - return info, nil } + return &Info{ + Exists: int(c_info.exists), + Suspended: int(c_info.suspended), + LiveTable: int(c_info.live_table), + InactiveTable: int(c_info.inactive_table), + OpenCount: int32(c_info.open_count), + EventNr: uint32(c_info.event_nr), + Major: uint32(c_info.major), + Minor: uint32(c_info.minor), + ReadOnly: int(c_info.read_only), + TargetCount: int32(c_info.target_count), + }, nil } func (t *Task) GetNextTarget(next uintptr) (uintptr, uint64, uint64, string, string) { - nextp := unsafe.Pointer(next) - var c_start C.uint64_t - var c_length C.uint64_t - var c_target_type *C.char - var c_params *C.char + var ( + c_start, c_length C.uint64_t + c_target_type, c_params *C.char + ) - nextp = C.dm_get_next_target(t.unmanaged, nextp, &c_start, &c_length, &c_target_type, &c_params) - - target_type := C.GoString(c_target_type) - params := C.GoString(c_params) - - return uintptr(nextp), uint64(c_start), uint64(c_length), target_type, params + nextp := C.dm_get_next_target(t.unmanaged, unsafe.Pointer(next), &c_start, &c_length, &c_target_type, &c_params) + return uintptr(nextp), uint64(c_start), uint64(c_length), C.GoString(c_target_type), C.GoString(c_params) } func AttachLoopDevice(filename string) (*os.File, error) { @@ -321,19 +308,16 @@ func AttachLoopDevice(filename string) (*os.File, error) { if res == nil { return nil, fmt.Errorf("error loopback mounting") } - file := os.NewFile(uintptr(fd), C.GoString(res)) - C.free(unsafe.Pointer(res)) - return file, nil + defer C.free(unsafe.Pointer(res)) + return os.NewFile(uintptr(fd), C.GoString(res)), nil } func GetBlockDeviceSize(file *os.File) (uint64, error) { - fd := file.Fd() - size := C.get_block_size(C.int(fd)) - if size == -1 { + if size := C.get_block_size(C.int(file.Fd())); size == -1 { return 0, fmt.Errorf("Can't get block size") + } else { + return uint64(size), nil } - return uint64(size), nil - } func UdevWait(cookie uint32) error { @@ -354,12 +338,10 @@ func RemoveDevice(name string) error { if task == nil { return fmt.Errorf("Can't create task of type DeviceRemove") } - err := task.SetName(name) - if err != nil { + if err := task.SetName(name); err != nil { return fmt.Errorf("Can't set task name %s", name) } - err = task.Run() - if err != nil { + if err := task.Run(); err != nil { return fmt.Errorf("Error running removeDevice") } return nil diff --git a/components/engine/docker/docker.go b/components/engine/docker/docker.go index 4dcc174d5e..3ecf02d326 100644 --- a/components/engine/docker/docker.go +++ b/components/engine/docker/docker.go @@ -123,7 +123,7 @@ func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart defer removePidFile(pidfile) c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM)) + signal.Notify(c, os.Interrupt, os.Kill, syscall.SIGTERM) go func() { sig := <-c log.Printf("Received signal '%v', exiting\n", sig) diff --git a/components/engine/hack/make/test b/components/engine/hack/make/test index 86e77c5913..1dadbdab8e 100644 --- a/components/engine/hack/make/test +++ b/components/engine/hack/make/test @@ -11,7 +11,7 @@ bundle_test() { for test_dir in $(find_test_dirs); do ( set -x cd $test_dir - go test -v -ldflags "$LDFLAGS" $BUILDFLAGS + DEBUG=1 go test -v -ldflags "$LDFLAGS" $BUILDFLAGS -run TestRunHostname ) done } 2>&1 | tee $DEST/test.log } diff --git a/components/engine/image.go b/components/engine/image.go index 06036e1e62..175bcfc6ae 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -378,28 +378,28 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { } utils.Debugf("Creating device-mapper device for image id %s", image.ID) - err = devices.AddDevice(image.ID, image.Parent) - if err != nil { + if err := devices.AddDevice(image.ID, image.Parent); err != nil { + utils.Debugf("Error add device: %s", err) return err } - err = devices.MountDevice(image.ID, mountDir) - if err != nil { - _ = devices.RemoveDevice(image.ID) + if err := devices.MountDevice(image.ID, mountDir); err != nil { + utils.Debugf("Error mounting device: %s", err) + devices.RemoveDevice(image.ID) return err } - err = ioutil.WriteFile(path.Join(mountDir, ".docker-id"), []byte(image.ID), 0600) - if err != nil { - _ = devices.UnmountDevice(image.ID, mountDir) - _ = devices.RemoveDevice(image.ID) + if err := ioutil.WriteFile(path.Join(mountDir, ".docker-id"), []byte(image.ID), 0600); err != nil { + utils.Debugf("Error writing file: %s", err) + devices.UnmountDevice(image.ID, mountDir) + devices.RemoveDevice(image.ID) return err } - err = image.applyLayer(layerPath(root), mountDir) - if err != nil { - _ = devices.UnmountDevice(image.ID, mountDir) - _ = devices.RemoveDevice(image.ID) + if err = image.applyLayer(layerPath(root), mountDir); err != nil { + utils.Debugf("Error applying layer: %s", err) + devices.UnmountDevice(image.ID, mountDir) + devices.RemoveDevice(image.ID) return err } @@ -415,16 +415,15 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { _ = devices.RemoveDevice(image.ID) return err } - err = image.applyLayer(dockerinitLayer, mountDir) - if err != nil { - _ = devices.UnmountDevice(image.ID, mountDir) - _ = devices.RemoveDevice(image.ID) + + if err := image.applyLayer(dockerinitLayer, mountDir); err != nil { + devices.UnmountDevice(image.ID, mountDir) + devices.RemoveDevice(image.ID) return err } - err = devices.UnmountDevice(image.ID, mountDir) - if err != nil { - _ = devices.RemoveDevice(image.ID) + if err := devices.UnmountDevice(image.ID, mountDir); err != nil { + devices.RemoveDevice(image.ID) return err } @@ -455,8 +454,8 @@ func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { if err != nil { return err } - err = image.ensureImageDevice(devices) - if err != nil { + + if err := image.ensureImageDevice(devices); err != nil { return err } @@ -471,8 +470,7 @@ func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { } utils.Debugf("Mounting container %s at %s for container", id, root) - err = devices.MountDevice(id, root) - if err != nil { + if err := devices.MountDevice(id, root); err != nil { return err } diff --git a/components/engine/server.go b/components/engine/server.go index f3081db81a..9c5519cd50 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -1307,7 +1307,7 @@ func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors if err != nil { return nil, err } - srv := &Server{ + runtime.srv = &Server{ runtime: runtime, enableCors: enableCors, pullingPool: make(map[string]struct{}), @@ -1316,18 +1316,14 @@ func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors listeners: make(map[string]chan utils.JSONMessage), reqFactory: nil, } - runtime.srv = srv - return srv, nil + return runtime.srv, nil } func (srv *Server) HTTPRequestFactory(metaHeaders map[string][]string) *utils.HTTPRequestFactory { if srv.reqFactory == nil { - ud := utils.NewHTTPUserAgentDecorator(srv.versionInfos()...) - md := &utils.HTTPMetaHeadersDecorator{ - Headers: metaHeaders, - } - factory := utils.NewHTTPRequestFactory(ud, md) - srv.reqFactory = factory + srv.reqFactory = utils.NewHTTPRequestFactory( + utils.NewHTTPUserAgentDecorator(srv.versionInfos()...), + &utils.HTTPMetaHeadersDecorator{Headers: metaHeaders}) } return srv.reqFactory } diff --git a/components/engine/utils/utils.go b/components/engine/utils/utils.go index 6daac53a03..138d5c9ca8 100644 --- a/components/engine/utils/utils.go +++ b/components/engine/utils/utils.go @@ -59,7 +59,15 @@ func Debugf(format string, a ...interface{}) { file = file[strings.LastIndex(file, "/")+1:] } - fmt.Fprintf(os.Stderr, fmt.Sprintf("[debug] %s:%d %s\n", file, line, format), a...) + _, file2, line2, ok := runtime.Caller(2) + if !ok { + file2 = "" + line2 = -1 + } else { + file2 = file2[strings.LastIndex(file2, "/")+1:] + } + + fmt.Fprintf(os.Stderr, fmt.Sprintf("[debug] %s:%d %s:%d %s\n", file, line, file2, line2, format), a...) } } From c41309385699da5abe45946acd5c0ed3ff84b6c8 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 3 Oct 2013 19:54:14 +0200 Subject: [PATCH 120/301] Be better at cleaning up leftover from earlier test runs When running the test inside a docker container we sometimes are left with leftover device nodes for device mapper devices that no longer exist. We were panic:ing in this case, but with this change we just remove such nodes. Upstream-commit: 7b58e15b08349dba35bf3813f77a0b900c2e4df5 Component: engine --- components/engine/runtime_test.go | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index 0a9b95a411..7c12832e30 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -10,6 +10,7 @@ import ( "log" "net" "os" + "path/filepath" "runtime" "strconv" "strings" @@ -85,6 +86,23 @@ func layerArchive(tarfile string) (io.Reader, error) { } // Remove any leftover device mapper devices from earlier runs of the unit tests +func removeDev(name string) { + path := filepath.Join("/dev/mapper", name) + fd, err := syscall.Open(path, syscall.O_RDONLY, 07777) + if err != nil { + if err == syscall.ENXIO { + // No device for this node, just remove it + os.Remove(path) + return + } + } else { + syscall.Close(fd) + } + if err := devmapper.RemoveDevice(name); err != nil { + panic(fmt.Errorf("Unable to remove existing device %s: %s", name, err)) + } +} + func cleanupDevMapper() { infos, _ := ioutil.ReadDir("/dev/mapper") if infos != nil { @@ -95,16 +113,12 @@ func cleanupDevMapper() { if name == "docker-unit-tests-devices-pool" { hasPool = true } else { - if err := devmapper.RemoveDevice(name); err != nil { - panic(fmt.Errorf("Unable to remove existing device %s: %s", name, err)) - } + removeDev(name) } } // We need to remove the pool last as the other devices block it if hasPool { - if err := devmapper.RemoveDevice("docker-unit-tests-devices-pool"); err != nil { - panic(fmt.Errorf("Unable to remove existing device docker-unit-tests-devices-pool: %s", name, err)) - } + removeDev("docker-unit-tests-devices-pool") } } } From 78b0694228f4777e8c62fd3fa93ccd8da74e7877 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 3 Oct 2013 21:00:16 +0200 Subject: [PATCH 121/301] Make sure we mark the libdevmapper /dev/mapper/control fd CLOEXEC We do a hack to mark it such, because otherwise lxc-start will not work. Upstream-commit: 1a1be5a87c2169def7b0b0c29fc5cbac850a00a5 Component: engine --- .../engine/devmapper/deviceset_devmapper.go | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index bad3b7a017..7e151a8a0d 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -10,6 +10,7 @@ import ( "os/exec" "path" "path/filepath" + "strconv" "strings" "syscall" ) @@ -564,6 +565,21 @@ func (devices *DeviceSetDM) setupBaseImage() error { return nil } +func setCloseOnExec(name string) { + fileInfos, _ := ioutil.ReadDir("/proc/self/fd") + if fileInfos != nil { + for _, i := range fileInfos { + link, _ := os.Readlink(filepath.Join("/proc/self/fd", i.Name())) + if link == name { + fd, err := strconv.Atoi(i.Name()) + if err == nil { + syscall.CloseOnExec(fd) + } + } + } + } +} + func (devices *DeviceSetDM) initDevmapper() error { info, err := devices.getInfo(devices.getPoolName()) if info == nil { @@ -572,6 +588,11 @@ func (devices *DeviceSetDM) initDevmapper() error { } utils.Debugf("initDevmapper(). Pool exists: %v", info.Exists) + // It seems libdevmapper opens this without O_CLOEXEC, and go exec will not close files + // that are not Close-on-exec, and lxc-start will die if it inherits any unexpected files, + // so we add this badhack to make sure it closes itself + setCloseOnExec("/dev/mapper/control") + if info.Exists != 0 { /* Pool exists, assume everything is up */ if err := devices.loadMetaData(); err != nil { From ff1de35e898b0be2a736a69d4079a1e4ff12ab02 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 3 Oct 2013 17:58:18 -0700 Subject: [PATCH 122/301] Small fixes Upstream-commit: b8439987184df45ee9571f9eb9d607518d96aeb8 Component: engine --- components/engine/api_test.go | 10 ++++++---- components/engine/image.go | 15 +++++++-------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/components/engine/api_test.go b/components/engine/api_test.go index ae8602882f..7223411b2d 100644 --- a/components/engine/api_test.go +++ b/components/engine/api_test.go @@ -336,9 +336,11 @@ func TestGetContainersJSON(t *testing.T) { } r := httptest.NewRecorder() - if err := getContainersJSON(srv, APIVERSION, r, req, nil); err != nil { - t.Fatal(err) - } + setTimeout(t, "getContainerJSON timed out", 5*time.Second, func() { + if err := getContainersJSON(srv, APIVERSION, r, req, nil); err != nil { + t.Fatal(err) + } + }) containers := []APIContainers{} if err := json.Unmarshal(r.Body.Bytes(), &containers); err != nil { t.Fatal(err) @@ -374,7 +376,7 @@ func TestGetContainersExport(t *testing.T) { } r := httptest.NewRecorder() - if err = getContainersExport(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil { + if err := getContainersExport(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil { t.Fatal(err) } diff --git a/components/engine/image.go b/components/engine/image.go index 175bcfc6ae..a937c74086 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -310,7 +310,7 @@ func (image *Image) applyLayer(layer, target string) error { update := updateTimes[i] O_PATH := 010000000 // Not in syscall yet - var err error = nil + var err error if update.mode&syscall.S_IFLNK == syscall.S_IFLNK { // Update time on the symlink via O_PATH + futimes(), if supported by the kernel @@ -321,7 +321,7 @@ func (image *Image) applyLayer(layer, target string) error { return err } else { syscall.Futimes(fd, update.time) - _ = syscall.Close(fd) + syscall.Close(fd) } } else { err = syscall.Utimes(update.path, update.time) @@ -411,8 +411,8 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { // part of the container changes dockerinitLayer, err := image.getDockerInitLayer() if err != nil { - _ = devices.UnmountDevice(image.ID, mountDir) - _ = devices.RemoveDevice(image.ID) + devices.UnmountDevice(image.ID, mountDir) + devices.RemoveDevice(image.ID) return err } @@ -491,8 +491,7 @@ func (image *Image) Unmount(runtime *Runtime, root string, id string) error { return err } - err = devices.UnmountDevice(id, root) - if err != nil { + if err = devices.UnmountDevice(id, root); err != nil { return err } @@ -519,9 +518,9 @@ func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, er } changes, err := ChangesDirs(root, rw) - _ = devices.UnmountDevice(image.ID, rw) + devices.UnmountDevice(image.ID, rw) if !wasActivated { - _ = devices.DeactivateDevice(image.ID) + devices.DeactivateDevice(image.ID) } if err != nil { return nil, err From baed3230a9d763595131872167f9d0abf99dcf46 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 3 Oct 2013 18:00:24 -0700 Subject: [PATCH 123/301] Small fixes Upstream-commit: f29c500d8d480547874425ba98a9381fdc9df89e Component: engine --- components/engine/deviceset.go | 59 ------ .../engine/devmapper/deviceset_devmapper.go | 1 + components/engine/devmapper/devmapper.go | 182 +++++++++++------- components/engine/utils_test.go | 59 ++++++ 4 files changed, 172 insertions(+), 129 deletions(-) diff --git a/components/engine/deviceset.go b/components/engine/deviceset.go index 7aca816f43..b2a968738c 100644 --- a/components/engine/deviceset.go +++ b/components/engine/deviceset.go @@ -12,62 +12,3 @@ type DeviceSet interface { HasActivatedDevice(hash string) bool Shutdown() error } - -type DeviceSetWrapper struct { - wrapped DeviceSet - prefix string -} - -func (wrapper *DeviceSetWrapper) wrap(hash string) string { - if hash != "" { - hash = wrapper.prefix + "-" + hash - } - return hash -} - -func (wrapper *DeviceSetWrapper) AddDevice(hash, baseHash string) error { - return wrapper.wrapped.AddDevice(wrapper.wrap(hash), wrapper.wrap(baseHash)) -} - -func (wrapper *DeviceSetWrapper) SetInitialized(hash string) error { - return wrapper.wrapped.SetInitialized(wrapper.wrap(hash)) -} - -func (wrapper *DeviceSetWrapper) DeactivateDevice(hash string) error { - return wrapper.wrapped.DeactivateDevice(wrapper.wrap(hash)) -} - -func (wrapper *DeviceSetWrapper) Shutdown() error { - return nil -} - -func (wrapper *DeviceSetWrapper) RemoveDevice(hash string) error { - return wrapper.wrapped.RemoveDevice(wrapper.wrap(hash)) -} - -func (wrapper *DeviceSetWrapper) MountDevice(hash, path string) error { - return wrapper.wrapped.MountDevice(wrapper.wrap(hash), path) -} - -func (wrapper *DeviceSetWrapper) UnmountDevice(hash, path string) error { - return wrapper.wrapped.UnmountDevice(wrapper.wrap(hash), path) -} - -func (wrapper *DeviceSetWrapper) HasDevice(hash string) bool { - return wrapper.wrapped.HasDevice(wrapper.wrap(hash)) -} - -func (wrapper *DeviceSetWrapper) HasInitializedDevice(hash string) bool { - return wrapper.wrapped.HasInitializedDevice(wrapper.wrap(hash)) -} - -func (wrapper *DeviceSetWrapper) HasActivatedDevice(hash string) bool { - return wrapper.wrapped.HasActivatedDevice(wrapper.wrap(hash)) -} - -func NewDeviceSetWrapper(wrapped DeviceSet, prefix string) DeviceSet { - return &DeviceSetWrapper{ - wrapped: wrapped, - prefix: prefix, - } -} diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index 7e151a8a0d..d9557659c7 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -442,6 +442,7 @@ func (devices *DeviceSetDM) registerDevice(id int, hash string, size uint64) (*D } func (devices *DeviceSetDM) activateDeviceIfNeeded(hash string) error { + utils.Debugf("activateDeviceIfNeeded()") info := devices.Devices[hash] if info == nil { return fmt.Errorf("Unknown device %s", hash) diff --git a/components/engine/devmapper/devmapper.go b/components/engine/devmapper/devmapper.go index 3799fd2df9..02922e836b 100644 --- a/components/engine/devmapper/devmapper.go +++ b/components/engine/devmapper/devmapper.go @@ -14,14 +14,13 @@ package devmapper #include #include -static char * -attach_loop_device(const char *filename, int *loop_fd_out) +char* attach_loop_device(const char *filename, int *loop_fd_out) { - struct loop_info64 loopinfo = { 0 }; - struct stat st; - char buf[64]; - int i, loop_fd, fd, start_index; - char *loopname; + struct loop_info64 loopinfo = {0}; + struct stat st; + char buf[64]; + int i, loop_fd, fd, start_index; + char* loopname; *loop_fd_out = -1; @@ -37,6 +36,7 @@ attach_loop_device(const char *filename, int *loop_fd_out) fd = open(filename, O_RDWR); if (fd < 0) { + perror("open"); return NULL; } @@ -44,6 +44,7 @@ attach_loop_device(const char *filename, int *loop_fd_out) for (i = start_index ; loop_fd < 0 ; i++ ) { if (sprintf(buf, "/dev/loop%d", i) < 0) { close(fd); + perror("sprintf"); return NULL; } @@ -55,12 +56,14 @@ attach_loop_device(const char *filename, int *loop_fd_out) loop_fd = open(buf, O_RDWR); if (loop_fd < 0 && errno == ENOENT) { close(fd); + perror("open"); fprintf (stderr, "no available loopback device!"); return NULL; } else if (loop_fd < 0) continue; if (ioctl (loop_fd, LOOP_SET_FD, (void *)(size_t)fd) < 0) { + perror("ioctl"); close(loop_fd); loop_fd = -1; if (errno != EBUSY) { @@ -78,7 +81,10 @@ attach_loop_device(const char *filename, int *loop_fd_out) loopinfo.lo_flags = LO_FLAGS_AUTOCLEAR; if (ioctl(loop_fd, LOOP_SET_STATUS64, &loopinfo) < 0) { - ioctl(loop_fd, LOOP_CLR_FD, 0); + perror("ioctl1"); + if (ioctl(loop_fd, LOOP_CLR_FD, 0) < 0) { + perror("ioctl2"); + } close(loop_fd); fprintf (stderr, "cannot set up loopback device info"); return NULL; @@ -105,39 +111,19 @@ get_block_size(int fd) return (int64_t)size; } - */ import "C" import ( + "errors" "fmt" "github.com/dotcloud/docker/utils" "os" "runtime" + "syscall" "unsafe" ) -func SetDevDir(dir string) error { - c_dir := C.CString(dir) - defer C.free(unsafe.Pointer(c_dir)) - - if res := C.dm_set_dev_dir(c_dir); res != 1 { - utils.Debugf("Error dm_set_dev_dir") - return fmt.Errorf("dm_set_dev_dir failed") - } - return nil -} - -func GetLibraryVersion() (string, error) { - buffer := make([]byte, 128) - if res := C.dm_get_library_version((*C.char)(unsafe.Pointer(&buffer)), 128); res != 1 { - return "", fmt.Errorf("dm_get_library_version failed") - } - return string(buffer), nil -} - -type TaskType int - const ( DeviceCreate TaskType = iota DeviceReload @@ -160,22 +146,41 @@ const ( DeviceSetGeometry ) -type Task struct { - unmanaged *C.struct_dm_task -} +var ( + ErrTaskRun = errors.New("dm_task_run failed") + ErrTaskSetName = errors.New("dm_task_set_name failed") + ErrTaskSetMessage = errors.New("dm_task_set_message failed") + ErrTaskSetAddNode = errors.New("dm_task_set_add_node failed") + ErrTaskSetRO = errors.New("dm_task_set_ro failed") + ErrTaskAddTarget = errors.New("dm_task_add_target failed") + ErrGetDriverVersion = errors.New("dm_task_get_driver_version failed") + ErrAttachLoopbackDevice = errors.New("loopback mounting failed") + ErrGetBlockSize = errors.New("Can't get block size") + ErrUdevWait = errors.New("wait on udev cookie failed") + ErrSetDevDir = errors.New("dm_set_dev_dir failed") + ErrGetLibraryVersion = errors.New("dm_get_library_version failed") + ErrCreateRemoveTask = errors.New("Can't create task of type DeviceRemove") + ErrRunRemoveDevice = errors.New("running removeDevice failed") +) -type Info struct { - Exists int - Suspended int - LiveTable int - InactiveTable int - OpenCount int32 - EventNr uint32 - Major uint32 - Minor uint32 - ReadOnly int - TargetCount int32 -} +type ( + Task struct { + unmanaged *C.struct_dm_task + } + Info struct { + Exists int + Suspended int + LiveTable int + InactiveTable int + OpenCount int32 + EventNr uint32 + Major uint32 + Minor uint32 + ReadOnly int + TargetCount int32 + } + TaskType int +) func (t *Task) destroy() { if t != nil { @@ -196,37 +201,37 @@ func TaskCreate(tasktype TaskType) *Task { func (t *Task) Run() error { if res := C.dm_task_run(t.unmanaged); res != 1 { - return fmt.Errorf("dm_task_run failed") + return ErrTaskRun } return nil } func (t *Task) SetName(name string) error { c_name := C.CString(name) - defer C.free(unsafe.Pointer(c_name)) + defer free(c_name) if res := C.dm_task_set_name(t.unmanaged, c_name); res != 1 { if os.Getenv("DEBUG") != "" { C.perror(C.CString(fmt.Sprintf("[debug] Error dm_task_set_name(%s, %#v)", name, t.unmanaged))) } - return fmt.Errorf("dm_task_set_name failed") + return ErrTaskSetName } return nil } func (t *Task) SetMessage(message string) error { c_message := C.CString(message) - defer C.free(unsafe.Pointer(c_message)) + defer free(c_message) if res := C.dm_task_set_message(t.unmanaged, c_message); res != 1 { - return fmt.Errorf("dm_task_set_message failed") + return ErrTaskSetMessage } return nil } func (t *Task) SetSector(sector uint64) error { if res := C.dm_task_set_sector(t.unmanaged, C.uint64_t(sector)); res != 1 { - return fmt.Errorf("dm_task_set_add_node failed") + return ErrTaskSetAddNode } return nil } @@ -234,7 +239,7 @@ func (t *Task) SetSector(sector uint64) error { func (t *Task) SetCookie(cookie *uint32, flags uint16) error { c_cookie := C.uint32_t(*cookie) if res := C.dm_task_set_cookie(t.unmanaged, &c_cookie, C.uint16_t(flags)); res != 1 { - return fmt.Errorf("dm_task_set_add_node failed") + return ErrTaskSetAddNode } *cookie = uint32(c_cookie) return nil @@ -242,30 +247,30 @@ func (t *Task) SetCookie(cookie *uint32, flags uint16) error { func (t *Task) SetRo() error { if res := C.dm_task_set_ro(t.unmanaged); res != 1 { - return fmt.Errorf("dm_task_set_ro failed") + return ErrTaskSetRO } return nil } func (t *Task) AddTarget(start uint64, size uint64, ttype string, params string) error { c_ttype := C.CString(ttype) - defer C.free(unsafe.Pointer(c_ttype)) + defer free(c_ttype) c_params := C.CString(params) - defer C.free(unsafe.Pointer(c_params)) + defer free(c_params) if res := C.dm_task_add_target(t.unmanaged, C.uint64_t(start), C.uint64_t(size), c_ttype, c_params); res != 1 { - return fmt.Errorf("dm_task_add_target failed") + return ErrTaskAddTarget } return nil } func (t *Task) GetDriverVersion() (string, error) { - buffer := (*C.char)(C.malloc(128)) - defer C.free(unsafe.Pointer(buffer)) + buffer := C.CString(string(make([]byte, 128))) + defer free(buffer) if res := C.dm_task_get_driver_version(t.unmanaged, buffer, 128); res != 1 { - return "", fmt.Errorf("dm_task_get_driver_version") + return "", ErrGetDriverVersion } return C.GoString(buffer), nil } @@ -273,7 +278,7 @@ func (t *Task) GetDriverVersion() (string, error) { func (t *Task) GetInfo() (*Info, error) { c_info := C.struct_dm_info{} if res := C.dm_task_get_info(t.unmanaged, &c_info); res != 1 { - return nil, fmt.Errorf("dm_task_get_driver_version") + return nil, ErrGetDriverVersion } return &Info{ Exists: int(c_info.exists), @@ -301,29 +306,40 @@ func (t *Task) GetNextTarget(next uintptr) (uintptr, uint64, uint64, string, str func AttachLoopDevice(filename string) (*os.File, error) { c_filename := C.CString(filename) - defer C.free(unsafe.Pointer(c_filename)) + defer free(c_filename) var fd C.int res := C.attach_loop_device(c_filename, &fd) if res == nil { - return nil, fmt.Errorf("error loopback mounting") + return nil, ErrAttachLoopbackDevice } - defer C.free(unsafe.Pointer(res)) + defer free(res) + return os.NewFile(uintptr(fd), C.GoString(res)), nil } +func getBlockSize(fd uintptr) int { + var size uint64 + + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.BLKGETSIZE64, uintptr(unsafe.Pointer(&size))); err != 0 { + utils.Debugf("Error ioctl: %s", err) + return -1 + } + return int(size) +} + func GetBlockDeviceSize(file *os.File) (uint64, error) { if size := C.get_block_size(C.int(file.Fd())); size == -1 { - return 0, fmt.Errorf("Can't get block size") + return 0, ErrGetBlockSize } else { return uint64(size), nil } } func UdevWait(cookie uint32) error { - res := C.dm_udev_wait(C.uint32_t(cookie)) - if res != 1 { - return fmt.Errorf("Failed to wait on udev cookie %d", cookie) + if res := C.dm_udev_wait(C.uint32_t(cookie)); res != 1 { + utils.Debugf("Failed to wait on udev cookie %d", cookie) + return ErrUdevWait } return nil } @@ -332,17 +348,43 @@ func LogInitVerbose(level int) { C.dm_log_init_verbose(C.int(level)) } +func SetDevDir(dir string) error { + c_dir := C.CString(dir) + defer free(c_dir) + + if res := C.dm_set_dev_dir(c_dir); res != 1 { + utils.Debugf("Error dm_set_dev_dir") + return ErrSetDevDir + } + return nil +} + +func GetLibraryVersion() (string, error) { + buffer := C.CString(string(make([]byte, 128))) + defer free(buffer) + + if res := C.dm_get_library_version(buffer, 128); res != 1 { + return "", ErrGetLibraryVersion + } + return C.GoString(buffer), nil +} + // Useful helper for cleanup func RemoveDevice(name string) error { task := TaskCreate(DeviceRemove) if task == nil { - return fmt.Errorf("Can't create task of type DeviceRemove") + return ErrCreateRemoveTask } if err := task.SetName(name); err != nil { - return fmt.Errorf("Can't set task name %s", name) + utils.Debugf("Can't set task name %s", name) + return err } if err := task.Run(); err != nil { - return fmt.Errorf("Error running removeDevice") + return ErrRunRemoveDevice } return nil } + +func free(p *C.char) { + C.free(unsafe.Pointer(p)) +} diff --git a/components/engine/utils_test.go b/components/engine/utils_test.go index f458b1da91..78968432e1 100644 --- a/components/engine/utils_test.go +++ b/components/engine/utils_test.go @@ -319,3 +319,62 @@ func TestParseLxcConfOpt(t *testing.T) { } } } + +type DeviceSetWrapper struct { + wrapped DeviceSet + prefix string +} + +func (wrapper *DeviceSetWrapper) wrap(hash string) string { + if hash != "" { + hash = wrapper.prefix + "-" + hash + } + return hash +} + +func (wrapper *DeviceSetWrapper) AddDevice(hash, baseHash string) error { + return wrapper.wrapped.AddDevice(wrapper.wrap(hash), wrapper.wrap(baseHash)) +} + +func (wrapper *DeviceSetWrapper) SetInitialized(hash string) error { + return wrapper.wrapped.SetInitialized(wrapper.wrap(hash)) +} + +func (wrapper *DeviceSetWrapper) DeactivateDevice(hash string) error { + return wrapper.wrapped.DeactivateDevice(wrapper.wrap(hash)) +} + +func (wrapper *DeviceSetWrapper) Shutdown() error { + return nil +} + +func (wrapper *DeviceSetWrapper) RemoveDevice(hash string) error { + return wrapper.wrapped.RemoveDevice(wrapper.wrap(hash)) +} + +func (wrapper *DeviceSetWrapper) MountDevice(hash, path string) error { + return wrapper.wrapped.MountDevice(wrapper.wrap(hash), path) +} + +func (wrapper *DeviceSetWrapper) UnmountDevice(hash, path string) error { + return wrapper.wrapped.UnmountDevice(wrapper.wrap(hash), path) +} + +func (wrapper *DeviceSetWrapper) HasDevice(hash string) bool { + return wrapper.wrapped.HasDevice(wrapper.wrap(hash)) +} + +func (wrapper *DeviceSetWrapper) HasInitializedDevice(hash string) bool { + return wrapper.wrapped.HasInitializedDevice(wrapper.wrap(hash)) +} + +func (wrapper *DeviceSetWrapper) HasActivatedDevice(hash string) bool { + return wrapper.wrapped.HasActivatedDevice(wrapper.wrap(hash)) +} + +func NewDeviceSetWrapper(wrapped DeviceSet, prefix string) DeviceSet { + return &DeviceSetWrapper{ + wrapped: wrapped, + prefix: prefix, + } +} From 850aea64f12c891e317889dd23dccc744f7266f1 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 4 Oct 2013 15:36:30 +0200 Subject: [PATCH 124/301] Remove overly spewy Debugf Upstream-commit: f7e374fb3a51bd25e61ae6c4343838a0ca756fba Component: engine --- components/engine/devmapper/deviceset_devmapper.go | 1 - 1 file changed, 1 deletion(-) diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index 7e151a8a0d..110bfbf1a2 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -880,7 +880,6 @@ func (devices *DeviceSetDM) SetInitialized(hash string) error { } func (devices *DeviceSetDM) ensureInit() error { - utils.Debugf("ensureInit(). Initialized: %v", devices.initialized) if !devices.initialized { devices.initialized = true if err := devices.initDevmapper(); err != nil { From a29076830eb63e81095dd8d2cd9ee7cdd2f9d590 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 4 Oct 2013 15:38:47 +0200 Subject: [PATCH 125/301] hack: Don't just run the "TestRunHostname" test Upstream-commit: 9b65c7cf49feacad61cdcd11d2b002319a6b6bae Component: engine --- components/engine/hack/make/test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/hack/make/test b/components/engine/hack/make/test index 1dadbdab8e..10264d9f7f 100644 --- a/components/engine/hack/make/test +++ b/components/engine/hack/make/test @@ -11,7 +11,7 @@ bundle_test() { for test_dir in $(find_test_dirs); do ( set -x cd $test_dir - DEBUG=1 go test -v -ldflags "$LDFLAGS" $BUILDFLAGS -run TestRunHostname + DEBUG=1 go test -v -ldflags "$LDFLAGS" $BUILDFLAGS ) done } 2>&1 | tee $DEST/test.log } From b3baf515e460b4bdddf0f90efe9d857344ac933f Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 4 Oct 2013 15:47:43 +0200 Subject: [PATCH 126/301] Tests: Initialize devicemapper early to avoid it happening in a test This can take a while and may cause some tests to timeout Upstream-commit: aaf1f73bcc2c594f07254a98ff77de3cc6351e92 Component: engine --- components/engine/runtime_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index 7c12832e30..07765e2d08 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -147,8 +147,13 @@ func init() { panic(err) } + deviceset := devmapper.NewDeviceSetDM(unitTestStoreDevicesBase) + // Create a device, which triggers the initiation of the base FS + // This avoids other tests doing this and timing out + deviceset.AddDevice("init","") + // Make it our Store root - if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreDevicesBase), false); err != nil { + if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, deviceset, false); err != nil { panic(err) } else { globalRuntime = runtime From ecfa6df5b63ddc29eef5ab8377a4d39d5360c93c Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 7 Oct 2013 14:06:24 +0200 Subject: [PATCH 127/301] devmapper: Move all "raw" libdevmapper wrappers to devmapper.go This separates out the DeviceSet logic a bit better from the raw device mapper operations. devicemapper: Serialize addess to the devicemapper deviceset This code is not safe to run in multiple threads at the same time, and neither is libdevmapper. DeviceMapper: Move deactivate into UnmountDevice This way the deactivate is atomic wrt othe device mapper operations and will not fail with EBUSY if someone else starts a devicemapper operation inbetween unmount and deactivate. devmapper: Fix loopback mounting regression Some changes were added to attach_loop_device which added a perror() in a place that caused it to override errno so that a later errno != EBUSY failed. This fixes that and cleans up the error reporting a bit. devmapper: Build on old kernels without LOOP_CTL_GET_FREE define Upstream-commit: c77697a45ca615f66351a7363e93c3903e92553f Component: engine --- components/engine/deviceset.go | 2 +- .../engine/devmapper/deviceset_devmapper.go | 359 ++++-------------- components/engine/devmapper/devmapper.go | 263 ++++++++++++- components/engine/image.go | 23 +- components/engine/utils_test.go | 4 +- 5 files changed, 345 insertions(+), 306 deletions(-) diff --git a/components/engine/deviceset.go b/components/engine/deviceset.go index b2a968738c..21d5fd4d2f 100644 --- a/components/engine/deviceset.go +++ b/components/engine/deviceset.go @@ -6,7 +6,7 @@ type DeviceSet interface { DeactivateDevice(hash string) error RemoveDevice(hash string) error MountDevice(hash, path string) error - UnmountDevice(hash, path string) error + UnmountDevice(hash, path string, deactivate bool) error HasDevice(hash string) bool HasInitializedDevice(hash string) bool HasActivatedDevice(hash string) bool diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index b789f94aa3..d0d4fbf85c 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -13,6 +13,7 @@ import ( "strconv" "strings" "syscall" + "sync" ) const ( @@ -36,6 +37,7 @@ type MetaData struct { type DeviceSetDM struct { MetaData + sync.Mutex initialized bool root string devicePrefix string @@ -77,74 +79,6 @@ func (devices *DeviceSetDM) getPoolDevName() string { return getDevName(devices.getPoolName()) } -func (devices *DeviceSetDM) createTask(t TaskType, name string) (*Task, error) { - task := TaskCreate(t) - if task == nil { - return nil, fmt.Errorf("Can't create task of type %d", int(t)) - } - if err := task.SetName(name); err != nil { - return nil, fmt.Errorf("Can't set task name %s", name) - } - return task, nil -} - -func (devices *DeviceSetDM) getInfo(name string) (*Info, error) { - task, err := devices.createTask(DeviceInfo, name) - if task == nil { - return nil, err - } - if err := task.Run(); err != nil { - return nil, err - } - return task.GetInfo() -} - -func (devices *DeviceSetDM) getStatus(name string) (uint64, uint64, string, string, error) { - task, err := devices.createTask(DeviceStatus, name) - if task == nil { - utils.Debugf("getStatus: Error createTask: %s", err) - return 0, 0, "", "", err - } - if err := task.Run(); err != nil { - utils.Debugf("getStatus: Error Run: %s", err) - return 0, 0, "", "", err - } - - devinfo, err := task.GetInfo() - if err != nil { - utils.Debugf("getStatus: Error GetInfo: %s", err) - return 0, 0, "", "", err - } - if devinfo.Exists == 0 { - utils.Debugf("getStatus: Non existing device %s", name) - return 0, 0, "", "", fmt.Errorf("Non existing device %s", name) - } - - _, start, length, target_type, params := task.GetNextTarget(0) - return start, length, target_type, params, nil -} - -func (devices *DeviceSetDM) setTransactionId(oldId uint64, newId uint64) error { - task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) - if task == nil { - utils.Debugf("\n--->Err: %s\n", err) - return err - } - - if err := task.SetSector(0); err != nil { - return fmt.Errorf("Can't set sector") - } - - if err := task.SetMessage(fmt.Sprintf("set_transaction_id %d %d", oldId, newId)); err != nil { - return fmt.Errorf("Can't set message") - } - - if err := task.Run(); err != nil { - return fmt.Errorf("Error running setTransactionId") - } - return nil -} - func (devices *DeviceSetDM) hasImage(name string) bool { dirname := devices.loopbackDir() filename := path.Join(dirname, name) @@ -178,194 +112,6 @@ func (devices *DeviceSetDM) ensureImage(name string, size int64) (string, error) return filename, nil } -func (devices *DeviceSetDM) createPool(dataFile *os.File, metadataFile *os.File) error { - utils.Debugf("Activating device-mapper pool %s", devices.getPoolName()) - task, err := devices.createTask(DeviceCreate, devices.getPoolName()) - if task == nil { - utils.Debugf("\n--->Err: %s\n", err) - return err - } - - size, err := GetBlockDeviceSize(dataFile) - if err != nil { - return fmt.Errorf("Can't get data size") - } - - params := metadataFile.Name() + " " + dataFile.Name() + " 512 8192" - if err := task.AddTarget(0, size/512, "thin-pool", params); err != nil { - return fmt.Errorf("Can't add target") - } - - var cookie uint32 = 0 - if err := task.SetCookie(&cookie, 0); err != nil { - return fmt.Errorf("Can't set cookie") - } - - if err := task.Run(); err != nil { - return fmt.Errorf("Error running DeviceCreate") - } - - UdevWait(cookie) - - return nil -} - -func (devices *DeviceSetDM) suspendDevice(info *DevInfo) error { - task, err := devices.createTask(DeviceSuspend, info.Name()) - if task == nil { - utils.Debugf("\n--->Err: %s\n", err) - return err - } - if err := task.Run(); err != nil { - return fmt.Errorf("Error running DeviceSuspend") - } - return nil -} - -func (devices *DeviceSetDM) resumeDevice(info *DevInfo) error { - task, err := devices.createTask(DeviceResume, info.Name()) - if task == nil { - utils.Debugf("\n--->Err: %s\n", err) - return err - } - - var cookie uint32 = 0 - if err := task.SetCookie(&cookie, 0); err != nil { - return fmt.Errorf("Can't set cookie") - } - - if err := task.Run(); err != nil { - return fmt.Errorf("Error running DeviceSuspend") - } - - UdevWait(cookie) - - return nil -} - -func (devices *DeviceSetDM) createDevice(deviceId int) error { - task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) - if task == nil { - utils.Debugf("\n--->Err: %s\n", err) - return err - } - - if err := task.SetSector(0); err != nil { - return fmt.Errorf("Can't set sector") - } - - if err := task.SetMessage(fmt.Sprintf("create_thin %d", deviceId)); err != nil { - return fmt.Errorf("Can't set message") - } - - if err := task.Run(); err != nil { - return fmt.Errorf("Error running createDevice") - } - return nil -} - -func (devices *DeviceSetDM) createSnapDevice(deviceId int, baseInfo *DevInfo) error { - devinfo, _ := devices.getInfo(baseInfo.Name()) - doSuspend := devinfo != nil && devinfo.Exists != 0 - - if doSuspend { - if err := devices.suspendDevice(baseInfo); err != nil { - utils.Debugf("\n--->Err: %s\n", err) - return err - } - } - - task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) - if task == nil { - devices.resumeDevice(baseInfo) - utils.Debugf("\n--->Err: %s\n", err) - return err - } - - if err := task.SetSector(0); err != nil { - devices.resumeDevice(baseInfo) - return fmt.Errorf("Can't set sector") - } - - if err := task.SetMessage(fmt.Sprintf("create_snap %d %d", deviceId, baseInfo.DeviceId)); err != nil { - devices.resumeDevice(baseInfo) - return fmt.Errorf("Can't set message") - } - - if err := task.Run(); err != nil { - devices.resumeDevice(baseInfo) - return fmt.Errorf("Error running DeviceCreate") - } - - if doSuspend { - if err := devices.resumeDevice(baseInfo); err != nil { - utils.Debugf("\n--->Err: %s\n", err) - return err - } - } - - return nil -} - -func (devices *DeviceSetDM) deleteDevice(deviceId int) error { - task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) - if task == nil { - utils.Debugf("\n--->Err: %s\n", err) - return err - } - - if err := task.SetSector(0); err != nil { - return fmt.Errorf("Can't set sector") - } - - if err := task.SetMessage(fmt.Sprintf("delete %d", deviceId)); err != nil { - return fmt.Errorf("Can't set message") - } - - if err := task.Run(); err != nil { - return fmt.Errorf("Error running deleteDevice") - } - return nil -} - -func (devices *DeviceSetDM) removeDevice(name string) error { - task, err := devices.createTask(DeviceRemove, name) - if task == nil { - utils.Debugf("\n--->Err: %s\n", err) - return err - } - if err = task.Run(); err != nil { - return fmt.Errorf("Error running removeDevice") - } - return nil -} - -func (devices *DeviceSetDM) activateDevice(info *DevInfo) error { - task, err := devices.createTask(DeviceCreate, info.Name()) - if task == nil { - utils.Debugf("\n--->Err: %s\n", err) - return err - } - - params := fmt.Sprintf("%s %d", devices.getPoolDevName(), info.DeviceId) - if err := task.AddTarget(0, info.Size/512, "thin", params); err != nil { - return fmt.Errorf("Can't add target") - } - - var cookie uint32 = 0 - if err := task.SetCookie(&cookie, 0); err != nil { - return fmt.Errorf("Can't set cookie") - } - - if err := task.Run(); err != nil { - return fmt.Errorf("Error running DeviceCreate") - } - - UdevWait(cookie) - - return nil -} - func (devices *DeviceSetDM) allocateDeviceId() int { // TODO: Add smarter reuse of deleted devices id := devices.nextFreeDevice @@ -412,7 +158,7 @@ func (devices *DeviceSetDM) saveMetadata() error { } if devices.NewTransactionId != devices.TransactionId { - if err = devices.setTransactionId(devices.TransactionId, devices.NewTransactionId); err != nil { + if err = setTransactionId(devices.getPoolDevName(), devices.TransactionId, devices.NewTransactionId); err != nil { utils.Debugf("\n--->Err: %s\n", err) return err } @@ -448,11 +194,11 @@ func (devices *DeviceSetDM) activateDeviceIfNeeded(hash string) error { return fmt.Errorf("Unknown device %s", hash) } - if devinfo, _ := devices.getInfo(info.Name()); devinfo != nil && devinfo.Exists != 0 { + if devinfo, _ := getInfo(info.Name()); devinfo != nil && devinfo.Exists != 0 { return nil } - return devices.activateDevice(info) + return activateDevice(devices.getPoolDevName(), info.Name(), info.DeviceId, info.Size) } func (devices *DeviceSetDM) createFilesystem(info *DevInfo) error { @@ -470,7 +216,7 @@ func (devices *DeviceSetDM) createFilesystem(info *DevInfo) error { } func (devices *DeviceSetDM) loadMetaData() error { - _, _, _, params, err := devices.getStatus(devices.getPoolName()) + _, _, _, params, err := getStatus(devices.getPoolName()) if err != nil { utils.Debugf("\n--->Err: %s\n", err) return err @@ -521,7 +267,7 @@ func (devices *DeviceSetDM) setupBaseImage() error { if oldInfo != nil && !oldInfo.Initialized { utils.Debugf("Removing uninitialized base image") - if err := devices.RemoveDevice(""); err != nil { + if err := devices.removeDevice(""); err != nil { utils.Debugf("\n--->Err: %s\n", err) return err } @@ -532,14 +278,14 @@ func (devices *DeviceSetDM) setupBaseImage() error { id := devices.allocateDeviceId() // Create initial device - if err := devices.createDevice(id); err != nil { + if err := createDevice(devices.getPoolDevName(), id); err != nil { utils.Debugf("\n--->Err: %s\n", err) return err } info, err := devices.registerDevice(id, "", defaultBaseFsSize) if err != nil { - _ = devices.deleteDevice(id) + _ = deleteDevice(devices.getPoolDevName(), id) utils.Debugf("\n--->Err: %s\n", err) return err } @@ -582,7 +328,7 @@ func setCloseOnExec(name string) { } func (devices *DeviceSetDM) initDevmapper() error { - info, err := devices.getInfo(devices.getPoolName()) + info, err := getInfo(devices.getPoolName()) if info == nil { utils.Debugf("Error device getInfo: %s", err) return err @@ -636,7 +382,7 @@ func (devices *DeviceSetDM) initDevmapper() error { } defer metadataFile.Close() - if err := devices.createPool(dataFile, metadataFile); err != nil { + if err := createPool(devices.getPoolName(), dataFile, metadataFile); err != nil { utils.Debugf("\n--->Err: %s\n", err) return err } @@ -657,6 +403,9 @@ func (devices *DeviceSetDM) initDevmapper() error { } func (devices *DeviceSetDM) AddDevice(hash, baseHash string) error { + devices.Lock() + defer devices.Unlock() + if err := devices.ensureInit(); err != nil { utils.Debugf("Error init: %s\n", err) return err @@ -674,33 +423,28 @@ func (devices *DeviceSetDM) AddDevice(hash, baseHash string) error { deviceId := devices.allocateDeviceId() - if err := devices.createSnapDevice(deviceId, baseInfo); err != nil { + if err := devices.createSnapDevice(devices.getPoolDevName(), deviceId, baseInfo.Name(), baseInfo.DeviceId); err != nil { utils.Debugf("Error creating snap device: %s\n", err) return err } if _, err := devices.registerDevice(deviceId, hash, baseInfo.Size); err != nil { - devices.deleteDevice(deviceId) + deleteDevice(devices.getPoolDevName(), deviceId) utils.Debugf("Error registering device: %s\n", err) return err } return nil } -func (devices *DeviceSetDM) RemoveDevice(hash string) error { - if err := devices.ensureInit(); err != nil { - utils.Debugf("\n--->Err: %s\n", err) - return err - } - +func (devices *DeviceSetDM) removeDevice(hash string) error { info := devices.Devices[hash] if info == nil { return fmt.Errorf("hash %s doesn't exists", hash) } - devinfo, _ := devices.getInfo(info.Name()) + devinfo, _ := getInfo(info.Name()) if devinfo != nil && devinfo.Exists != 0 { - if err := devices.removeDevice(info.Name()); err != nil { + if err := removeDevice(info.Name()); err != nil { utils.Debugf("Error removing device: %s\n", err) return err } @@ -714,7 +458,7 @@ func (devices *DeviceSetDM) RemoveDevice(hash string) error { } } - if err := devices.deleteDevice(info.DeviceId); err != nil { + if err := deleteDevice(devices.getPoolDevName(), info.DeviceId); err != nil { utils.Debugf("Error deleting device: %s\n", err) return err } @@ -731,24 +475,32 @@ func (devices *DeviceSetDM) RemoveDevice(hash string) error { return nil } -func (devices *DeviceSetDM) DeactivateDevice(hash string) error { +func (devices *DeviceSetDM) RemoveDevice(hash string) error { + devices.Lock() + defer devices.Unlock() + if err := devices.ensureInit(); err != nil { utils.Debugf("\n--->Err: %s\n", err) return err } + + return devices.removeDevice(hash) +} + +func (devices *DeviceSetDM) deactivateDevice(hash string) error { info := devices.Devices[hash] if info == nil { return fmt.Errorf("hash %s doesn't exists", hash) } - devinfo, err := devices.getInfo(info.Name()) + devinfo, err := getInfo(info.Name()) if err != nil { utils.Debugf("\n--->Err: %s\n", err) return err } if devinfo.Exists != 0 { - if err := devices.removeDevice(info.Name()); err != nil { + if err := removeDevice(info.Name()); err != nil { utils.Debugf("\n--->Err: %s\n", err) return err } @@ -757,7 +509,24 @@ func (devices *DeviceSetDM) DeactivateDevice(hash string) error { return nil } +func (devices *DeviceSetDM) DeactivateDevice(hash string) error { + devices.Lock() + defer devices.Unlock() + + if err := devices.ensureInit(); err != nil { + utils.Debugf("\n--->Err: %s\n", err) + return err + } + + utils.Debugf("DeactivateDevice %s", hash) + return devices.deactivateDevice(hash); +} + + func (devices *DeviceSetDM) Shutdown() error { + devices.Lock() + defer devices.Unlock() + if !devices.initialized { return nil } @@ -772,14 +541,14 @@ func (devices *DeviceSetDM) Shutdown() error { } for _, d := range devices.Devices { - if err := devices.DeactivateDevice(d.Hash); err != nil { + if err := devices.deactivateDevice(d.Hash); err != nil { utils.Debugf("Shutdown deactivate %s , error: %s\n", d.Hash, err) } } pool := devices.getPoolDevName() - if devinfo, err := devices.getInfo(pool); err == nil && devinfo.Exists != 0 { - if err := devices.removeDevice(pool); err != nil { + if devinfo, err := getInfo(pool); err == nil && devinfo.Exists != 0 { + if err := removeDevice(pool); err != nil { utils.Debugf("Shutdown deactivate %s , error: %s\n", pool, err) } } @@ -788,6 +557,9 @@ func (devices *DeviceSetDM) Shutdown() error { } func (devices *DeviceSetDM) MountDevice(hash, path string) error { + devices.Lock() + defer devices.Unlock() + if err := devices.ensureInit(); err != nil { utils.Debugf("\n--->Err: %s\n", err) return err @@ -815,7 +587,10 @@ func (devices *DeviceSetDM) MountDevice(hash, path string) error { return nil } -func (devices *DeviceSetDM) UnmountDevice(hash, path string) error { +func (devices *DeviceSetDM) UnmountDevice(hash, path string, deactivate bool) error { + devices.Lock() + defer devices.Unlock() + if err := syscall.Unmount(path, 0); err != nil { utils.Debugf("\n--->Err: %s\n", err) return err @@ -827,10 +602,17 @@ func (devices *DeviceSetDM) UnmountDevice(hash, path string) error { delete(devices.activeMounts, path) } + if deactivate { + devices.deactivateDevice(hash) + } + return nil } func (devices *DeviceSetDM) HasDevice(hash string) bool { + devices.Lock() + defer devices.Unlock() + if err := devices.ensureInit(); err != nil { return false } @@ -838,6 +620,9 @@ func (devices *DeviceSetDM) HasDevice(hash string) bool { } func (devices *DeviceSetDM) HasInitializedDevice(hash string) bool { + devices.Lock() + defer devices.Unlock() + if err := devices.ensureInit(); err != nil { return false } @@ -847,6 +632,9 @@ func (devices *DeviceSetDM) HasInitializedDevice(hash string) bool { } func (devices *DeviceSetDM) HasActivatedDevice(hash string) bool { + devices.Lock() + defer devices.Unlock() + if err := devices.ensureInit(); err != nil { return false } @@ -855,11 +643,14 @@ func (devices *DeviceSetDM) HasActivatedDevice(hash string) bool { if info == nil { return false } - devinfo, _ := devices.getInfo(info.Name()) + devinfo, _ := getInfo(info.Name()) return devinfo != nil && devinfo.Exists != 0 } func (devices *DeviceSetDM) SetInitialized(hash string) error { + devices.Lock() + defer devices.Unlock() + if err := devices.ensureInit(); err != nil { utils.Debugf("\n--->Err: %s\n", err) return err diff --git a/components/engine/devmapper/devmapper.go b/components/engine/devmapper/devmapper.go index 02922e836b..e4db906637 100644 --- a/components/engine/devmapper/devmapper.go +++ b/components/engine/devmapper/devmapper.go @@ -14,6 +14,10 @@ package devmapper #include #include +#ifndef LOOP_CTL_GET_FREE +#define LOOP_CTL_GET_FREE 0x4C82 +#endif + char* attach_loop_device(const char *filename, int *loop_fd_out) { struct loop_info64 loopinfo = {0}; @@ -56,19 +60,18 @@ char* attach_loop_device(const char *filename, int *loop_fd_out) loop_fd = open(buf, O_RDWR); if (loop_fd < 0 && errno == ENOENT) { close(fd); - perror("open"); fprintf (stderr, "no available loopback device!"); return NULL; } else if (loop_fd < 0) continue; if (ioctl (loop_fd, LOOP_SET_FD, (void *)(size_t)fd) < 0) { - perror("ioctl"); + int errsv = errno; close(loop_fd); loop_fd = -1; - if (errno != EBUSY) { + if (errsv != EBUSY) { close (fd); - fprintf (stderr, "cannot set up loopback device %s", buf); + fprintf (stderr, "cannot set up loopback device %s: %s", buf, strerror(errsv)); return NULL; } continue; @@ -388,3 +391,255 @@ func RemoveDevice(name string) error { func free(p *C.char) { C.free(unsafe.Pointer(p)) } + +func createPool(poolName string, dataFile *os.File, metadataFile *os.File) error { + task, err := createTask(DeviceCreate, poolName) + if task == nil { + return err + } + + size, err := GetBlockDeviceSize(dataFile) + if err != nil { + return fmt.Errorf("Can't get data size") + } + + params := metadataFile.Name() + " " + dataFile.Name() + " 512 8192" + if err := task.AddTarget(0, size/512, "thin-pool", params); err != nil { + return fmt.Errorf("Can't add target") + } + + var cookie uint32 = 0 + if err := task.SetCookie(&cookie, 0); err != nil { + return fmt.Errorf("Can't set cookie") + } + + if err := task.Run(); err != nil { + return fmt.Errorf("Error running DeviceCreate") + } + + UdevWait(cookie) + + return nil +} + +func createTask(t TaskType, name string) (*Task, error) { + task := TaskCreate(t) + if task == nil { + return nil, fmt.Errorf("Can't create task of type %d", int(t)) + } + if err := task.SetName(name); err != nil { + return nil, fmt.Errorf("Can't set task name %s", name) + } + return task, nil +} + +func getInfo(name string) (*Info, error) { + task, err := createTask(DeviceInfo, name) + if task == nil { + return nil, err + } + if err := task.Run(); err != nil { + return nil, err + } + return task.GetInfo() +} + +func getStatus(name string) (uint64, uint64, string, string, error) { + task, err := createTask(DeviceStatus, name) + if task == nil { + utils.Debugf("getStatus: Error createTask: %s", err) + return 0, 0, "", "", err + } + if err := task.Run(); err != nil { + utils.Debugf("getStatus: Error Run: %s", err) + return 0, 0, "", "", err + } + + devinfo, err := task.GetInfo() + if err != nil { + utils.Debugf("getStatus: Error GetInfo: %s", err) + return 0, 0, "", "", err + } + if devinfo.Exists == 0 { + utils.Debugf("getStatus: Non existing device %s", name) + return 0, 0, "", "", fmt.Errorf("Non existing device %s", name) + } + + _, start, length, target_type, params := task.GetNextTarget(0) + return start, length, target_type, params, nil +} + +func setTransactionId(poolName string, oldId uint64, newId uint64) error { + task, err := createTask(DeviceTargetMsg, poolName) + if task == nil { + return err + } + + if err := task.SetSector(0); err != nil { + return fmt.Errorf("Can't set sector") + } + + if err := task.SetMessage(fmt.Sprintf("set_transaction_id %d %d", oldId, newId)); err != nil { + return fmt.Errorf("Can't set message") + } + + if err := task.Run(); err != nil { + return fmt.Errorf("Error running setTransactionId") + } + return nil +} + +func suspendDevice(name string) error { + task, err := createTask(DeviceSuspend, name) + if task == nil { + return err + } + if err := task.Run(); err != nil { + return fmt.Errorf("Error running DeviceSuspend") + } + return nil +} + +func resumeDevice(name string) error { + task, err := createTask(DeviceResume, name) + if task == nil { + return err + } + + var cookie uint32 = 0 + if err := task.SetCookie(&cookie, 0); err != nil { + return fmt.Errorf("Can't set cookie") + } + + if err := task.Run(); err != nil { + return fmt.Errorf("Error running DeviceSuspend") + } + + UdevWait(cookie) + + return nil +} + +func createDevice(poolName string, deviceId int) error { + task, err := createTask(DeviceTargetMsg, poolName) + if task == nil { + return err + } + + if err := task.SetSector(0); err != nil { + return fmt.Errorf("Can't set sector") + } + + if err := task.SetMessage(fmt.Sprintf("create_thin %d", deviceId)); err != nil { + return fmt.Errorf("Can't set message") + } + + if err := task.Run(); err != nil { + return fmt.Errorf("Error running createDevice") + } + return nil +} + +func deleteDevice(poolName string, deviceId int) error { + task, err := createTask(DeviceTargetMsg, poolName) + if task == nil { + return err + } + + if err := task.SetSector(0); err != nil { + return fmt.Errorf("Can't set sector") + } + + if err := task.SetMessage(fmt.Sprintf("delete %d", deviceId)); err != nil { + return fmt.Errorf("Can't set message") + } + + if err := task.Run(); err != nil { + return fmt.Errorf("Error running deleteDevice") + } + return nil +} + +func removeDevice(name string) error { + task, err := createTask(DeviceRemove, name) + if task == nil { + return err + } + if err = task.Run(); err != nil { + return fmt.Errorf("Error running removeDevice") + } + return nil +} + +func activateDevice(poolName string, name string, deviceId int, size uint64) error { + task, err := createTask(DeviceCreate, name) + if task == nil { + return err + } + + params := fmt.Sprintf("%s %d", poolName, deviceId) + if err := task.AddTarget(0, size/512, "thin", params); err != nil { + return fmt.Errorf("Can't add target") + } + + var cookie uint32 = 0 + if err := task.SetCookie(&cookie, 0); err != nil { + return fmt.Errorf("Can't set cookie") + } + + if err := task.Run(); err != nil { + return fmt.Errorf("Error running DeviceCreate") + } + + UdevWait(cookie) + + return nil +} + +func (devices *DeviceSetDM) createSnapDevice(poolName string, deviceId int, baseName string, baseDeviceId int) error { + devinfo, _ := getInfo(baseName) + doSuspend := devinfo != nil && devinfo.Exists != 0 + + if doSuspend { + if err := suspendDevice(baseName); err != nil { + return err + } + } + + task, err := createTask(DeviceTargetMsg, poolName) + if task == nil { + if doSuspend { + resumeDevice(baseName) + } + return err + } + + if err := task.SetSector(0); err != nil { + if doSuspend { + resumeDevice(baseName) + } + return fmt.Errorf("Can't set sector") + } + + if err := task.SetMessage(fmt.Sprintf("create_snap %d %d", deviceId, baseDeviceId)); err != nil { + if doSuspend { + resumeDevice(baseName) + } + return fmt.Errorf("Can't set message") + } + + if err := task.Run(); err != nil { + if doSuspend { + resumeDevice(baseName) + } + return fmt.Errorf("Error running DeviceCreate") + } + + if doSuspend { + if err := resumeDevice(baseName); err != nil { + return err + } + } + + return nil +} diff --git a/components/engine/image.go b/components/engine/image.go index a937c74086..6a1191ef1b 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -391,14 +391,14 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { if err := ioutil.WriteFile(path.Join(mountDir, ".docker-id"), []byte(image.ID), 0600); err != nil { utils.Debugf("Error writing file: %s", err) - devices.UnmountDevice(image.ID, mountDir) + devices.UnmountDevice(image.ID, mountDir, true) devices.RemoveDevice(image.ID) return err } if err = image.applyLayer(layerPath(root), mountDir); err != nil { utils.Debugf("Error applying layer: %s", err) - devices.UnmountDevice(image.ID, mountDir) + devices.UnmountDevice(image.ID, mountDir, true) devices.RemoveDevice(image.ID) return err } @@ -411,28 +411,24 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { // part of the container changes dockerinitLayer, err := image.getDockerInitLayer() if err != nil { - devices.UnmountDevice(image.ID, mountDir) + devices.UnmountDevice(image.ID, mountDir, true) devices.RemoveDevice(image.ID) return err } if err := image.applyLayer(dockerinitLayer, mountDir); err != nil { - devices.UnmountDevice(image.ID, mountDir) + devices.UnmountDevice(image.ID, mountDir, true) devices.RemoveDevice(image.ID) return err } - if err := devices.UnmountDevice(image.ID, mountDir); err != nil { + if err := devices.UnmountDevice(image.ID, mountDir, true); err != nil { devices.RemoveDevice(image.ID) return err } devices.SetInitialized(image.ID) - // No need to the device-mapper device to hang around once we've written - // the image, it can be enabled on-demand when needed - devices.DeactivateDevice(image.ID) - return nil } @@ -491,11 +487,11 @@ func (image *Image) Unmount(runtime *Runtime, root string, id string) error { return err } - if err = devices.UnmountDevice(id, root); err != nil { + if err = devices.UnmountDevice(id, root, true); err != nil { return err } - return devices.DeactivateDevice(id) + return nil } func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, error) { @@ -518,10 +514,7 @@ func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, er } changes, err := ChangesDirs(root, rw) - devices.UnmountDevice(image.ID, rw) - if !wasActivated { - devices.DeactivateDevice(image.ID) - } + devices.UnmountDevice(image.ID, rw, !wasActivated) if err != nil { return nil, err } diff --git a/components/engine/utils_test.go b/components/engine/utils_test.go index 78968432e1..9c7ab2f26c 100644 --- a/components/engine/utils_test.go +++ b/components/engine/utils_test.go @@ -356,8 +356,8 @@ func (wrapper *DeviceSetWrapper) MountDevice(hash, path string) error { return wrapper.wrapped.MountDevice(wrapper.wrap(hash), path) } -func (wrapper *DeviceSetWrapper) UnmountDevice(hash, path string) error { - return wrapper.wrapped.UnmountDevice(wrapper.wrap(hash), path) +func (wrapper *DeviceSetWrapper) UnmountDevice(hash, path string, deactivate bool) error { + return wrapper.wrapped.UnmountDevice(wrapper.wrap(hash), path, deactivate) } func (wrapper *DeviceSetWrapper) HasDevice(hash string) bool { From f3a38555ac76b81a592b80aa39a123451f5565e2 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 10 Oct 2013 15:24:39 +0200 Subject: [PATCH 128/301] device-mapper: Move all devicemapper spew to log through utils.Debugf(). Upstream-commit: b440ec0136793ebcfbf8d76e6cd16ccba0785382 Component: engine --- .../engine/devmapper/deviceset_devmapper.go | 11 +++++++ components/engine/devmapper/devmapper.go | 33 +++++++++++++++++++ components/engine/devmapper/devmapper_log.go | 13 ++++++++ 3 files changed, 57 insertions(+) create mode 100644 components/engine/devmapper/devmapper_log.go diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index d0d4fbf85c..2107a305ae 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -327,7 +327,18 @@ func setCloseOnExec(name string) { } } +func (devices *DeviceSetDM) log(level int, file string, line int, dmError int, message string) { + if level >= 7 { + return // Ignore _LOG_DEBUG + } + + utils.Debugf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message) +} + + func (devices *DeviceSetDM) initDevmapper() error { + logInit(devices) + info, err := getInfo(devices.getPoolName()) if info == nil { utils.Debugf("Error device getInfo: %s", err) diff --git a/components/engine/devmapper/devmapper.go b/components/engine/devmapper/devmapper.go index e4db906637..97a388e4c9 100644 --- a/components/engine/devmapper/devmapper.go +++ b/components/engine/devmapper/devmapper.go @@ -114,6 +114,28 @@ get_block_size(int fd) return (int64_t)size; } +extern void DevmapperLogCallback(int level, char *file, int line, int dm_errno_or_class, char *str); + +static void +log_cb(int level, const char *file, int line, + int dm_errno_or_class, const char *f, ...) +{ + char buffer[256]; + va_list ap; + + va_start(ap, f); + vsnprintf(buffer, 256, f, ap); + va_end(ap); + + DevmapperLogCallback(level, (char *)file, line, dm_errno_or_class, buffer); +} + +static void +log_with_errno_init () +{ + dm_log_with_errno_init(log_cb); +} + */ import "C" @@ -127,6 +149,10 @@ import ( "unsafe" ) +type DevmapperLogger interface { + log(level int, file string, line int, dmError int, message string) +} + const ( DeviceCreate TaskType = iota DeviceReload @@ -351,6 +377,13 @@ func LogInitVerbose(level int) { C.dm_log_init_verbose(C.int(level)) } +var dmLogger DevmapperLogger = nil + +func logInit(logger DevmapperLogger) { + dmLogger = logger + C.log_with_errno_init() +} + func SetDevDir(dir string) error { c_dir := C.CString(dir) defer free(c_dir) diff --git a/components/engine/devmapper/devmapper_log.go b/components/engine/devmapper/devmapper_log.go new file mode 100644 index 0000000000..1f95eb7bac --- /dev/null +++ b/components/engine/devmapper/devmapper_log.go @@ -0,0 +1,13 @@ +package devmapper + +import "C" + +// Due to the way cgo works this has to be in a separate file, as devmapper.go has +// definitions in the cgo block, which is incompatible with using "//export" + +//export DevmapperLogCallback +func DevmapperLogCallback(level C.int, file *C.char, line C.int, dm_errno_or_class C.int, message *C.char) { + if dmLogger != nil { + dmLogger.log(int(level), C.GoString(file), int(line), int(dm_errno_or_class), C.GoString(message)) + } +} From 53581a197dcc5d4124fc2e5e87b2364d686af425 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 8 Oct 2013 14:54:00 +0200 Subject: [PATCH 129/301] Clean up better from previous unit-test runs This makes sure we unmount existing mounts (as well as removing the devmapper devices), and it fails with proper logs rather than just panic()ing. Upstream-commit: e7986da531568040e0185aafce03cdae2169baac Component: engine --- components/engine/runtime_test.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index 73697137bc..964c1a9e93 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -100,11 +100,25 @@ func removeDev(name string) { syscall.Close(fd) } if err := devmapper.RemoveDevice(name); err != nil { - panic(fmt.Errorf("Unable to remove existing device %s: %s", name, err)) + log.Fatalf("Unable to remove device %s needed to get a freash unit test environment", name) } } func cleanupDevMapper() { + // Unmount any leftover mounts from previous unit test runs + if data, err := ioutil.ReadFile("/proc/mounts"); err == nil { + for _, line := range strings.Split(string(data), "\n") { + cols := strings.Split(line, " ") + if len(cols) >= 2 && strings.HasPrefix(cols[0], "/dev/mapper/docker-unit-tests-devices") { + err = syscall.Unmount(cols[1], 0) + if err != nil { + log.Fatalf("Unable to unmount %s needed to get a freash unit test environment: %s", cols[1], err) + } + } + } + } + + // Remove any leftover devmapper devices from previous unit run tests infos, _ := ioutil.ReadDir("/dev/mapper") if infos != nil { hasPool := false From 58c60c1584e71a0d285e2f53f4a20d3d6027426e Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 8 Oct 2013 17:00:40 -0700 Subject: [PATCH 130/301] Migrate AUFS containers to devmapper Conflicts: hack/make.sh runtime.go Upstream-commit: a263e07678c813af2a7b0859034cc76c9a888c7b Component: engine --- components/engine/hack/make.sh | 2 +- components/engine/runtime.go | 68 +++++++++++++++++++++++++++++++--- 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/components/engine/hack/make.sh b/components/engine/hack/make.sh index 62863bb8d8..15380e6a53 100755 --- a/components/engine/hack/make.sh +++ b/components/engine/hack/make.sh @@ -44,7 +44,7 @@ if [ -n "$(git status --porcelain)" ]; then fi # Use these flags when compiling the tests and final binary -LDFLAGS='-X main.GITCOMMIT "'$GITCOMMIT'" -X main.VERSION "'$VERSION'" -w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' +LDFLAGS='-X main.GITCOMMIT "'$GITCOMMIT'" -X main.VERSION "'$VERSION'" -w -linkmode external -extldflags "-lpthread -static -Wl,--unresolved-symbols=ignore-all"' BUILDFLAGS='-tags netgo' bundle() { diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 4ea95a8b51..11b75f2e9c 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -134,7 +134,7 @@ func (runtime *Runtime) containerRoot(id string) string { // Load reads the contents of a container from disk and registers // it with Register. // This is typically done at startup. -func (runtime *Runtime) Load(id string) (*Container, error) { +func (runtime *Runtime) load(id string) (*Container, error) { container := &Container{root: runtime.containerRoot(id)} if err := container.FromDisk(); err != nil { return nil, err @@ -145,9 +145,6 @@ func (runtime *Runtime) Load(id string) (*Container, error) { if container.State.Running { container.State.Ghost = true } - if err := runtime.Register(container); err != nil { - return nil, err - } return container, nil } @@ -289,9 +286,11 @@ func (runtime *Runtime) restore() error { if err != nil { return err } + + containers := []*Container{} for i, v := range dir { id := v.Name() - container, err := runtime.Load(id) + container, err := runtime.load(id) if i%21 == 0 && os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" { fmt.Printf("\b%c", wheel[i%4]) } @@ -300,7 +299,66 @@ func (runtime *Runtime) restore() error { continue } utils.Debugf("Loaded container %v", container.ID) + containers = append(containers, container) } + + deviceSet := runtime.deviceSet + for _, container := range containers { + + // Perform a migration for aufs containers + if !deviceSet.HasDevice(container.ID) { + contents, err := ioutil.ReadDir(container.rwPath()) + if err != nil { + if !os.IsNotExist(err) { + utils.Debugf("[migration] Error reading rw dir %s", err) + } + continue + } + + if len(contents) > 0 { + utils.Debugf("[migration] Begin migration of %s", container.ID) + + image, err := runtime.graph.Get(container.Image) + if err != nil { + utils.Debugf("[migratoin] Failed to get image %s", err) + continue + } + + unmount := func() { + if err := image.Unmount(runtime, container.RootfsPath(), container.ID); err != nil { + utils.Debugf("[migraton] Failed to unmount image %s", err) + } + } + + if err := image.Mount(runtime, container.RootfsPath(), container.rwPath(), container.ID); err != nil { + utils.Debugf("[migratoin] Failed to mount image %s", err) + continue + } + + if err := image.applyLayer(container.rwPath(), container.RootfsPath()); err != nil { + utils.Debugf("[migration] Failed to apply layer %s", err) + unmount() + continue + } + + unmount() + + if err := os.RemoveAll(container.rwPath()); err != nil { + utils.Debugf("[migration] Failed to remove rw dir %s", err) + } + + utils.Debugf("[migration] End migration of %s", container.ID) + } + } + + } + + for _, container := range containers { + if err := runtime.Register(container); err != nil { + utils.Debugf("Failed to register container %s: %s", container.ID, err) + } + } + if os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" { fmt.Printf("\bdone.\n") } From a829b7de69384ba359d108cf1f1db036c5d08322 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 10 Oct 2013 17:52:01 -0700 Subject: [PATCH 131/301] Verbose migration add warning for running container Conflicts: hack/make.sh runtime.go runtime_test.go Upstream-commit: 562e4f1e23a52cff05078f68e21199514ba58250 Component: engine --- components/engine/hack/make.sh | 2 +- components/engine/runtime.go | 125 ++++++++++++++++++------------ components/engine/runtime_test.go | 2 +- 3 files changed, 78 insertions(+), 51 deletions(-) diff --git a/components/engine/hack/make.sh b/components/engine/hack/make.sh index 15380e6a53..62863bb8d8 100755 --- a/components/engine/hack/make.sh +++ b/components/engine/hack/make.sh @@ -44,7 +44,7 @@ if [ -n "$(git status --porcelain)" ]; then fi # Use these flags when compiling the tests and final binary -LDFLAGS='-X main.GITCOMMIT "'$GITCOMMIT'" -X main.VERSION "'$VERSION'" -w -linkmode external -extldflags "-lpthread -static -Wl,--unresolved-symbols=ignore-all"' +LDFLAGS='-X main.GITCOMMIT "'$GITCOMMIT'" -X main.VERSION "'$VERSION'" -w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' BUILDFLAGS='-tags netgo' bundle() { diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 11b75f2e9c..578d374b38 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -287,7 +287,10 @@ func (runtime *Runtime) restore() error { return err } + deviceSet := runtime.deviceSet containers := []*Container{} + containersToMigrate := []*Container{} + for i, v := range dir { id := v.Name() container, err := runtime.load(id) @@ -300,68 +303,92 @@ func (runtime *Runtime) restore() error { } utils.Debugf("Loaded container %v", container.ID) containers = append(containers, container) + + if !deviceSet.HasDevice(container.ID) { + containersToMigrate = append(containersToMigrate, container) + } } - deviceSet := runtime.deviceSet - for _, container := range containers { - - // Perform a migration for aufs containers - if !deviceSet.HasDevice(container.ID) { - contents, err := ioutil.ReadDir(container.rwPath()) - if err != nil { - if !os.IsNotExist(err) { - utils.Debugf("[migration] Error reading rw dir %s", err) - } - continue - } - - if len(contents) > 0 { - utils.Debugf("[migration] Begin migration of %s", container.ID) - - image, err := runtime.graph.Get(container.Image) - if err != nil { - utils.Debugf("[migratoin] Failed to get image %s", err) - continue - } - - unmount := func() { - if err := image.Unmount(runtime, container.RootfsPath(), container.ID); err != nil { - utils.Debugf("[migraton] Failed to unmount image %s", err) - } - } - - if err := image.Mount(runtime, container.RootfsPath(), container.rwPath(), container.ID); err != nil { - utils.Debugf("[migratoin] Failed to mount image %s", err) - continue - } - - if err := image.applyLayer(container.rwPath(), container.RootfsPath()); err != nil { - utils.Debugf("[migration] Failed to apply layer %s", err) - unmount() - continue - } - - unmount() - - if err := os.RemoveAll(container.rwPath()); err != nil { - utils.Debugf("[migration] Failed to remove rw dir %s", err) - } - - utils.Debugf("[migration] End migration of %s", container.ID) - } + // Migrate AUFS containers to device mapper + if len(containersToMigrate) > 0 { + if err := migrateToDeviceMapper(runtime, containersToMigrate); err != nil { + return err } - } for _, container := range containers { if err := runtime.Register(container); err != nil { utils.Debugf("Failed to register container %s: %s", container.ID, err) + continue } } - if os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" { fmt.Printf("\bdone.\n") } + + return nil +} + +func migrateToDeviceMapper(runtime *Runtime, containers []*Container) error { + var ( + image *Image + contents []os.FileInfo + err error + ) + + fmt.Printf("Migrating %d containers to new storage backend\n", len(containers)) + for _, container := range containers { + if container.State.Running { + fmt.Printf("WARNING - Cannot migrate %s because the container is running. Please stop the container and relaunch the daemon!") + continue + } + + fmt.Printf("Migrating %s\n", container.ID) + + if contents, err = ioutil.ReadDir(container.rwPath()); err != nil { + if !os.IsNotExist(err) { + fmt.Printf("Error reading rw dir %s\n", err) + } + continue + } + + if len(contents) == 0 { + fmt.Printf("Skipping migration of %s because rw layer contains no changes\n") + continue + } + + if image, err = runtime.graph.Get(container.Image); err != nil { + fmt.Printf("Failed to fetch image %s\n", err) + continue + } + + unmount := func() { + if err = image.Unmount(runtime, container.RootfsPath(), container.ID); err != nil { + fmt.Printf("Failed to unmount image %s\n", err) + } + } + + if err = image.Mount(runtime, container.RootfsPath(), container.rwPath(), container.ID); err != nil { + fmt.Printf("Failed to mount image %s\n", err) + continue + } + + if err = image.applyLayer(container.rwPath(), container.RootfsPath()); err != nil { + fmt.Printf("Failed to apply layer in storage backend %s\n", err) + unmount() + continue + } + + unmount() + + if err = os.RemoveAll(container.rwPath()); err != nil { + fmt.Printf("Failed to remove rw layer %s\n", err) + } + + fmt.Printf("Successful migration for %s\n", container.ID) + } + fmt.Printf("Migration complete\n") + return nil } diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index 73697137bc..b3a2ccb16f 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -151,7 +151,7 @@ func init() { deviceset := devmapper.NewDeviceSetDM(unitTestStoreDevicesBase) // Create a device, which triggers the initiation of the base FS // This avoids other tests doing this and timing out - deviceset.AddDevice("init","") + deviceset.AddDevice("init", "") // Make it our Store root if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, deviceset, false); err != nil { From 0e3ebd255a57ce90ff994897dc9137f52f091b27 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Oct 2013 20:37:11 -0700 Subject: [PATCH 132/301] Allow loopback and base fs sizes set by env var Upstream-commit: c3f1bb3287a10ed0d49f48630b92c69ff6ca4ff7 Component: engine --- .../engine/devmapper/deviceset_devmapper.go | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index 2107a305ae..720b57294b 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -12,11 +12,11 @@ import ( "path/filepath" "strconv" "strings" - "syscall" "sync" + "syscall" ) -const ( +var ( defaultDataLoopbackSize int64 = 100 * 1024 * 1024 * 1024 defaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024 defaultBaseFsSize uint64 = 10 * 1024 * 1024 * 1024 @@ -47,6 +47,30 @@ type DeviceSetDM struct { activeMounts map[string]int } +func init() { + var err error + + rawMetaSize := os.Getenv("DOCKER_LOOPBACK_META_SIZE") + rawDataSize := os.Getenv("DOCKER_LOOPBACK_DATA_SIZE") + rawBaseFSSize := os.Getenv("DOCKER_BASE_FS_SIZE") + + if rawMetaSize != "" { + if defaultMetaDataLoopbackSize, err = strconv.ParseInt(rawMetaSize, 0, 0); err != nil { + panic(err) + } + } + if rawDataSize != "" { + if defaultDataLoopbackSize, err = strconv.ParseInt(rawDataSize, 0, 0); err != nil { + panic(err) + } + } + if rawBaseFSSize != "" { + if defaultBaseFsSize, err = strconv.ParseUint(rawBaseFSSize, 0, 0); err != nil { + panic(err) + } + } +} + func getDevName(name string) string { return "/dev/mapper/" + name } @@ -317,7 +341,7 @@ func setCloseOnExec(name string) { if fileInfos != nil { for _, i := range fileInfos { link, _ := os.Readlink(filepath.Join("/proc/self/fd", i.Name())) - if link == name { + if link == name { fd, err := strconv.Atoi(i.Name()) if err == nil { syscall.CloseOnExec(fd) @@ -327,7 +351,7 @@ func setCloseOnExec(name string) { } } -func (devices *DeviceSetDM) log(level int, file string, line int, dmError int, message string) { +func (devices *DeviceSetDM) log(level int, file string, line int, dmError int, message string) { if level >= 7 { return // Ignore _LOG_DEBUG } @@ -335,7 +359,6 @@ func (devices *DeviceSetDM) log(level int, file string, line int, dmError int, m utils.Debugf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message) } - func (devices *DeviceSetDM) initDevmapper() error { logInit(devices) @@ -495,7 +518,6 @@ func (devices *DeviceSetDM) RemoveDevice(hash string) error { return err } - return devices.removeDevice(hash) } @@ -530,10 +552,9 @@ func (devices *DeviceSetDM) DeactivateDevice(hash string) error { } utils.Debugf("DeactivateDevice %s", hash) - return devices.deactivateDevice(hash); + return devices.deactivateDevice(hash) } - func (devices *DeviceSetDM) Shutdown() error { devices.Lock() defer devices.Unlock() From c072ec522a0b78fbd1c0568ed8122aab68f02950 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 12 Oct 2013 15:06:53 -0700 Subject: [PATCH 133/301] Change the base filesystem size in unit tests Upstream-commit: 48070274ee93bf0d4cef7486a4ec19c4d97ac9ce Component: engine --- components/engine/runtime_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index 73697137bc..ba1947f4ae 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -127,6 +127,9 @@ func cleanupDevMapper() { func init() { os.Setenv("TEST", "1") + os.Setenv("DOCKER_LOOPBACK_DATA_SIZE", "209715200") // 200MB + os.Setenv("DOCKER_LOOPBACK_META_SIZE", "104857600") // 100MB + os.Setenv("DOCKER_BASE_FS_SIZE", "157286400") // 150MB // Hack to run sys init during unit testing if selfPath := utils.SelfPath(); selfPath == "/sbin/init" || selfPath == "/.dockerinit" { From db7264277704ba1faccae487a5cdd6918b28eae1 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 14 Oct 2013 10:53:12 +0200 Subject: [PATCH 134/301] Add some docs for newly exported functions Upstream-commit: 8e4b3a33906785c3181151cc46bb323952f6eb2b Component: engine --- components/engine/changes.go | 3 +++ components/engine/utils/utils.go | 2 ++ 2 files changed, 5 insertions(+) diff --git a/components/engine/changes.go b/components/engine/changes.go index 77bef6fb22..bf31114cdd 100644 --- a/components/engine/changes.go +++ b/components/engine/changes.go @@ -283,6 +283,8 @@ func collectFileInfo(sourceDir string) (*FileInfo, error) { return root, nil } +// Compare a directory with an array of layer directories it was based on and +// generate an array of Change objects describing the changes func ChangesLayers(newDir string, layers []string) ([]Change, error) { newRoot, err := collectFileInfo(newDir) if err != nil { @@ -299,6 +301,7 @@ func ChangesLayers(newDir string, layers []string) ([]Change, error) { return newRoot.Changes(oldRoot), nil } +// Compare two directories and generate an array of Change objects describing the changes func ChangesDirs(newDir, oldDir string) ([]Change, error) { oldRoot, err := collectFileInfo(oldDir) if err != nil { diff --git a/components/engine/utils/utils.go b/components/engine/utils/utils.go index 5a46d8562b..4187da1aec 100644 --- a/components/engine/utils/utils.go +++ b/components/engine/utils/utils.go @@ -1051,6 +1051,8 @@ func quote(word string, buf *bytes.Buffer) { buf.WriteString("'") } +// Take a list of strings and escape them so they will be handled right +// when passed as arguments to an program via a shell func ShellQuoteArguments(args []string) string { var buf bytes.Buffer for i, arg := range args { From d4b9fc2a32d15af546574098d21f7a5f3009bfbd Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 14 Oct 2013 10:54:31 +0200 Subject: [PATCH 135/301] Runtime: Remove unused funtion hasFilesystemSupport() This used to be used to be used to detect AUFS support, but is not used anymore. Upstream-commit: bb42801cdcd0924ada0ff5ccb3a84aa9a1cbd916 Component: engine --- components/engine/runtime.go | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 4ea95a8b51..2ff1f0d10b 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -81,25 +81,6 @@ func (runtime *Runtime) getContainerElement(id string) *list.Element { return nil } -func hasFilesystemSupport(fstype string) bool { - content, err := ioutil.ReadFile("/proc/filesystems") - if err != nil { - log.Printf("WARNING: Unable to read /proc/filesystems, assuming fs %s is not supported.", fstype) - return false - } - lines := strings.Split(string(content), "\n") - for _, line := range lines { - if strings.HasPrefix(line, "nodev") { - line = line[5:] - } - line = strings.TrimSpace(line) - if line == fstype { - return true - } - } - return false -} - func (runtime *Runtime) GetDeviceSet() (DeviceSet, error) { if runtime.deviceSet == nil { return nil, fmt.Errorf("No device set available") From ef6f38c75b19ca09024d46922f7ceff1a913cb7b Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 11 Oct 2013 19:27:04 -0700 Subject: [PATCH 136/301] Do not allow image to be deleted when containers are dependent Upstream-commit: 7f429e0cebf9f7d0b203191646f2afa7b4bee35b Component: engine --- components/engine/server.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/components/engine/server.go b/components/engine/server.go index 30025fcaae..45d8bbf9cf 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -996,6 +996,14 @@ func (srv *Server) ContainerDestroy(name string, removeVolume bool) error { var ErrImageReferenced = errors.New("Image referenced by a repository") +func (srv *Server) getChildImages(id string) ([]*Image, error) { + byParents, err := srv.runtime.graph.ByParent() + if err != nil { + return nil, err + } + return byParents[id], nil +} + func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi) error { // If the image is referenced by a repo, do not delete if len(srv.runtime.repositories.ByID()[id]) != 0 { @@ -1101,6 +1109,33 @@ func (srv *Server) ImageDelete(name string, autoPrune bool) ([]APIRmi, error) { if err != nil { return nil, fmt.Errorf("No such image: %s", name) } + images := make(map[string]bool) + images[img.ID] = true + + children, err := srv.getChildImages(img.ID) + if err != nil { + return nil, err + } + + for _, i := range children { + images[i.ID] = true + } + + // Check for any containers referencing the image or children of the image + referencedContainers := []string{} + + for e := srv.runtime.containers.Front(); e != nil; e = e.Next() { + c := e.Value.(*Container) + if images[c.Image] { + referencedContainers = append(referencedContainers, c.ID) + } + } + + if len(referencedContainers) > 0 { + return nil, fmt.Errorf("Cannot delete image with existing containers. Please remove %s before deleting image.", + strings.Join(referencedContainers, ", ")) + } + if !autoPrune { if err := srv.runtime.DeleteImage(img.ID); err != nil { return nil, fmt.Errorf("Error deleting image %s: %s", name, err) From 1bca3c7d07d47278c3037fe89c2d837b9256f6f3 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 14 Oct 2013 12:26:46 -0700 Subject: [PATCH 137/301] Add error return to cleanup, use os.OpenFile instead of syscall.Open, Make sure the pools are removed after all other devices Upstream-commit: ea92dc2e8c42c4387eccf1ebb88e20ccc92c1853 Component: engine --- components/engine/runtime_test.go | 69 +++++++++++++++++-------------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index 964c1a9e93..35272727dc 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -87,56 +87,61 @@ func layerArchive(tarfile string) (io.Reader, error) { } // Remove any leftover device mapper devices from earlier runs of the unit tests -func removeDev(name string) { +func removeDev(name string) error { path := filepath.Join("/dev/mapper", name) - fd, err := syscall.Open(path, syscall.O_RDONLY, 07777) - if err != nil { - if err == syscall.ENXIO { + + if f, err := os.OpenFile(path, os.O_RDONLY, 07777); err != nil { + if er, ok := err.(*os.PathError); ok && er.Err == syscall.ENXIO { // No device for this node, just remove it - os.Remove(path) - return + return os.Remove(path) } } else { - syscall.Close(fd) + f.Close() } if err := devmapper.RemoveDevice(name); err != nil { - log.Fatalf("Unable to remove device %s needed to get a freash unit test environment", name) + return fmt.Errorf("Unable to remove device %s needed to get a freash unit test environment", name) } + return nil } -func cleanupDevMapper() { +func cleanupDevMapper() error { // Unmount any leftover mounts from previous unit test runs - if data, err := ioutil.ReadFile("/proc/mounts"); err == nil { + if data, err := ioutil.ReadFile("/proc/mounts"); err != nil { + return err + } else { for _, line := range strings.Split(string(data), "\n") { - cols := strings.Split(line, " ") - if len(cols) >= 2 && strings.HasPrefix(cols[0], "/dev/mapper/docker-unit-tests-devices") { - err = syscall.Unmount(cols[1], 0) - if err != nil { - log.Fatalf("Unable to unmount %s needed to get a freash unit test environment: %s", cols[1], err) + if cols := strings.Split(line, " "); len(cols) >= 2 && strings.HasPrefix(cols[0], "/dev/mapper/docker-unit-tests-devices") { + if err := syscall.Unmount(cols[1], 0); err != nil { + return fmt.Errorf("Unable to unmount %s needed to get a freash unit test environment: %s", cols[1], err) } } } } // Remove any leftover devmapper devices from previous unit run tests - infos, _ := ioutil.ReadDir("/dev/mapper") - if infos != nil { - hasPool := false - for _, info := range infos { - name := info.Name() - if strings.HasPrefix(name, "docker-unit-tests-devices-") { - if name == "docker-unit-tests-devices-pool" { - hasPool = true - } else { - removeDev(name) + infos, err := ioutil.ReadDir("/dev/mapper") + if err != nil { + return err + } + pools := []string{} + for _, info := range infos { + if name := info.Name(); strings.HasPrefix(name, "docker-unit-tests-devices-") { + if name == "docker-unit-tests-devices-pool" { + pools = append(pools, name) + } else { + if err := removeDev(name); err != nil { + return err } } - // We need to remove the pool last as the other devices block it - if hasPool { - removeDev("docker-unit-tests-devices-pool") - } } } + // We need to remove the pool last as the other devices block it + for _, pool := range pools { + if err := removeDev(pool); err != nil { + return err + } + } + return nil } func init() { @@ -154,7 +159,9 @@ func init() { NetworkBridgeIface = unitTestNetworkBridge - cleanupDevMapper() + if err := cleanupDevMapper(); err != nil { + log.Fatalf("Unable to cleanup devmapper: %s", err) + } // Always start from a clean set of loopback mounts err := os.RemoveAll(unitTestStoreDevicesBase) @@ -165,7 +172,7 @@ func init() { deviceset := devmapper.NewDeviceSetDM(unitTestStoreDevicesBase) // Create a device, which triggers the initiation of the base FS // This avoids other tests doing this and timing out - deviceset.AddDevice("init","") + deviceset.AddDevice("init", "") // Make it our Store root if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, deviceset, false); err != nil { From b5fc16147b3eafc883a3447b801fd853006afb86 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 14 Oct 2013 14:23:58 -0700 Subject: [PATCH 138/301] Ignore cleanup with /dev/mapper does not exist Upstream-commit: 5dd12ba20a962c0f67e5eefb7b0e00e5caccb1e1 Component: engine --- components/engine/runtime_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index dd670f0ce0..5a5a9c4cb6 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -121,6 +121,10 @@ func cleanupDevMapper() error { // Remove any leftover devmapper devices from previous unit run tests infos, err := ioutil.ReadDir("/dev/mapper") if err != nil { + // If the mapper file does not exist there is nothing to clean up + if os.IsNotExist(err) { + return nil + } return err } pools := []string{} @@ -148,7 +152,7 @@ func init() { os.Setenv("TEST", "1") os.Setenv("DOCKER_LOOPBACK_DATA_SIZE", "209715200") // 200MB os.Setenv("DOCKER_LOOPBACK_META_SIZE", "104857600") // 100MB - os.Setenv("DOCKER_BASE_FS_SIZE", "157286400") // 150MB + os.Setenv("DOCKER_BASE_FS_SIZE", "157286400") // 150MB // Hack to run sys init during unit testing if selfPath := utils.SelfPath(); selfPath == "/sbin/init" || selfPath == "/.dockerinit" { From ceadf839725ecdb3fc5288faba3fa253725e7f98 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 14 Oct 2013 17:48:43 -0700 Subject: [PATCH 139/301] Use incrementing prefix on pool and loopback to allow dind Upstream-commit: 3455c1a0983ae74d22b05abc3c0552460c6a2710 Component: engine --- .../engine/devmapper/deviceset_devmapper.go | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index 720b57294b..baccb17fb9 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -362,19 +362,45 @@ func (devices *DeviceSetDM) log(level int, file string, line int, dmError int, m func (devices *DeviceSetDM) initDevmapper() error { logInit(devices) +begin: info, err := getInfo(devices.getPoolName()) if info == nil { utils.Debugf("Error device getInfo: %s", err) return err } - utils.Debugf("initDevmapper(). Pool exists: %v", info.Exists) + + loopbackExists := false + if _, err := os.Stat(devices.loopbackDir()); err != nil { + if !os.IsNotExist(err) { + return err + } + // If it does not, then we use a different pool name + parts := strings.Split(devices.devicePrefix, "-") + i, err := strconv.Atoi(parts[len(parts)-1]) + if err != nil { + i = 0 + parts = append(parts, "0") + } + i++ + parts[len(parts)-1] = strconv.Itoa(i) + devices.devicePrefix = strings.Join(parts, "-") + } else { + loopbackExists = true + } + + // If the pool exists but the loopback does not, then we start again + if info.Exists == 1 && !loopbackExists { + goto begin + } + + utils.Debugf("initDevmapper(). Pool exists: %v, loopback Exists: %v", info.Exists, loopbackExists) // It seems libdevmapper opens this without O_CLOEXEC, and go exec will not close files // that are not Close-on-exec, and lxc-start will die if it inherits any unexpected files, // so we add this badhack to make sure it closes itself setCloseOnExec("/dev/mapper/control") - if info.Exists != 0 { + if info.Exists != 0 && loopbackExists { /* Pool exists, assume everything is up */ if err := devices.loadMetaData(); err != nil { utils.Debugf("Error device loadMetaData: %s\n", err) From 3b33148b0889a0e2a2c24717649b1176cd1a5c0b Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 14 Oct 2013 18:33:29 -0700 Subject: [PATCH 140/301] Make sure the base device is well created before running the tests Upstream-commit: 5778ed7db2fee3b7005a53a06d010272504924e9 Component: engine --- components/engine/runtime_test.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index dd670f0ce0..995567c06b 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -148,7 +148,7 @@ func init() { os.Setenv("TEST", "1") os.Setenv("DOCKER_LOOPBACK_DATA_SIZE", "209715200") // 200MB os.Setenv("DOCKER_LOOPBACK_META_SIZE", "104857600") // 100MB - os.Setenv("DOCKER_BASE_FS_SIZE", "157286400") // 150MB + os.Setenv("DOCKER_BASE_FS_SIZE", "157286400") // 150MB // Hack to run sys init during unit testing if selfPath := utils.SelfPath(); selfPath == "/sbin/init" || selfPath == "/.dockerinit" { @@ -167,15 +167,16 @@ func init() { } // Always start from a clean set of loopback mounts - err := os.RemoveAll(unitTestStoreDevicesBase) - if err != nil { - panic(err) + if err := os.RemoveAll(unitTestStoreDevicesBase); err != nil { + log.Fatalf("Unable to remove former unit-test directory: %s", err) } deviceset := devmapper.NewDeviceSetDM(unitTestStoreDevicesBase) // Create a device, which triggers the initiation of the base FS // This avoids other tests doing this and timing out - deviceset.AddDevice("init", "") + if err := deviceset.AddDevice("init", ""); err != nil { + log.Fatalf("Unable to create the base device: %s", err) + } // Make it our Store root if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, deviceset, false); err != nil { From 5b6d6020fa37f666bc0a5ae7544ce208b9233fdf Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 12 Oct 2013 18:47:00 -0700 Subject: [PATCH 141/301] Initialize devicemapper in NewRuntimeFromDIrectory Upstream-commit: 7093411a8dd36612ddc636c14395e33ecff47f7f Component: engine --- components/engine/docker/docker.go | 3 +-- components/engine/runtime.go | 9 ++++++--- components/engine/runtime_test.go | 24 ++++++++++-------------- components/engine/server.go | 4 ++-- components/engine/utils_test.go | 3 +-- 5 files changed, 20 insertions(+), 23 deletions(-) diff --git a/components/engine/docker/docker.go b/components/engine/docker/docker.go index 8118dbb611..cb1a5020f4 100644 --- a/components/engine/docker/docker.go +++ b/components/engine/docker/docker.go @@ -4,7 +4,6 @@ import ( "flag" "fmt" "github.com/dotcloud/docker" - "github.com/dotcloud/docker/devmapper" "github.com/dotcloud/docker/utils" "io/ioutil" "log" @@ -134,7 +133,7 @@ func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart if flDns != "" { dns = []string{flDns} } - server, err := docker.NewServer(flGraphPath, devmapper.NewDeviceSetDM(flGraphPath), autoRestart, enableCors, dns) + server, err := docker.NewServer(flGraphPath, autoRestart, enableCors, dns) if err != nil { return err } diff --git a/components/engine/runtime.go b/components/engine/runtime.go index dfcb2c1c70..9799c86b34 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -4,6 +4,7 @@ import ( "container/list" "fmt" "github.com/dotcloud/docker/utils" + "github.com/dotcloud/docker/devmapper" "io" "io/ioutil" "log" @@ -555,8 +556,8 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a } // FIXME: harmonize with NewGraph() -func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns []string) (*Runtime, error) { - runtime, err := NewRuntimeFromDirectory(flGraphPath, deviceSet, autoRestart) +func NewRuntime(flGraphPath string, autoRestart bool, dns []string) (*Runtime, error) { + runtime, err := NewRuntimeFromDirectory(flGraphPath, autoRestart) if err != nil { return nil, err } @@ -574,7 +575,7 @@ func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns [ return runtime, nil } -func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool) (*Runtime, error) { +func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) { runtimeRepo := path.Join(root, "containers") if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) { @@ -600,6 +601,8 @@ func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool) if err != nil { return nil, err } + deviceSet := devmapper.NewDeviceSetDM(root) + // Initialize devicemapper deviceSet runtime := &Runtime{ root: root, repository: runtimeRepo, diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index dd670f0ce0..cebd16a4fb 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -25,7 +25,6 @@ const ( unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 unitTestNetworkBridge = "testdockbr0" unitTestStoreBase = "/var/lib/docker/unit-tests" - unitTestStoreDevicesBase = "/var/lib/docker/unit-tests-devices" testDaemonAddr = "127.0.0.1:4270" testDaemonProto = "tcp" ) @@ -51,6 +50,9 @@ func nuke(runtime *Runtime) error { for _, container := range runtime.List() { container.EnsureUnmounted() } + if err := runtime.deviceSet.Shutdown(); err != nil { + utils.Debugf("Error shutting down devicemapper for runtime %s", runtime.root) + } return os.RemoveAll(runtime.root) } @@ -166,24 +168,18 @@ func init() { log.Fatalf("Unable to cleanup devmapper: %s", err) } - // Always start from a clean set of loopback mounts - err := os.RemoveAll(unitTestStoreDevicesBase) - if err != nil { - panic(err) - } - - deviceset := devmapper.NewDeviceSetDM(unitTestStoreDevicesBase) - // Create a device, which triggers the initiation of the base FS - // This avoids other tests doing this and timing out - deviceset.AddDevice("init", "") - // Make it our Store root - if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, deviceset, false); err != nil { + if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false); err != nil { log.Fatalf("Unable to create a runtime for tests:", err) } else { globalRuntime = runtime } + // Create a device, which triggers the initiation of the base FS + // This avoids other tests doing this and timing out + deviceset := devmapper.NewDeviceSetDM(unitTestStoreBase) + deviceset.AddDevice("init", "") + // Create the "Server" srv := &Server{ runtime: globalRuntime, @@ -548,7 +544,7 @@ func TestRestore(t *testing.T) { // Here are are simulating a docker restart - that is, reloading all containers // from scratch - runtime2, err := NewRuntimeFromDirectory(runtime1.root, runtime1.deviceSet, false) + runtime2, err := NewRuntimeFromDirectory(runtime1.root, false) if err != nil { t.Fatal(err) } diff --git a/components/engine/server.go b/components/engine/server.go index 45d8bbf9cf..7e6c556a3f 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -1337,11 +1337,11 @@ func (srv *Server) ContainerCopy(name string, resource string, out io.Writer) er } -func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors bool, dns ListOpts) (*Server, error) { +func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) (*Server, error) { if runtime.GOARCH != "amd64" { log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) } - runtime, err := NewRuntime(flGraphPath, deviceSet, autoRestart, dns) + runtime, err := NewRuntime(flGraphPath, autoRestart, dns) if err != nil { return nil, err } diff --git a/components/engine/utils_test.go b/components/engine/utils_test.go index 9c7ab2f26c..013626a03a 100644 --- a/components/engine/utils_test.go +++ b/components/engine/utils_test.go @@ -6,7 +6,6 @@ import ( "io/ioutil" "os" "path" - "path/filepath" "strings" "testing" ) @@ -43,7 +42,7 @@ func newTestRuntime() (*Runtime, error) { return nil, err } - runtime, err := NewRuntimeFromDirectory(root, NewDeviceSetWrapper(globalRuntime.deviceSet, filepath.Base(root)), false) + runtime, err := NewRuntimeFromDirectory(root, false) if err != nil { return nil, err } From 821d8910fe340050ab4a88d3ba4bad63cd90c462 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 15 Oct 2013 11:30:06 -0700 Subject: [PATCH 142/301] Rename loopback dir to devicemapper Upstream-commit: 4431e9edb7cf49ec7da8201da1221b5b03ea09ee Component: engine --- components/engine/devmapper/deviceset_devmapper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index 720b57294b..013c6324d4 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -88,7 +88,7 @@ func (info *DevInfo) DevName() string { } func (devices *DeviceSetDM) loopbackDir() string { - return path.Join(devices.root, "loopback") + return path.Join(devices.root, "devicemapper") } func (devices *DeviceSetDM) jsonFile() string { From 246243d186d9ab8c09d8c2bc4a3924dceb9ec544 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 15 Oct 2013 11:49:13 -0700 Subject: [PATCH 143/301] Add filesystemtype for containers If no type is specified then assume aufs. Upstream-commit: 80bd64245f14d4d8a6fc8349cff8b441d770da42 Component: engine --- components/engine/container.go | 1 + components/engine/runtime.go | 23 +++++++++++++++++------ components/engine/runtime_test.go | 14 +++++++++++++- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/components/engine/container.go b/components/engine/container.go index 8dfcbcf3eb..f133bce142 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -43,6 +43,7 @@ type Container struct { ResolvConfPath string HostnamePath string HostsPath string + FilesystemType string cmd *exec.Cmd stdout *utils.WriteBroadcaster diff --git a/components/engine/runtime.go b/components/engine/runtime.go index dfcb2c1c70..39c9913413 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -16,6 +16,10 @@ import ( "time" ) +const ( + DefaultFilesystemType = "devicemapper" +) + var defaultDns = []string{"8.8.8.8", "8.8.4.4"} type Capabilities struct { @@ -268,9 +272,10 @@ func (runtime *Runtime) restore() error { return err } - deviceSet := runtime.deviceSet - containers := []*Container{} - containersToMigrate := []*Container{} + var ( + containers []*Container + containersToMigrate []*Container + ) for i, v := range dir { id := v.Name() @@ -285,12 +290,12 @@ func (runtime *Runtime) restore() error { utils.Debugf("Loaded container %v", container.ID) containers = append(containers, container) - if !deviceSet.HasDevice(container.ID) { + if container.FilesystemType != DefaultFilesystemType { containersToMigrate = append(containersToMigrate, container) } } - // Migrate AUFS containers to device mapper + // Migrate containers to the default filesystem type if len(containersToMigrate) > 0 { if err := migrateToDeviceMapper(runtime, containersToMigrate); err != nil { return err @@ -366,6 +371,11 @@ func migrateToDeviceMapper(runtime *Runtime, containers []*Container) error { fmt.Printf("Failed to remove rw layer %s\n", err) } + container.FilesystemType = DefaultFilesystemType + if err := container.ToDisk(); err != nil { + fmt.Printf("Failed to save filesystem type to disk %s\n", err) + } + fmt.Printf("Successful migration for %s\n", container.ID) } fmt.Printf("Migration complete\n") @@ -448,7 +458,8 @@ func (runtime *Runtime) Create(config *Config) (*Container, error) { Image: img.ID, // Always use the resolved image id NetworkSettings: &NetworkSettings{}, // FIXME: do we need to store this in the container? - SysInitPath: sysInitPath, + SysInitPath: sysInitPath, + FilesystemType: DefaultFilesystemType, } container.root = runtime.containerRoot(container.ID) // Step 1: create the container directory. diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index dd670f0ce0..6cae6e6f82 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -148,7 +148,7 @@ func init() { os.Setenv("TEST", "1") os.Setenv("DOCKER_LOOPBACK_DATA_SIZE", "209715200") // 200MB os.Setenv("DOCKER_LOOPBACK_META_SIZE", "104857600") // 100MB - os.Setenv("DOCKER_BASE_FS_SIZE", "157286400") // 150MB + os.Setenv("DOCKER_BASE_FS_SIZE", "157286400") // 150MB // Hack to run sys init during unit testing if selfPath := utils.SelfPath(); selfPath == "/sbin/init" || selfPath == "/.dockerinit" { @@ -575,3 +575,15 @@ func TestRestore(t *testing.T) { } container2.State.Running = false } + +func TestContainerCreatedWithDefaultFilesystemType(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + + container, _, _ := mkContainer(runtime, []string{"_", "ls", "-al"}, t) + defer runtime.Destroy(container) + + if container.FilesystemType != DefaultFilesystemType { + t.Fatalf("Container filesystem type should be %s but got %s", DefaultFilesystemType, container.FilesystemType) + } +} From 3dc44296240bf3c7a35f898d09715162fbd9babd Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 15 Oct 2013 21:27:47 +0000 Subject: [PATCH 144/301] devmapper: create device nodes 'on create' instead of 'on resume' Upstream-commit: 5ebaca7e55e006083e6e9c2782e4f8b421275579 Component: engine --- components/engine/devmapper/devmapper.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/components/engine/devmapper/devmapper.go b/components/engine/devmapper/devmapper.go index 97a388e4c9..a925c1a996 100644 --- a/components/engine/devmapper/devmapper.go +++ b/components/engine/devmapper/devmapper.go @@ -175,6 +175,11 @@ const ( DeviceSetGeometry ) +const ( + AddNodeOnResume AddNodeType = iota + AddNodeOnCreate +) + var ( ErrTaskRun = errors.New("dm_task_run failed") ErrTaskSetName = errors.New("dm_task_set_name failed") @@ -209,6 +214,7 @@ type ( TargetCount int32 } TaskType int + AddNodeType int ) func (t *Task) destroy() { @@ -274,6 +280,13 @@ func (t *Task) SetCookie(cookie *uint32, flags uint16) error { return nil } +func (t *Task) SetAddNode(add_node AddNodeType) error { + if res := C.dm_task_set_add_node(t.unmanaged, C.dm_add_node_t (add_node)); res != 1 { + return ErrTaskSetAddNode + } + return nil +} + func (t *Task) SetRo() error { if res := C.dm_task_set_ro(t.unmanaged); res != 1 { return ErrTaskSetRO @@ -614,6 +627,9 @@ func activateDevice(poolName string, name string, deviceId int, size uint64) err if err := task.AddTarget(0, size/512, "thin", params); err != nil { return fmt.Errorf("Can't add target") } + if err := task.SetAddNode(AddNodeOnCreate); err != nil { + return fmt.Errorf("Can't add node") + } var cookie uint32 = 0 if err := task.SetCookie(&cookie, 0); err != nil { From 242fae9ab304f90b63373ed2683dd72d51b72b44 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 15 Oct 2013 21:54:52 +0000 Subject: [PATCH 145/301] hack: only run certain tests with TESTFLAGS='-run TestName' make.sh Upstream-commit: ec885d91806595add47c793e1e6bb8a4b9e8d100 Component: engine --- components/engine/hack/make/test | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/components/engine/hack/make/test b/components/engine/hack/make/test index 10264d9f7f..732eb7839c 100644 --- a/components/engine/hack/make/test +++ b/components/engine/hack/make/test @@ -5,13 +5,18 @@ DEST=$1 set -e # Run Docker's test suite, including sub-packages, and store their output as a bundle +# If $TESTFLAGS is set in the environment, it is passed as extra arguments to 'go test'. +# You can use this to select certain tests to run, eg. +# +# TESTFLAGS='-run ^TestBuild$' ./hack/make.sh test +# bundle_test() { { date for test_dir in $(find_test_dirs); do ( set -x cd $test_dir - DEBUG=1 go test -v -ldflags "$LDFLAGS" $BUILDFLAGS + DEBUG=1 go test -v -ldflags "$LDFLAGS" $BUILDFLAGS $TESTFLAGS ) done } 2>&1 | tee $DEST/test.log } From 560c0ccd1019a8ea57b61e1ec7982b25c6aad228 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 15 Oct 2013 23:07:26 +0000 Subject: [PATCH 146/301] hack: don't set DEBUG when running tests Upstream-commit: 3997b8a923c3e95c14237657f0e2c9301fc5f66d Component: engine --- components/engine/hack/make/test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/hack/make/test b/components/engine/hack/make/test index 732eb7839c..44f8bd6c3e 100644 --- a/components/engine/hack/make/test +++ b/components/engine/hack/make/test @@ -16,7 +16,7 @@ bundle_test() { for test_dir in $(find_test_dirs); do ( set -x cd $test_dir - DEBUG=1 go test -v -ldflags "$LDFLAGS" $BUILDFLAGS $TESTFLAGS + go test -v -ldflags "$LDFLAGS" $BUILDFLAGS $TESTFLAGS ) done } 2>&1 | tee $DEST/test.log } From 05176434d1f1f037df06a94f65a980b3bc88cb1e Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 15 Oct 2013 23:56:04 +0000 Subject: [PATCH 147/301] WIP: debugging dm-base-hash + dm-refactor-init Upstream-commit: cd61fb2e6f9d2a7bbd3c4db2ab9091674746126d Component: engine --- components/engine/devmapper/devmapper.go | 3 +++ components/engine/runtime_test.go | 12 +++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/components/engine/devmapper/devmapper.go b/components/engine/devmapper/devmapper.go index a925c1a996..c08e10171c 100644 --- a/components/engine/devmapper/devmapper.go +++ b/components/engine/devmapper/devmapper.go @@ -353,6 +353,9 @@ func AttachLoopDevice(filename string) (*os.File, error) { var fd C.int res := C.attach_loop_device(c_filename, &fd) if res == nil { + if os.Getenv("DEBUG") != "" { + C.perror(C.CString(fmt.Sprintf("[debug] Error attach_loop_device(%s, $#v)", c_filename, &fd))) + } return nil, ErrAttachLoopbackDevice } defer free(res) diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index d0c489de7a..dfb6306c5f 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -1,6 +1,7 @@ package docker import ( + "path" "bytes" "fmt" "github.com/dotcloud/docker/devmapper" @@ -107,12 +108,15 @@ func removeDev(name string) error { } func cleanupDevMapper() error { + filter := "docker-" + path.Base(unitTestStoreBase) + utils.Debugf("Filtering out %s\n", filter) // Unmount any leftover mounts from previous unit test runs if data, err := ioutil.ReadFile("/proc/mounts"); err != nil { return err } else { for _, line := range strings.Split(string(data), "\n") { - if cols := strings.Split(line, " "); len(cols) >= 2 && strings.HasPrefix(cols[0], "/dev/mapper/docker-unit-tests-devices") { + if cols := strings.Split(line, " "); len(cols) >= 2 && strings.HasPrefix(cols[0], path.Join("/dev/mapper", filter)) { + utils.Debugf("[devmapper cleanup] Unmounting device: %s", cols[1]) if err := syscall.Unmount(cols[1], 0); err != nil { return fmt.Errorf("Unable to unmount %s needed to get a freash unit test environment: %s", cols[1], err) } @@ -120,6 +124,7 @@ func cleanupDevMapper() error { } } + utils.Debugf("[devmapper cleanup] looking for leftover devices") // Remove any leftover devmapper devices from previous unit run tests infos, err := ioutil.ReadDir("/dev/mapper") if err != nil { @@ -131,8 +136,8 @@ func cleanupDevMapper() error { } pools := []string{} for _, info := range infos { - if name := info.Name(); strings.HasPrefix(name, "docker-unit-tests-devices-") { - if name == "docker-unit-tests-devices-pool" { + if name := info.Name(); strings.HasPrefix(name, filter + "-") { + if strings.HasSuffix(name, "-pool") { pools = append(pools, name) } else { if err := removeDev(name); err != nil { @@ -143,6 +148,7 @@ func cleanupDevMapper() error { } // We need to remove the pool last as the other devices block it for _, pool := range pools { + utils.Debugf("[devmapper cleanup] Removing pool: %s", pool) if err := removeDev(pool); err != nil { return err } From 9c9b801114a481605273677360d76aa631c33cb9 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 16 Oct 2013 20:10:20 +0000 Subject: [PATCH 148/301] Hack: don't run integration tests in /var/lib/docker/unit-tests; add missing cleanups in a few tests Upstream-commit: 1da335f784882292fb55b25bf255ec5f45072ea4 Component: engine --- components/engine/api_test.go | 1 + components/engine/runtime_test.go | 49 +++++++++++++++++++++---------- components/engine/server_test.go | 1 + components/engine/z_final_test.go | 2 +- 4 files changed, 37 insertions(+), 16 deletions(-) diff --git a/components/engine/api_test.go b/components/engine/api_test.go index 7223411b2d..35ecf574e0 100644 --- a/components/engine/api_test.go +++ b/components/engine/api_test.go @@ -91,6 +91,7 @@ func TestGetInfo(t *testing.T) { func TestGetEvents(t *testing.T) { runtime := mkRuntime(t) + defer nuke(runtime) srv := &Server{ runtime: runtime, events: make([]utils.JSONMessage, 0, 64), diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index dfb6306c5f..79b39ddb89 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -74,12 +74,6 @@ func cleanup(runtime *Runtime) error { return nil } -func cleanupLast(runtime *Runtime) error { - cleanup(runtime) - runtime.deviceSet.Shutdown() - return nil -} - func layerArchive(tarfile string) (io.Reader, error) { // FIXME: need to close f somewhere f, err := os.Open(tarfile) @@ -178,11 +172,20 @@ func init() { log.Fatalf("Unable to cleanup devmapper: %s", err) } - // Make it our Store root - if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false); err != nil { + // Setup the base runtime, which will be duplicated for each test. + // (no tests are run directly in the base) + setupBaseImage() + + // Create the "global runtime" with a long-running daemon for integration tests + spawnGlobalDaemon() + startFds, startGoroutines = utils.GetTotalUsedFds(), runtime.NumGoroutine() +} + + +func setupBaseImage() { + runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false) + if err != nil { log.Fatalf("Unable to create a runtime for tests:", err) - } else { - globalRuntime = runtime } // Create a device, which triggers the initiation of the base FS @@ -192,29 +195,45 @@ func init() { // Create the "Server" srv := &Server{ - runtime: globalRuntime, + runtime: runtime, enableCors: false, pullingPool: make(map[string]struct{}), pushingPool: make(map[string]struct{}), } + // If the unit test is not found, try to download it. - if img, err := globalRuntime.repositories.LookupImage(unitTestImageName); err != nil || img.ID != unitTestImageID { + if img, err := runtime.repositories.LookupImage(unitTestImageName); err != nil || img.ID != unitTestImageID { // Retrieve the Image if err := srv.ImagePull(unitTestImageName, "", os.Stdout, utils.NewStreamFormatter(false), nil, nil, true); err != nil { log.Fatalf("Unable to pull the test image:", err) } } +} + + +func spawnGlobalDaemon() { + if globalRuntime != nil { + utils.Debugf("Global runtime already exists. Skipping.") + return + } + globalRuntime = mkRuntime(log.New(os.Stderr, "", 0)) + srv := &Server{ + runtime: globalRuntime, + enableCors: false, + pullingPool: make(map[string]struct{}), + pushingPool: make(map[string]struct{}), + } + // Spawn a Daemon go func() { + utils.Debugf("Spawning global daemon for integration tests") if err := ListenAndServe(testDaemonProto, testDaemonAddr, srv, os.Getenv("DEBUG") != ""); err != nil { log.Fatalf("Unable to spawn the test daemon:", err) } }() - // Give some time to ListenAndServer to actually start + // FIXME: use inmem transports instead of tcp time.Sleep(time.Second) - - startFds, startGoroutines = utils.GetTotalUsedFds(), runtime.NumGoroutine() } // FIXME: test that ImagePull(json=true) send correct json output diff --git a/components/engine/server_test.go b/components/engine/server_test.go index ee9549d7b7..c13754b5ea 100644 --- a/components/engine/server_test.go +++ b/components/engine/server_test.go @@ -351,6 +351,7 @@ func TestPools(t *testing.T) { func TestLogEvent(t *testing.T) { runtime := mkRuntime(t) + defer nuke(runtime) srv := &Server{ runtime: runtime, events: make([]utils.JSONMessage, 0, 64), diff --git a/components/engine/z_final_test.go b/components/engine/z_final_test.go index c52f87cddb..837b5d13e6 100644 --- a/components/engine/z_final_test.go +++ b/components/engine/z_final_test.go @@ -11,7 +11,7 @@ func displayFdGoroutines(t *testing.T) { } func TestFinal(t *testing.T) { - cleanupLast(globalRuntime) + nuke(globalRuntime) t.Logf("Start Fds: %d, Start Goroutines: %d", startFds, startGoroutines) displayFdGoroutines(t) } From e0dfea89d27e3601c2ebc3096a80f9788f4bd92e Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 16 Oct 2013 20:42:50 +0000 Subject: [PATCH 149/301] Change default values for devicemapper as variable instead of env Upstream-commit: 3a246ac3d1368931998a082043c9b719dd3c10cd Component: engine --- .../engine/devmapper/deviceset_devmapper.go | 37 ++++--------------- components/engine/runtime_test.go | 14 ++++--- 2 files changed, 16 insertions(+), 35 deletions(-) diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index 876d781fba..e55b3d482f 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -17,9 +17,9 @@ import ( ) var ( - defaultDataLoopbackSize int64 = 100 * 1024 * 1024 * 1024 - defaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024 - defaultBaseFsSize uint64 = 10 * 1024 * 1024 * 1024 + DefaultDataLoopbackSize int64 = 100 * 1024 * 1024 * 1024 + DefaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024 + DefaultBaseFsSize uint64 = 10 * 1024 * 1024 * 1024 ) type DevInfo struct { @@ -47,30 +47,6 @@ type DeviceSetDM struct { activeMounts map[string]int } -func init() { - var err error - - rawMetaSize := os.Getenv("DOCKER_LOOPBACK_META_SIZE") - rawDataSize := os.Getenv("DOCKER_LOOPBACK_DATA_SIZE") - rawBaseFSSize := os.Getenv("DOCKER_BASE_FS_SIZE") - - if rawMetaSize != "" { - if defaultMetaDataLoopbackSize, err = strconv.ParseInt(rawMetaSize, 0, 0); err != nil { - panic(err) - } - } - if rawDataSize != "" { - if defaultDataLoopbackSize, err = strconv.ParseInt(rawDataSize, 0, 0); err != nil { - panic(err) - } - } - if rawBaseFSSize != "" { - if defaultBaseFsSize, err = strconv.ParseUint(rawBaseFSSize, 0, 0); err != nil { - panic(err) - } - } -} - func getDevName(name string) string { return "/dev/mapper/" + name } @@ -307,7 +283,8 @@ func (devices *DeviceSetDM) setupBaseImage() error { return err } - info, err := devices.registerDevice(id, "", defaultBaseFsSize) + utils.Debugf("Registering base device (id %v) with FS size %v", id, DefaultBaseFsSize) + info, err := devices.registerDevice(id, "", DefaultBaseFsSize) if err != nil { _ = deleteDevice(devices.getPoolDevName(), id) utils.Debugf("\n--->Err: %s\n", err) @@ -416,13 +393,13 @@ begin: /* If we create the loopback mounts we also need to initialize the base fs */ createdLoopback := !devices.hasImage("data") || !devices.hasImage("metadata") - data, err := devices.ensureImage("data", defaultDataLoopbackSize) + data, err := devices.ensureImage("data", DefaultDataLoopbackSize) if err != nil { utils.Debugf("Error device ensureImage (data): %s\n", err) return err } - metadata, err := devices.ensureImage("metadata", defaultMetaDataLoopbackSize) + metadata, err := devices.ensureImage("metadata", DefaultMetaDataLoopbackSize) if err != nil { utils.Debugf("Error device ensureImage (metadata): %s\n", err) return err diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index 79b39ddb89..9c044538d7 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -28,6 +28,10 @@ const ( unitTestStoreBase = "/var/lib/docker/unit-tests" testDaemonAddr = "127.0.0.1:4270" testDaemonProto = "tcp" + + unitTestDMDataLoopbackSize = 209715200 // 200MB + unitTestDMMetaDataLoopbackSize = 104857600 // 100MB + unitTestDMBaseFsSize = 157286400 // 150MB ) var ( @@ -152,9 +156,11 @@ func cleanupDevMapper() error { func init() { os.Setenv("TEST", "1") - os.Setenv("DOCKER_LOOPBACK_DATA_SIZE", "209715200") // 200MB - os.Setenv("DOCKER_LOOPBACK_META_SIZE", "104857600") // 100MB - os.Setenv("DOCKER_BASE_FS_SIZE", "157286400") // 150MB + // Set unit-test specific values + devmapper.DefaultDataLoopbackSize = unitTestDMDataLoopbackSize + devmapper.DefaultMetaDataLoopbackSize = unitTestDMMetaDataLoopbackSize + devmapper.DefaultBaseFsSize = unitTestDMBaseFsSize + NetworkBridgeIface = unitTestNetworkBridge // Hack to run sys init during unit testing if selfPath := utils.SelfPath(); selfPath == "/sbin/init" || selfPath == "/.dockerinit" { @@ -166,8 +172,6 @@ func init() { log.Fatal("docker tests need to be run as root") } - NetworkBridgeIface = unitTestNetworkBridge - if err := cleanupDevMapper(); err != nil { log.Fatalf("Unable to cleanup devmapper: %s", err) } From 64572038db7a3f03b8e5a44d5af873da12ef7a9f Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 16 Oct 2013 20:44:15 +0000 Subject: [PATCH 150/301] Hack: fix tests which didn't cleanup properly Upstream-commit: acf58362cb957bf4d02af460935a9c5febd112f4 Component: engine --- components/engine/auth/auth_test.go | 5 ++++- components/engine/container_test.go | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/components/engine/auth/auth_test.go b/components/engine/auth/auth_test.go index 01aecae3da..5dc634a719 100644 --- a/components/engine/auth/auth_test.go +++ b/components/engine/auth/auth_test.go @@ -76,7 +76,7 @@ func TestCreateAccount(t *testing.T) { } func setupTempConfigFile() (*ConfigFile, error) { - root, err := ioutil.TempDir("", "docker-test") + root, err := ioutil.TempDir("", "docker-test-auth") if err != nil { return nil, err } @@ -101,6 +101,7 @@ func TestSameAuthDataPostSave(t *testing.T) { if err != nil { t.Fatal(err) } + defer os.RemoveAll(configFile.rootPath) err = SaveConfig(configFile) if err != nil { @@ -127,6 +128,7 @@ func TestResolveAuthConfigIndexServer(t *testing.T) { if err != nil { t.Fatal(err) } + defer os.RemoveAll(configFile.rootPath) for _, registry := range []string{"", IndexServerAddress()} { resolved := configFile.ResolveAuthConfig(registry) @@ -141,6 +143,7 @@ func TestResolveAuthConfigFullURL(t *testing.T) { if err != nil { t.Fatal(err) } + defer os.RemoveAll(configFile.rootPath) registryAuth := AuthConfig{ Username: "foo-user", diff --git a/components/engine/container_test.go b/components/engine/container_test.go index e678c98898..9fdddc9044 100644 --- a/components/engine/container_test.go +++ b/components/engine/container_test.go @@ -1189,7 +1189,7 @@ func BenchmarkRunParallel(b *testing.B) { } func tempDir(t *testing.T) string { - tmpDir, err := ioutil.TempDir("", "docker-test") + tmpDir, err := ioutil.TempDir("", "docker-test-container") if err != nil { t.Fatal(err) } From d7aa3ef25d7f05dfdcef97adf405cb79d9ec831a Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 16 Oct 2013 20:45:59 +0000 Subject: [PATCH 151/301] Add debug messages while testing devicemapper Upstream-commit: 11d695a2973d67906145e7f0972b6e693bdaa3f9 Component: engine --- components/engine/devmapper/deviceset_devmapper.go | 4 +++- components/engine/devmapper/devmapper.go | 1 + components/engine/runtime_test.go | 2 ++ components/engine/utils_test.go | 10 ++++++++-- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index e55b3d482f..63783dcb63 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -168,6 +168,7 @@ func (devices *DeviceSetDM) saveMetadata() error { } func (devices *DeviceSetDM) registerDevice(id int, hash string, size uint64) (*DevInfo, error) { + utils.Debugf("registerDevice(%v, %v)", id, hash) info := &DevInfo{ Hash: hash, DeviceId: id, @@ -188,7 +189,7 @@ func (devices *DeviceSetDM) registerDevice(id int, hash string, size uint64) (*D } func (devices *DeviceSetDM) activateDeviceIfNeeded(hash string) error { - utils.Debugf("activateDeviceIfNeeded()") + utils.Debugf("activateDeviceIfNeeded(%v)", hash) info := devices.Devices[hash] if info == nil { return fmt.Errorf("Unknown device %s", hash) @@ -560,6 +561,7 @@ func (devices *DeviceSetDM) DeactivateDevice(hash string) error { func (devices *DeviceSetDM) Shutdown() error { devices.Lock() + utils.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root) defer devices.Unlock() if !devices.initialized { diff --git a/components/engine/devmapper/devmapper.go b/components/engine/devmapper/devmapper.go index c08e10171c..d0fb34465e 100644 --- a/components/engine/devmapper/devmapper.go +++ b/components/engine/devmapper/devmapper.go @@ -570,6 +570,7 @@ func resumeDevice(name string) error { } func createDevice(poolName string, deviceId int) error { + utils.Debugf("[devmapper] createDevice(poolName=%v, deviceId=%v)", poolName, deviceId) task, err := createTask(DeviceTargetMsg, poolName) if task == nil { return err diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index 9c044538d7..49ecd3b360 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -106,6 +106,8 @@ func removeDev(name string) error { } func cleanupDevMapper() error { + utils.Debugf("[devmapper cleanup] starting") + defer utils.Debugf("[devmapper cleanup] done") filter := "docker-" + path.Base(unitTestStoreBase) utils.Debugf("Filtering out %s\n", filter) // Unmount any leftover mounts from previous unit test runs diff --git a/components/engine/utils_test.go b/components/engine/utils_test.go index 013626a03a..a7afa289dc 100644 --- a/components/engine/utils_test.go +++ b/components/engine/utils_test.go @@ -30,19 +30,25 @@ type Fataler interface { Fatal(args ...interface{}) } -func newTestRuntime() (*Runtime, error) { +func newTestRuntime() (runtime *Runtime, err error) { + utils.Debugf("newTestRuntime start") root, err := ioutil.TempDir("", "docker-test") + defer func() { + utils.Debugf("newTestRuntime: %s", root) + }() if err != nil { return nil, err } if err := os.Remove(root); err != nil { return nil, err } + utils.Debugf("Copying %s to %s", unitTestStoreBase, root) if err := utils.CopyDirectory(unitTestStoreBase, root); err != nil { + utils.Debugf("ERROR: Copying %s to %s returned %s", unitTestStoreBase, root, err) return nil, err } - runtime, err := NewRuntimeFromDirectory(root, false) + runtime, err = NewRuntimeFromDirectory(root, false) if err != nil { return nil, err } From c659ac4a843cd2d0d7116252d86642d151bed2a0 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 16 Oct 2013 23:06:07 +0000 Subject: [PATCH 152/301] devmapper: wait for devices to be effectively unmounted before removing them Upstream-commit: 1711de4b0996a6fd668f1d1ba07901d04f090810 Component: engine --- .../engine/devmapper/deviceset_devmapper.go | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index 63783dcb63..b778228a59 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -1,6 +1,7 @@ package devmapper import ( + "time" "encoding/json" "fmt" "github.com/dotcloud/docker/utils" @@ -546,6 +547,32 @@ func (devices *DeviceSetDM) deactivateDevice(hash string) error { return nil } +// waitClose blocks until either: +// a) the device registered at - is closed, +// or b) the 1 second timeout expires. +func (devices *DeviceSetDM) waitClose(hash string) error { + devname, err := devices.byHash(hash) + if err != nil { + return err + } + i := 0 + for ; i<1000; i+=1 { + devinfo, err := getInfo(devname) + if err != nil { + return err + } + utils.Debugf("Waiting for unmount of %s: opencount=%d", devname, devinfo.OpenCount) + if devinfo.OpenCount == 0 { + break + } + time.Sleep(1 * time.Millisecond) + } + if i == 1000 { + return fmt.Errorf("Timeout while waiting for device %s to close", devname) + } + return nil +} + func (devices *DeviceSetDM) DeactivateDevice(hash string) error { devices.Lock() defer devices.Unlock() @@ -578,6 +605,9 @@ func (devices *DeviceSetDM) Shutdown() error { } for _, d := range devices.Devices { + if err := devices.waitClose(d.Hash); err != nil { + utils.Errorf("Warning: error waiting for device %s to unmount: %s\n", d.Hash, err) + } if err := devices.deactivateDevice(d.Hash); err != nil { utils.Debugf("Shutdown deactivate %s , error: %s\n", d.Hash, err) } @@ -632,6 +662,12 @@ func (devices *DeviceSetDM) UnmountDevice(hash, path string, deactivate bool) er utils.Debugf("\n--->Err: %s\n", err) return err } + utils.Debugf("[devmapper] Unmount done") + // Wait for the unmount to be effective, + // by watching the value of Info.OpenCount for the device + if err := devices.waitClose(hash); err != nil { + return err + } if count := devices.activeMounts[path]; count > 1 { devices.activeMounts[path] = count - 1 From 45b9d24be06d29619038225d8913f067c3fed232 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 16 Oct 2013 23:23:35 +0000 Subject: [PATCH 153/301] devicemapper: remove unused code Upstream-commit: 153248b60f551d4cb92bce4f35b08084f554c62c Component: engine --- components/engine/deviceset.go | 14 ----- .../engine/devmapper/deviceset_devmapper.go | 13 ---- components/engine/image.go | 3 +- components/engine/runtime.go | 4 +- components/engine/utils_test.go | 59 ------------------- 5 files changed, 4 insertions(+), 89 deletions(-) delete mode 100644 components/engine/deviceset.go diff --git a/components/engine/deviceset.go b/components/engine/deviceset.go deleted file mode 100644 index 21d5fd4d2f..0000000000 --- a/components/engine/deviceset.go +++ /dev/null @@ -1,14 +0,0 @@ -package docker - -type DeviceSet interface { - AddDevice(hash, baseHash string) error - SetInitialized(hash string) error - DeactivateDevice(hash string) error - RemoveDevice(hash string) error - MountDevice(hash, path string) error - UnmountDevice(hash, path string, deactivate bool) error - HasDevice(hash string) bool - HasInitializedDevice(hash string) bool - HasActivatedDevice(hash string) bool - Shutdown() error -} diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index b778228a59..ee5c121ff9 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -573,19 +573,6 @@ func (devices *DeviceSetDM) waitClose(hash string) error { return nil } -func (devices *DeviceSetDM) DeactivateDevice(hash string) error { - devices.Lock() - defer devices.Unlock() - - if err := devices.ensureInit(); err != nil { - utils.Debugf("\n--->Err: %s\n", err) - return err - } - - utils.Debugf("DeactivateDevice %s", hash) - return devices.deactivateDevice(hash) -} - func (devices *DeviceSetDM) Shutdown() error { devices.Lock() utils.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root) diff --git a/components/engine/image.go b/components/engine/image.go index 6a1191ef1b..eead33f068 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "github.com/dotcloud/docker/utils" + "github.com/dotcloud/docker/devmapper" "io" "io/ioutil" "os" @@ -334,7 +335,7 @@ func (image *Image) applyLayer(layer, target string) error { return nil } -func (image *Image) ensureImageDevice(devices DeviceSet) error { +func (image *Image) ensureImageDevice(devices *devmapper.DeviceSetDM) error { if devices.HasInitializedDevice(image.ID) { return nil } diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 94c8f21d20..df83e90fbe 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -43,7 +43,7 @@ type Runtime struct { volumes *Graph srv *Server Dns []string - deviceSet DeviceSet + deviceSet *devmapper.DeviceSetDM } var sysInitPath string @@ -86,7 +86,7 @@ func (runtime *Runtime) getContainerElement(id string) *list.Element { return nil } -func (runtime *Runtime) GetDeviceSet() (DeviceSet, error) { +func (runtime *Runtime) GetDeviceSet() (*devmapper.DeviceSetDM, error) { if runtime.deviceSet == nil { return nil, fmt.Errorf("No device set available") } diff --git a/components/engine/utils_test.go b/components/engine/utils_test.go index a7afa289dc..8d9aafbf6b 100644 --- a/components/engine/utils_test.go +++ b/components/engine/utils_test.go @@ -324,62 +324,3 @@ func TestParseLxcConfOpt(t *testing.T) { } } } - -type DeviceSetWrapper struct { - wrapped DeviceSet - prefix string -} - -func (wrapper *DeviceSetWrapper) wrap(hash string) string { - if hash != "" { - hash = wrapper.prefix + "-" + hash - } - return hash -} - -func (wrapper *DeviceSetWrapper) AddDevice(hash, baseHash string) error { - return wrapper.wrapped.AddDevice(wrapper.wrap(hash), wrapper.wrap(baseHash)) -} - -func (wrapper *DeviceSetWrapper) SetInitialized(hash string) error { - return wrapper.wrapped.SetInitialized(wrapper.wrap(hash)) -} - -func (wrapper *DeviceSetWrapper) DeactivateDevice(hash string) error { - return wrapper.wrapped.DeactivateDevice(wrapper.wrap(hash)) -} - -func (wrapper *DeviceSetWrapper) Shutdown() error { - return nil -} - -func (wrapper *DeviceSetWrapper) RemoveDevice(hash string) error { - return wrapper.wrapped.RemoveDevice(wrapper.wrap(hash)) -} - -func (wrapper *DeviceSetWrapper) MountDevice(hash, path string) error { - return wrapper.wrapped.MountDevice(wrapper.wrap(hash), path) -} - -func (wrapper *DeviceSetWrapper) UnmountDevice(hash, path string, deactivate bool) error { - return wrapper.wrapped.UnmountDevice(wrapper.wrap(hash), path, deactivate) -} - -func (wrapper *DeviceSetWrapper) HasDevice(hash string) bool { - return wrapper.wrapped.HasDevice(wrapper.wrap(hash)) -} - -func (wrapper *DeviceSetWrapper) HasInitializedDevice(hash string) bool { - return wrapper.wrapped.HasInitializedDevice(wrapper.wrap(hash)) -} - -func (wrapper *DeviceSetWrapper) HasActivatedDevice(hash string) bool { - return wrapper.wrapped.HasActivatedDevice(wrapper.wrap(hash)) -} - -func NewDeviceSetWrapper(wrapped DeviceSet, prefix string) DeviceSet { - return &DeviceSetWrapper{ - wrapped: wrapped, - prefix: prefix, - } -} From 43f0475727cc799e7c1f3e0f17443170cc6a1e2c Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 16 Oct 2013 23:26:37 +0000 Subject: [PATCH 154/301] devmapper: wait for devices to be effectively removed before returning a successful remove Upstream-commit: ea04f3de72ab97c9f9e49e46e049bf0cde58ac55 Component: engine --- .../engine/devmapper/deviceset_devmapper.go | 62 ++++++++++++++++--- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index ee5c121ff9..c3ed63ae7b 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -527,26 +527,58 @@ func (devices *DeviceSetDM) RemoveDevice(hash string) error { } func (devices *DeviceSetDM) deactivateDevice(hash string) error { - info := devices.Devices[hash] - if info == nil { - return fmt.Errorf("hash %s doesn't exists", hash) + utils.Debugf("[devmapper] deactivateDevice(%s)", hash) + defer utils.Debugf("[devmapper] deactivateDevice END") + var devname string + // FIXME: shouldn't we just register the pool into devices? + devname, err := devices.byHash(hash) + if err != nil { + return err } - - devinfo, err := getInfo(info.Name()) + devinfo, err := getInfo(devname) if err != nil { utils.Debugf("\n--->Err: %s\n", err) return err } if devinfo.Exists != 0 { - if err := removeDevice(info.Name()); err != nil { + if err := removeDevice(devname); err != nil { utils.Debugf("\n--->Err: %s\n", err) return err } + if err := devices.waitRemove(hash); err != nil { + return err + } } return nil } +// waitRemove blocks until either: +// a) the device registered at - is removed, +// or b) the 1 second timeout expires. +func (devices *DeviceSetDM) waitRemove(hash string) error { + devname, err := devices.byHash(hash) + if err != nil { + return err + } + i := 0 + for ; i<1000; i+=1 { + devinfo, err := getInfo(devname) + if err != nil { + return err + } + utils.Debugf("Waiting for removal of %s: exists=%d", devname, devinfo.Exists) + if devinfo.Exists == 0 { + break + } + time.Sleep(1 * time.Millisecond) + } + if i == 1000 { + return fmt.Errorf("Timeout while waiting for device %s to be removed", devname) + } + return nil +} + // waitClose blocks until either: // a) the device registered at - is closed, // or b) the 1 second timeout expires. @@ -573,6 +605,22 @@ func (devices *DeviceSetDM) waitClose(hash string) error { return nil } +// byHash is a hack to allow looking up the deviceset's pool by the hash "pool". +// FIXME: it seems probably cleaner to register the pool in devices.Devices, +// but I am afraid of arcane implications deep in the devicemapper code, +// so this will do. +func (devices *DeviceSetDM) byHash(hash string) (devname string, err error) { + if hash == "pool" { + return devices.getPoolDevName(), nil + } + info := devices.Devices[hash] + if info == nil { + return "", fmt.Errorf("hash %s doesn't exists", hash) + } + return info.Name(), nil +} + + func (devices *DeviceSetDM) Shutdown() error { devices.Lock() utils.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root) @@ -602,7 +650,7 @@ func (devices *DeviceSetDM) Shutdown() error { pool := devices.getPoolDevName() if devinfo, err := getInfo(pool); err == nil && devinfo.Exists != 0 { - if err := removeDevice(pool); err != nil { + if err := devices.deactivateDevice("pool"); err != nil { utils.Debugf("Shutdown deactivate %s , error: %s\n", pool, err) } } From 38810afde9b0ee46fe8266306ce3dc1043124d97 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 16 Oct 2013 23:27:00 +0000 Subject: [PATCH 155/301] hack: cleanup devicemapper at the last test Upstream-commit: f3e6d34df23adf84f520b058e4363f8b789d2ebf Component: engine --- components/engine/z_final_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/components/engine/z_final_test.go b/components/engine/z_final_test.go index 837b5d13e6..5bdfa4fd7c 100644 --- a/components/engine/z_final_test.go +++ b/components/engine/z_final_test.go @@ -13,5 +13,6 @@ func displayFdGoroutines(t *testing.T) { func TestFinal(t *testing.T) { nuke(globalRuntime) t.Logf("Start Fds: %d, Start Goroutines: %d", startFds, startGoroutines) + cleanupDevMapper() displayFdGoroutines(t) } From 1dde114e36c27b1e5c21d7b453bd44a02143e802 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 16 Oct 2013 23:27:33 +0000 Subject: [PATCH 156/301] devmapper: debug messages Upstream-commit: c688e9b5a61c1979d497cfe9cba0a6099aad63e8 Component: engine --- components/engine/devmapper/deviceset_devmapper.go | 3 +++ components/engine/devmapper/devmapper.go | 2 ++ 2 files changed, 5 insertions(+) diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index c3ed63ae7b..53717f4bd5 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -690,9 +690,12 @@ func (devices *DeviceSetDM) MountDevice(hash, path string) error { } func (devices *DeviceSetDM) UnmountDevice(hash, path string, deactivate bool) error { + utils.Debugf("[devmapper] UnmountDevice(hash=%s path=%s)", hash, path) + defer utils.Debugf("[devmapper] UnmountDevice END") devices.Lock() defer devices.Unlock() + utils.Debugf("[devmapper] Unmount(%s)", path) if err := syscall.Unmount(path, 0); err != nil { utils.Debugf("\n--->Err: %s\n", err) return err diff --git a/components/engine/devmapper/devmapper.go b/components/engine/devmapper/devmapper.go index d0fb34465e..74e172dac8 100644 --- a/components/engine/devmapper/devmapper.go +++ b/components/engine/devmapper/devmapper.go @@ -611,6 +611,8 @@ func deleteDevice(poolName string, deviceId int) error { } func removeDevice(name string) error { + utils.Debugf("[devmapper] removeDevice START") + defer utils.Debugf("[devmapper] removeDevice END") task, err := createTask(DeviceRemove, name) if task == nil { return err From 0563e530aad47b006bbd1f2f54c60ff6b59ec29e Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 17 Oct 2013 01:42:05 +0000 Subject: [PATCH 157/301] devmapper: add useful comments Upstream-commit: ad968ef3ef54f3161e8e1012f0ef20b8757ac0aa Component: engine --- components/engine/devmapper/deviceset_devmapper.go | 4 ++++ components/engine/devmapper/devmapper.go | 2 ++ 2 files changed, 6 insertions(+) diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index 53717f4bd5..72d2664549 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -88,6 +88,10 @@ func (devices *DeviceSetDM) hasImage(name string) bool { return err == nil } +// ensureImage creates a sparse file of bytes at the path +// /devicemapper/. +// If the file already exists, it does nothing. +// Either way it returns the full path. func (devices *DeviceSetDM) ensureImage(name string, size int64) (string, error) { dirname := devices.loopbackDir() filename := path.Join(dirname, name) diff --git a/components/engine/devmapper/devmapper.go b/components/engine/devmapper/devmapper.go index 74e172dac8..021661159c 100644 --- a/components/engine/devmapper/devmapper.go +++ b/components/engine/devmapper/devmapper.go @@ -18,6 +18,7 @@ package devmapper #define LOOP_CTL_GET_FREE 0x4C82 #endif +// FIXME: this could easily be rewritten in go char* attach_loop_device(const char *filename, int *loop_fd_out) { struct loop_info64 loopinfo = {0}; @@ -441,6 +442,7 @@ func free(p *C.char) { C.free(unsafe.Pointer(p)) } +// This is the programmatic example of "dmsetup create" func createPool(poolName string, dataFile *os.File, metadataFile *os.File) error { task, err := createTask(DeviceCreate, poolName) if task == nil { From d0feec342727057360b6f679920eac59a0bbf16b Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 17 Oct 2013 01:46:28 +0000 Subject: [PATCH 158/301] devmapper: prefix device names with the dev_id+inode of the data image Upstream-commit: f1d07e2dbe41a3a4f9b8ee1de544fc9c2de88c60 Component: engine --- .../engine/devmapper/deviceset_devmapper.go | 117 +++++++----------- 1 file changed, 46 insertions(+), 71 deletions(-) diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index 72d2664549..80c1fc84ee 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -12,7 +12,6 @@ import ( "path" "path/filepath" "strconv" - "strings" "sync" "syscall" ) @@ -345,91 +344,72 @@ func (devices *DeviceSetDM) log(level int, file string, line int, dmError int, m func (devices *DeviceSetDM) initDevmapper() error { logInit(devices) -begin: - info, err := getInfo(devices.getPoolName()) - if info == nil { - utils.Debugf("Error device getInfo: %s", err) - return err - } + // Make sure the sparse images exist in /devicemapper/data and + // /devicemapper/metadata - loopbackExists := false - if _, err := os.Stat(devices.loopbackDir()); err != nil { - if !os.IsNotExist(err) { - return err - } - // If it does not, then we use a different pool name - parts := strings.Split(devices.devicePrefix, "-") - i, err := strconv.Atoi(parts[len(parts)-1]) - if err != nil { - i = 0 - parts = append(parts, "0") - } - i++ - parts[len(parts)-1] = strconv.Itoa(i) - devices.devicePrefix = strings.Join(parts, "-") - } else { - loopbackExists = true - } - - // If the pool exists but the loopback does not, then we start again - if info.Exists == 1 && !loopbackExists { - goto begin - } - - utils.Debugf("initDevmapper(). Pool exists: %v, loopback Exists: %v", info.Exists, loopbackExists) - - // It seems libdevmapper opens this without O_CLOEXEC, and go exec will not close files - // that are not Close-on-exec, and lxc-start will die if it inherits any unexpected files, - // so we add this badhack to make sure it closes itself - setCloseOnExec("/dev/mapper/control") - - if info.Exists != 0 && loopbackExists { - /* Pool exists, assume everything is up */ - if err := devices.loadMetaData(); err != nil { - utils.Debugf("Error device loadMetaData: %s\n", err) - return err - } - if err := devices.setupBaseImage(); err != nil { - utils.Debugf("Error device setupBaseImage: %s\n", err) - return err - } - return nil - } - - /* If we create the loopback mounts we also need to initialize the base fs */ createdLoopback := !devices.hasImage("data") || !devices.hasImage("metadata") - data, err := devices.ensureImage("data", DefaultDataLoopbackSize) if err != nil { utils.Debugf("Error device ensureImage (data): %s\n", err) return err } - metadata, err := devices.ensureImage("metadata", DefaultMetaDataLoopbackSize) if err != nil { utils.Debugf("Error device ensureImage (metadata): %s\n", err) return err } - dataFile, err := AttachLoopDevice(data) + // Set the device prefix from the device id and inode of the data image + + st, err := os.Stat(data) if err != nil { utils.Debugf("\n--->Err: %s\n", err) return err } - defer dataFile.Close() + sysSt := st.Sys().(*syscall.Stat_t) + // "reg-" stands for "regular file". + // In the future we might use "dev-" for "device file", etc. + devices.devicePrefix = fmt.Sprintf("docker-reg-%d-%d", sysSt.Dev, sysSt.Ino) - metadataFile, err := AttachLoopDevice(metadata) - if err != nil { - utils.Debugf("\n--->Err: %s\n", err) - return err - } - defer metadataFile.Close() - if err := createPool(devices.getPoolName(), dataFile, metadataFile); err != nil { - utils.Debugf("\n--->Err: %s\n", err) + // Check for the existence of the device -pool + utils.Debugf("Checking for existence of the pool '%s'", devices.getPoolName()) + info, err := getInfo(devices.getPoolName()) + if info == nil { + utils.Debugf("Error device getInfo: %s", err) return err } + // It seems libdevmapper opens this without O_CLOEXEC, and go exec will not close files + // that are not Close-on-exec, and lxc-start will die if it inherits any unexpected files, + // so we add this badhack to make sure it closes itself + setCloseOnExec("/dev/mapper/control") + + // If the pool doesn't exist, create it + if info.Exists == 0 { + utils.Debugf("Pool doesn't exist. Creating it.") + dataFile, err := AttachLoopDevice(data) + if err != nil { + utils.Debugf("\n--->Err: %s\n", err) + return err + } + defer dataFile.Close() + + metadataFile, err := AttachLoopDevice(metadata) + if err != nil { + utils.Debugf("\n--->Err: %s\n", err) + return err + } + defer metadataFile.Close() + + if err := createPool(devices.getPoolName(), dataFile, metadataFile); err != nil { + utils.Debugf("\n--->Err: %s\n", err) + return err + } + } + + // If we didn't just create the data or metadata image, we need to + // load the metadata from the existing file. if !createdLoopback { if err = devices.loadMetaData(); err != nil { utils.Debugf("\n--->Err: %s\n", err) @@ -437,8 +417,9 @@ begin: } } + // Setup the base image if err := devices.setupBaseImage(); err != nil { - utils.Debugf("\n--->Err: %s\n", err) + utils.Debugf("Error device setupBaseImage: %s\n", err) return err } @@ -800,15 +781,9 @@ func (devices *DeviceSetDM) ensureInit() error { func NewDeviceSetDM(root string) *DeviceSetDM { SetDevDir("/dev") - base := filepath.Base(root) - if !strings.HasPrefix(base, "docker") { - base = "docker-" + base - } - return &DeviceSetDM{ initialized: false, root: root, - devicePrefix: base, MetaData: MetaData{Devices: make(map[string]*DevInfo)}, activeMounts: make(map[string]int), } From 379a09a137ac043b5ce7b937625760b5f7ca188d Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 17 Oct 2013 01:47:03 +0000 Subject: [PATCH 159/301] devmapper: debug messages Upstream-commit: 7d3c7e2b297badc152213cae1e6a11f64a7691b4 Component: engine --- components/engine/devmapper/deviceset_devmapper.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index 80c1fc84ee..da59bba6c8 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -221,6 +221,8 @@ func (devices *DeviceSetDM) createFilesystem(info *DevInfo) error { } func (devices *DeviceSetDM) loadMetaData() error { + utils.Debugf("loadMetadata()") + defer utils.Debugf("loadMetadata END") _, _, _, params, err := getStatus(devices.getPoolName()) if err != nil { utils.Debugf("\n--->Err: %s\n", err) @@ -542,6 +544,8 @@ func (devices *DeviceSetDM) deactivateDevice(hash string) error { // a) the device registered at - is removed, // or b) the 1 second timeout expires. func (devices *DeviceSetDM) waitRemove(hash string) error { + utils.Debugf("[deviceset %s] waitRemove(%s)", devices.devicePrefix, hash) + defer utils.Debugf("[deviceset %s] waitRemove END", devices.devicePrefix, hash) devname, err := devices.byHash(hash) if err != nil { return err @@ -607,6 +611,8 @@ func (devices *DeviceSetDM) byHash(hash string) (devname string, err error) { func (devices *DeviceSetDM) Shutdown() error { + utils.Debugf("[deviceset %s] shutdown()", devices.devicePrefix) + defer utils.Debugf("[deviceset %s] shutdown END", devices.devicePrefix) devices.Lock() utils.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root) defer devices.Unlock() From cff980c7fc8ddeea67fdb3fd9243b8a86a2bcaa1 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 17 Oct 2013 01:49:27 +0000 Subject: [PATCH 160/301] devmapper: small fixes in error reporting Upstream-commit: e5d7472a0d0268957eebbc385005d6b69c442270 Component: engine --- components/engine/devmapper/deviceset_devmapper.go | 3 +-- components/engine/devmapper/devmapper.go | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index da59bba6c8..f2c2e4853a 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -365,8 +365,7 @@ func (devices *DeviceSetDM) initDevmapper() error { st, err := os.Stat(data) if err != nil { - utils.Debugf("\n--->Err: %s\n", err) - return err + return fmt.Errorf("Error looking up data image %s: %s", data, err) } sysSt := st.Sys().(*syscall.Stat_t) // "reg-" stands for "regular file". diff --git a/components/engine/devmapper/devmapper.go b/components/engine/devmapper/devmapper.go index 021661159c..8600c09e8f 100644 --- a/components/engine/devmapper/devmapper.go +++ b/components/engine/devmapper/devmapper.go @@ -247,9 +247,6 @@ func (t *Task) SetName(name string) error { defer free(c_name) if res := C.dm_task_set_name(t.unmanaged, c_name); res != 1 { - if os.Getenv("DEBUG") != "" { - C.perror(C.CString(fmt.Sprintf("[debug] Error dm_task_set_name(%s, %#v)", name, t.unmanaged))) - } return ErrTaskSetName } return nil From d2fc2c3021299a1053e0620d7484dd93452acd70 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 17 Oct 2013 01:49:51 +0000 Subject: [PATCH 161/301] devmapper: error reporting workaround in waitRemove() Upstream-commit: ad2fbd9e873915d9d09685887eb0afe16b9559a4 Component: engine --- components/engine/devmapper/deviceset_devmapper.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index f2c2e4853a..309a26db79 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -553,7 +553,9 @@ func (devices *DeviceSetDM) waitRemove(hash string) error { for ; i<1000; i+=1 { devinfo, err := getInfo(devname) if err != nil { - return err + // If there is an error we assume the device doesn't exist. + // The error might actually be something else, but we can't differentiate. + return nil } utils.Debugf("Waiting for removal of %s: exists=%d", devname, devinfo.Exists) if devinfo.Exists == 0 { From 9f21580debe97dfab4572ee6967f0a4b89805e9e Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 17 Oct 2013 09:53:09 +0200 Subject: [PATCH 162/301] devicemapper: Use device/inode of the root dir, not the image This way the devicemapper prefix stays stable even if we're not using loopback mounted devices. Upstream-commit: e6a73e65a23163273fa63d54b8f12530f7eef104 Component: engine --- components/engine/devmapper/deviceset_devmapper.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index 309a26db79..aa7090e284 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -361,11 +361,11 @@ func (devices *DeviceSetDM) initDevmapper() error { return err } - // Set the device prefix from the device id and inode of the data image + // Set the device prefix from the device id and inode of the docker root dir - st, err := os.Stat(data) + st, err := os.Stat(devices.root) if err != nil { - return fmt.Errorf("Error looking up data image %s: %s", data, err) + return fmt.Errorf("Error looking up dir %s: %s", devices.root, err) } sysSt := st.Sys().(*syscall.Stat_t) // "reg-" stands for "regular file". From 79b56857033571af42e049c2178831766150c3dd Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 17 Oct 2013 14:46:58 +0200 Subject: [PATCH 163/301] TarFilter: Fix leak of tmpfiles We were leaking the temporary directory that we create in TarFilter, because the "tmpDir, err := ioutil.TempDir()" call overrides the tmpDir in the outer scope with a new locally scoped variable. This means tmpDir is always "" when the cleanup function is called. Also, we did not call the atExit() function if CmdStream had an error early on. On errors in CmdStream(), Upstream-commit: 0aee096fd73676e2548e3bf132770f1692ac47dd Component: engine --- components/engine/archive.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/components/engine/archive.go b/components/engine/archive.go index 75b6e7e1f1..6ea436c9bb 100644 --- a/components/engine/archive.go +++ b/components/engine/archive.go @@ -120,7 +120,8 @@ func TarFilter(path string, compression Compression, filter []string, recursive tmpDir := "" if createFiles != nil { - tmpDir, err := ioutil.TempDir("", "docker-tar") + var err error // Can't use := here or we override the outer tmpDir + tmpDir, err = ioutil.TempDir("", "docker-tar") if err != nil { return nil, err } @@ -284,6 +285,9 @@ func CmdStream(cmd *exec.Cmd, input *string, atEnd func()) (io.Reader, error) { if input != nil { stdin, err := cmd.StdinPipe() if err != nil { + if atEnd != nil { + atEnd() + } return nil, err } // Write stdin if any @@ -294,10 +298,16 @@ func CmdStream(cmd *exec.Cmd, input *string, atEnd func()) (io.Reader, error) { } stdout, err := cmd.StdoutPipe() if err != nil { + if atEnd != nil { + atEnd() + } return nil, err } stderr, err := cmd.StderrPipe() if err != nil { + if atEnd != nil { + atEnd() + } return nil, err } pipeR, pipeW := io.Pipe() From 3972ff12325b81de3d49855fe66fdefb1be68739 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 17 Oct 2013 11:16:50 +0200 Subject: [PATCH 164/301] Show devicemapper status in "docker info" This shows the current global diskspace use in "docker info" Upstream-commit: d733cdcebbcb6bc8573e1869b11f0d9116a92892 Component: engine --- components/engine/api_params.go | 29 +++++++----- components/engine/commands.go | 6 +++ .../engine/devmapper/deviceset_devmapper.go | 46 +++++++++++++++++++ components/engine/server.go | 31 ++++++++----- 4 files changed, 88 insertions(+), 24 deletions(-) diff --git a/components/engine/api_params.go b/components/engine/api_params.go index 5f1a338057..b685d40566 100644 --- a/components/engine/api_params.go +++ b/components/engine/api_params.go @@ -19,18 +19,23 @@ type APIImages struct { } type APIInfo struct { - Debug bool - Containers int - Images int - NFd int `json:",omitempty"` - NGoroutines int `json:",omitempty"` - MemoryLimit bool `json:",omitempty"` - SwapLimit bool `json:",omitempty"` - IPv4Forwarding bool `json:",omitempty"` - LXCVersion string `json:",omitempty"` - NEventsListener int `json:",omitempty"` - KernelVersion string `json:",omitempty"` - IndexServerAddress string `json:",omitempty"` + Debug bool + Containers int + Images int + NFd int `json:",omitempty"` + NGoroutines int `json:",omitempty"` + MemoryLimit bool `json:",omitempty"` + SwapLimit bool `json:",omitempty"` + IPv4Forwarding bool `json:",omitempty"` + LXCVersion string `json:",omitempty"` + NEventsListener int `json:",omitempty"` + KernelVersion string `json:",omitempty"` + IndexServerAddress string `json:",omitempty"` + DevmapperPool string `json:",omitempty"` + DevmapperDataUsed uint64 `json:",omitempty"` + DevmapperDataTotal uint64 `json:",omitempty"` + DevmapperMetadataUsed uint64 `json:",omitempty"` + DevmapperMetadataTotal uint64 `json:",omitempty"` } type APITop struct { diff --git a/components/engine/commands.go b/components/engine/commands.go index 7d33b81d0a..b72c2c1516 100644 --- a/components/engine/commands.go +++ b/components/engine/commands.go @@ -465,6 +465,11 @@ func (cli *DockerCli) CmdInfo(args ...string) error { fmt.Fprintf(cli.out, "Containers: %d\n", out.Containers) fmt.Fprintf(cli.out, "Images: %d\n", out.Images) + if out.DevmapperDataTotal != 0 { + fmt.Fprintf(cli.out, "Devmapper disk use: Data: %.1f/%.1f Metadata: %.1f/%.1f\n", + float64(out.DevmapperDataUsed)/(1024*1024), float64(out.DevmapperDataTotal)/(1024*1024), + float64(out.DevmapperMetadataUsed)/(1024*1024), float64(out.DevmapperMetadataTotal)/(1024*1024)) + } if out.Debug || os.Getenv("DEBUG") != "" { fmt.Fprintf(cli.out, "Debug mode (server): %v\n", out.Debug) fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "") @@ -473,6 +478,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error { fmt.Fprintf(cli.out, "LXC Version: %s\n", out.LXCVersion) fmt.Fprintf(cli.out, "EventsListeners: %d\n", out.NEventsListener) fmt.Fprintf(cli.out, "Kernel Version: %s\n", out.KernelVersion) + fmt.Fprintf(cli.out, "Devmapper pool: %s\n", out.DevmapperPool) } if len(out.IndexServerAddress) != 0 { diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index 309a26db79..4037f93136 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -47,6 +47,19 @@ type DeviceSetDM struct { activeMounts map[string]int } +type DiskUsage struct { + Used uint64 + Total uint64 +} + +type Status struct { + PoolName string + DataLoopback string + MetadataLoopback string + Data DiskUsage + Metadata DiskUsage +} + func getDevName(name string) string { return "/dev/mapper/" + name } @@ -774,6 +787,39 @@ func (devices *DeviceSetDM) SetInitialized(hash string) error { return nil } +func (devices *DeviceSetDM) Status() *Status { + devices.Lock() + defer devices.Unlock() + + status := &Status {} + + if err := devices.ensureInit(); err != nil { + return status + } + + status.PoolName = devices.getPoolName() + status.DataLoopback = path.Join( devices.loopbackDir(), "data") + status.MetadataLoopback = path.Join( devices.loopbackDir(), "metadata") + + _, totalSizeInSectors, _, params, err := getStatus(devices.getPoolName()) + if err == nil { + var transactionId, dataUsed, dataTotal, metadataUsed, metadataTotal uint64 + if _, err := fmt.Sscanf(params, "%d %d/%d %d/%d", &transactionId, &metadataUsed, &metadataTotal, &dataUsed, &dataTotal); err == nil { + // Convert from blocks to bytes + blockSizeInSectors := totalSizeInSectors / dataTotal; + + status.Data.Used = dataUsed * blockSizeInSectors * 512 + status.Data.Total = dataTotal * blockSizeInSectors * 512 + + // metadata blocks are always 4k + status.Metadata.Used = metadataUsed * 4096 + status.Metadata.Total = metadataTotal * 4096 + } + } + + return status +} + func (devices *DeviceSetDM) ensureInit() error { if !devices.initialized { devices.initialized = true diff --git a/components/engine/server.go b/components/engine/server.go index 7e6c556a3f..63c67b25ec 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -268,19 +268,26 @@ func (srv *Server) DockerInfo() *APIInfo { kernelVersion = kv.String() } + devSetInfo := srv.runtime.deviceSet.Status() + return &APIInfo{ - Containers: len(srv.runtime.List()), - Images: imgcount, - MemoryLimit: srv.runtime.capabilities.MemoryLimit, - SwapLimit: srv.runtime.capabilities.SwapLimit, - IPv4Forwarding: !srv.runtime.capabilities.IPv4ForwardingDisabled, - Debug: os.Getenv("DEBUG") != "", - NFd: utils.GetTotalUsedFds(), - NGoroutines: runtime.NumGoroutine(), - LXCVersion: lxcVersion, - NEventsListener: len(srv.events), - KernelVersion: kernelVersion, - IndexServerAddress: auth.IndexServerAddress(), + Containers: len(srv.runtime.List()), + Images: imgcount, + MemoryLimit: srv.runtime.capabilities.MemoryLimit, + SwapLimit: srv.runtime.capabilities.SwapLimit, + IPv4Forwarding: !srv.runtime.capabilities.IPv4ForwardingDisabled, + Debug: os.Getenv("DEBUG") != "", + NFd: utils.GetTotalUsedFds(), + NGoroutines: runtime.NumGoroutine(), + LXCVersion: lxcVersion, + NEventsListener: len(srv.events), + KernelVersion: kernelVersion, + IndexServerAddress: auth.IndexServerAddress(), + DevmapperPool: devSetInfo.PoolName, + DevmapperDataUsed: devSetInfo.Data.Used, + DevmapperDataTotal: devSetInfo.Data.Total, + DevmapperMetadataUsed: devSetInfo.Metadata.Used, + DevmapperMetadataTotal: devSetInfo.Metadata.Total, } } From 17a97293a6605e578835f0686b59c0fc1dc67849 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 17 Oct 2013 15:33:00 +0200 Subject: [PATCH 165/301] devmapper: Use a smaller blocksize for the thin-pool As per the thin provisioning docs for creating the pool: $data_block_size gives the smallest unit of disk space that can be allocated at a time expressed in units of 512-byte sectors. $data_block_size must be between 128 (64KB) and 2097152 (1GB) and a multiple of 128 (64KB). $data_block_size cannot be changed after the thin-pool is created. People primarily interested in thin provisioning may want to use a value such as 1024 (512KB). People doing lots of snapshotting may want a smaller value such as 128 (64KB). The switch from 512 (which we used before) to 128 (recommended above for lots of snapshoting) means a simple container creation (based on the mattdm/fedora:f19 image) adds 1 MB of diskspace rather than 3.6. This seems more in tune with how docker is typically used. Upstream-commit: 8abcc8e713fdf6229d65dec23e6e104f8040d704 Component: engine --- components/engine/devmapper/devmapper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/devmapper/devmapper.go b/components/engine/devmapper/devmapper.go index 8600c09e8f..23ca50b28a 100644 --- a/components/engine/devmapper/devmapper.go +++ b/components/engine/devmapper/devmapper.go @@ -451,7 +451,7 @@ func createPool(poolName string, dataFile *os.File, metadataFile *os.File) error return fmt.Errorf("Can't get data size") } - params := metadataFile.Name() + " " + dataFile.Name() + " 512 8192" + params := metadataFile.Name() + " " + dataFile.Name() + " 128 32768" if err := task.AddTarget(0, size/512, "thin-pool", params); err != nil { return fmt.Errorf("Can't add target") } From efce3d86027cf01c5c3c7ccbc040f19d80d6ae2f Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 17 Oct 2013 16:14:53 +0200 Subject: [PATCH 166/301] Devmapper: Mount images readonly when calculating changes There is no need to have this be writable, and there is a chance that e.g. atime updates will cause writes to the image which is bad for disk use wrt sharing between all containers. Upstream-commit: a14496ce891f1f09b10f0459550e8fe095b477b5 Component: engine --- components/engine/devmapper/deviceset_devmapper.go | 12 +++++++++--- components/engine/image.go | 6 +++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index 309a26db79..6f9a237b73 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -650,7 +650,7 @@ func (devices *DeviceSetDM) Shutdown() error { return nil } -func (devices *DeviceSetDM) MountDevice(hash, path string) error { +func (devices *DeviceSetDM) MountDevice(hash, path string, readOnly bool) error { devices.Lock() defer devices.Unlock() @@ -666,9 +666,15 @@ func (devices *DeviceSetDM) MountDevice(hash, path string) error { info := devices.Devices[hash] - err := syscall.Mount(info.DevName(), path, "ext4", syscall.MS_MGC_VAL, "discard") + var flags uintptr = syscall.MS_MGC_VAL + + if readOnly { + flags = flags | syscall.MS_RDONLY + } + + err := syscall.Mount(info.DevName(), path, "ext4", flags, "discard") if err != nil && err == syscall.EINVAL { - err = syscall.Mount(info.DevName(), path, "ext4", syscall.MS_MGC_VAL, "") + err = syscall.Mount(info.DevName(), path, "ext4", flags, "") } if err != nil { utils.Debugf("\n--->Err: %s\n", err) diff --git a/components/engine/image.go b/components/engine/image.go index eead33f068..c0596e7c5b 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -384,7 +384,7 @@ func (image *Image) ensureImageDevice(devices *devmapper.DeviceSetDM) error { return err } - if err := devices.MountDevice(image.ID, mountDir); err != nil { + if err := devices.MountDevice(image.ID, mountDir, false); err != nil { utils.Debugf("Error mounting device: %s", err) devices.RemoveDevice(image.ID) return err @@ -467,7 +467,7 @@ func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { } utils.Debugf("Mounting container %s at %s for container", id, root) - if err := devices.MountDevice(id, root); err != nil { + if err := devices.MountDevice(id, root, false); err != nil { return err } @@ -509,7 +509,7 @@ func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, er // We re-use rw for the temporary mount of the base image as its // not used by device-mapper otherwise - err = devices.MountDevice(image.ID, rw) + err = devices.MountDevice(image.ID, rw, true) if err != nil { return nil, err } From 5c8226fc06d98e85a8a74519c0ae757da61df21b Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 17 Oct 2013 15:04:14 -0700 Subject: [PATCH 167/301] Add error checking and error messages Upstream-commit: 31b883b07641bfab721a05f0a68629c79b74a058 Component: engine --- .../engine/devmapper/deviceset_devmapper.go | 10 ++-- components/engine/devmapper/devmapper.go | 60 +++++++++++-------- components/engine/runtime_test.go | 22 +++---- 3 files changed, 51 insertions(+), 41 deletions(-) diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index aa7090e284..8636522edc 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -1,7 +1,6 @@ package devmapper import ( - "time" "encoding/json" "fmt" "github.com/dotcloud/docker/utils" @@ -14,6 +13,7 @@ import ( "strconv" "sync" "syscall" + "time" ) var ( @@ -371,7 +371,7 @@ func (devices *DeviceSetDM) initDevmapper() error { // "reg-" stands for "regular file". // In the future we might use "dev-" for "device file", etc. devices.devicePrefix = fmt.Sprintf("docker-reg-%d-%d", sysSt.Dev, sysSt.Ino) - + utils.Debugf("Generated prefix: %s", devices.devicePrefix) // Check for the existence of the device -pool utils.Debugf("Checking for existence of the pool '%s'", devices.getPoolName()) @@ -389,6 +389,7 @@ func (devices *DeviceSetDM) initDevmapper() error { // If the pool doesn't exist, create it if info.Exists == 0 { utils.Debugf("Pool doesn't exist. Creating it.") + dataFile, err := AttachLoopDevice(data) if err != nil { utils.Debugf("\n--->Err: %s\n", err) @@ -550,7 +551,7 @@ func (devices *DeviceSetDM) waitRemove(hash string) error { return err } i := 0 - for ; i<1000; i+=1 { + for ; i < 1000; i += 1 { devinfo, err := getInfo(devname) if err != nil { // If there is an error we assume the device doesn't exist. @@ -578,7 +579,7 @@ func (devices *DeviceSetDM) waitClose(hash string) error { return err } i := 0 - for ; i<1000; i+=1 { + for ; i < 1000; i += 1 { devinfo, err := getInfo(devname) if err != nil { return err @@ -610,7 +611,6 @@ func (devices *DeviceSetDM) byHash(hash string) (devname string, err error) { return info.Name(), nil } - func (devices *DeviceSetDM) Shutdown() error { utils.Debugf("[deviceset %s] shutdown()", devices.devicePrefix) defer utils.Debugf("[deviceset %s] shutdown END", devices.devicePrefix) diff --git a/components/engine/devmapper/devmapper.go b/components/engine/devmapper/devmapper.go index 8600c09e8f..bfd733defb 100644 --- a/components/engine/devmapper/devmapper.go +++ b/components/engine/devmapper/devmapper.go @@ -27,6 +27,7 @@ char* attach_loop_device(const char *filename, int *loop_fd_out) int i, loop_fd, fd, start_index; char* loopname; + *loop_fd_out = -1; start_index = 0; @@ -48,71 +49,80 @@ char* attach_loop_device(const char *filename, int *loop_fd_out) loop_fd = -1; for (i = start_index ; loop_fd < 0 ; i++ ) { if (sprintf(buf, "/dev/loop%d", i) < 0) { - close(fd); - perror("sprintf"); - return NULL; + close(fd); + return NULL; } - if (stat(buf, &st) || !S_ISBLK(st.st_mode)) { + if (stat(buf, &st)) { + if (!S_ISBLK(st.st_mode)) { + fprintf(stderr, "[error] Loopback device %s is not a block device.\n", buf); + } else if (errno == ENOENT) { + fprintf(stderr, "[error] There are no more loopback device available.\n"); + } else { + fprintf(stderr, "[error] Unkown error trying to stat the loopback device %s (errno: %d).\n", buf, errno); + } close(fd); return NULL; } loop_fd = open(buf, O_RDWR); if (loop_fd < 0 && errno == ENOENT) { + fprintf(stderr, "[error] The loopback device %s does not exists.\n", buf); close(fd); - fprintf (stderr, "no available loopback device!"); return NULL; - } else if (loop_fd < 0) - continue; + } else if (loop_fd < 0) { + fprintf(stderr, "[error] Unkown error openning the loopback device %s. (errno: %d)\n", buf, errno); + continue; + } - if (ioctl (loop_fd, LOOP_SET_FD, (void *)(size_t)fd) < 0) { + if (ioctl(loop_fd, LOOP_SET_FD, (void *)(size_t)fd) < 0) { int errsv = errno; close(loop_fd); loop_fd = -1; if (errsv != EBUSY) { - close (fd); - fprintf (stderr, "cannot set up loopback device %s: %s", buf, strerror(errsv)); + close(fd); + fprintf(stderr, "cannot set up loopback device %s: %s", buf, strerror(errsv)); return NULL; } continue; } - close (fd); + close(fd); strncpy((char*)loopinfo.lo_file_name, buf, LO_NAME_SIZE); loopinfo.lo_offset = 0; loopinfo.lo_flags = LO_FLAGS_AUTOCLEAR; if (ioctl(loop_fd, LOOP_SET_STATUS64, &loopinfo) < 0) { - perror("ioctl1"); + perror("ioctl LOOP_SET_STATUS64"); if (ioctl(loop_fd, LOOP_CLR_FD, 0) < 0) { - perror("ioctl2"); + perror("ioctl LOOP_CLR_FD"); } close(loop_fd); fprintf (stderr, "cannot set up loopback device info"); - return NULL; + return (NULL); } loopname = strdup(buf); if (loopname == NULL) { close(loop_fd); - return NULL; + return (NULL); } *loop_fd_out = loop_fd; - return loopname; + return (loopname); } - return NULL; + + return (NULL); } -static int64_t -get_block_size(int fd) +static int64_t get_block_size(int fd) { - uint64_t size; + uint64_t size; + if (ioctl(fd, BLKGETSIZE64, &size) == -1) return -1; - return (int64_t)size; + return ((int64_t)size); } extern void DevmapperLogCallback(int level, char *file, int line, int dm_errno_or_class, char *str); @@ -150,7 +160,7 @@ import ( "unsafe" ) -type DevmapperLogger interface { +type DevmapperLogger interface { log(level int, file string, line int, dmError int, message string) } @@ -214,7 +224,7 @@ type ( ReadOnly int TargetCount int32 } - TaskType int + TaskType int AddNodeType int ) @@ -279,7 +289,7 @@ func (t *Task) SetCookie(cookie *uint32, flags uint16) error { } func (t *Task) SetAddNode(add_node AddNodeType) error { - if res := C.dm_task_set_add_node(t.unmanaged, C.dm_add_node_t (add_node)); res != 1 { + if res := C.dm_task_set_add_node(t.unmanaged, C.dm_add_node_t(add_node)); res != 1 { return ErrTaskSetAddNode } return nil @@ -352,7 +362,7 @@ func AttachLoopDevice(filename string) (*os.File, error) { res := C.attach_loop_device(c_filename, &fd) if res == nil { if os.Getenv("DEBUG") != "" { - C.perror(C.CString(fmt.Sprintf("[debug] Error attach_loop_device(%s, $#v)", c_filename, &fd))) + C.perror(C.CString(fmt.Sprintf("[debug] Error attach_loop_device(%s, %d)", filename, int(fd)))) } return nil, ErrAttachLoopbackDevice } diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index 49ecd3b360..8347f92f20 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -1,7 +1,6 @@ package docker import ( - "path" "bytes" "fmt" "github.com/dotcloud/docker/devmapper" @@ -11,6 +10,7 @@ import ( "log" "net" "os" + "path" "path/filepath" "runtime" "strconv" @@ -22,12 +22,12 @@ import ( ) const ( - unitTestImageName = "docker-test-image" - unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 - unitTestNetworkBridge = "testdockbr0" - unitTestStoreBase = "/var/lib/docker/unit-tests" - testDaemonAddr = "127.0.0.1:4270" - testDaemonProto = "tcp" + unitTestImageName = "docker-test-image" + unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 + unitTestNetworkBridge = "testdockbr0" + unitTestStoreBase = "/var/lib/docker/unit-tests" + testDaemonAddr = "127.0.0.1:4270" + testDaemonProto = "tcp" unitTestDMDataLoopbackSize = 209715200 // 200MB unitTestDMMetaDataLoopbackSize = 104857600 // 100MB @@ -136,7 +136,7 @@ func cleanupDevMapper() error { } pools := []string{} for _, info := range infos { - if name := info.Name(); strings.HasPrefix(name, filter + "-") { + if name := info.Name(); strings.HasPrefix(name, filter+"-") { if strings.HasSuffix(name, "-pool") { pools = append(pools, name) } else { @@ -187,7 +187,6 @@ func init() { startFds, startGoroutines = utils.GetTotalUsedFds(), runtime.NumGoroutine() } - func setupBaseImage() { runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false) if err != nil { @@ -197,7 +196,9 @@ func setupBaseImage() { // Create a device, which triggers the initiation of the base FS // This avoids other tests doing this and timing out deviceset := devmapper.NewDeviceSetDM(unitTestStoreBase) - deviceset.AddDevice("init", "") + if err := deviceset.AddDevice("init", ""); err != nil { + log.Fatalf("Unable to setup the base image: %s", err) + } // Create the "Server" srv := &Server{ @@ -216,7 +217,6 @@ func setupBaseImage() { } } - func spawnGlobalDaemon() { if globalRuntime != nil { utils.Debugf("Global runtime already exists. Skipping.") From 8e5b6c7d93192eab135834d5923aaef839b33f2c Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 17 Oct 2013 23:59:59 +0000 Subject: [PATCH 168/301] Remove race condition caused by double-destroy in 2 tests Upstream-commit: 330062ef723f66ed8a640627f983e586f46df03a Component: engine --- components/engine/container_test.go | 1 - components/engine/runtime_test.go | 6 ------ 2 files changed, 7 deletions(-) diff --git a/components/engine/container_test.go b/components/engine/container_test.go index 9fdddc9044..414ce64b98 100644 --- a/components/engine/container_test.go +++ b/components/engine/container_test.go @@ -1072,7 +1072,6 @@ func TestLXCConfig(t *testing.T) { if err != nil { t.Fatal(err) } - defer runtime.Destroy(container) container.generateLXCConfig(nil) grepFile(t, container.lxcConfigPath(), "lxc.utsname = foobar") grepFile(t, container.lxcConfigPath(), diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index 8347f92f20..a0ed14f706 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -276,12 +276,6 @@ func TestRuntimeCreate(t *testing.T) { t.Fatal(err) } - defer func() { - if err := runtime.Destroy(container); err != nil { - t.Error(err) - } - }() - // Make sure we can find the newly created container with List() if len(runtime.List()) != 1 { t.Errorf("Expected 1 container, %v found", len(runtime.List())) From 27c3cb736517be7366a3fee2cc9af514f03cedcb Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 18 Oct 2013 00:01:29 +0000 Subject: [PATCH 169/301] Don't add /.docker-id to the container filesystem Upstream-commit: ed03dbfe82f4481ea3845071606ef25c7ea2ec3e Component: engine --- components/engine/changes.go | 4 ---- components/engine/image.go | 16 ---------------- 2 files changed, 20 deletions(-) diff --git a/components/engine/changes.go b/components/engine/changes.go index bf31114cdd..765dca77b6 100644 --- a/components/engine/changes.go +++ b/components/engine/changes.go @@ -312,9 +312,5 @@ func ChangesDirs(newDir, oldDir string) ([]Change, error) { return nil, err } - // Ignore changes in .docker-id - _ = newRoot.Remove("/.docker-id") - _ = oldRoot.Remove("/.docker-id") - return newRoot.Changes(oldRoot), nil } diff --git a/components/engine/image.go b/components/engine/image.go index eead33f068..71e65e4533 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -390,13 +390,6 @@ func (image *Image) ensureImageDevice(devices *devmapper.DeviceSetDM) error { return err } - if err := ioutil.WriteFile(path.Join(mountDir, ".docker-id"), []byte(image.ID), 0600); err != nil { - utils.Debugf("Error writing file: %s", err) - devices.UnmountDevice(image.ID, mountDir, true) - devices.RemoveDevice(image.ID) - return err - } - if err = image.applyLayer(layerPath(root), mountDir); err != nil { utils.Debugf("Error applying layer: %s", err) devices.UnmountDevice(image.ID, mountDir, true) @@ -456,14 +449,12 @@ func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { return err } - createdDevice := false if !devices.HasDevice(id) { utils.Debugf("Creating device %s for container based on image %s", id, image.ID) err = devices.AddDevice(id, image.ID) if err != nil { return err } - createdDevice = true } utils.Debugf("Mounting container %s at %s for container", id, root) @@ -471,13 +462,6 @@ func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { return err } - if createdDevice { - err = ioutil.WriteFile(path.Join(root, ".docker-id"), []byte(id), 0600) - if err != nil { - _ = devices.RemoveDevice(image.ID) - return err - } - } return nil } From d24b97a31467731d7175f19749241b5c013cb6b1 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 18 Oct 2013 00:07:34 +0000 Subject: [PATCH 170/301] devmapper: Rename DeviceSetDM to DeviceSet Upstream-commit: 4bd6021806985b48b872aa9dbe2d0183675eac1d Component: engine --- .../engine/devmapper/deviceset_devmapper.go | 70 +++++++++---------- components/engine/devmapper/devmapper.go | 2 +- .../docker-device-tool/device_tool.go | 2 +- components/engine/image.go | 2 +- components/engine/runtime.go | 6 +- components/engine/runtime_test.go | 2 +- 6 files changed, 42 insertions(+), 42 deletions(-) diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset_devmapper.go index 8636522edc..c498549108 100644 --- a/components/engine/devmapper/deviceset_devmapper.go +++ b/components/engine/devmapper/deviceset_devmapper.go @@ -28,14 +28,14 @@ type DevInfo struct { Size uint64 `json:"size"` TransactionId uint64 `json:"transaction_id"` Initialized bool `json:"initialized"` - devices *DeviceSetDM `json:"-"` + devices *DeviceSet `json:"-"` } type MetaData struct { Devices map[string]*DevInfo `json:devices` } -type DeviceSetDM struct { +type DeviceSet struct { MetaData sync.Mutex initialized bool @@ -63,23 +63,23 @@ func (info *DevInfo) DevName() string { return getDevName(info.Name()) } -func (devices *DeviceSetDM) loopbackDir() string { +func (devices *DeviceSet) loopbackDir() string { return path.Join(devices.root, "devicemapper") } -func (devices *DeviceSetDM) jsonFile() string { +func (devices *DeviceSet) jsonFile() string { return path.Join(devices.loopbackDir(), "json") } -func (devices *DeviceSetDM) getPoolName() string { +func (devices *DeviceSet) getPoolName() string { return devices.devicePrefix + "-pool" } -func (devices *DeviceSetDM) getPoolDevName() string { +func (devices *DeviceSet) getPoolDevName() string { return getDevName(devices.getPoolName()) } -func (devices *DeviceSetDM) hasImage(name string) bool { +func (devices *DeviceSet) hasImage(name string) bool { dirname := devices.loopbackDir() filename := path.Join(dirname, name) @@ -91,7 +91,7 @@ func (devices *DeviceSetDM) hasImage(name string) bool { // /devicemapper/. // If the file already exists, it does nothing. // Either way it returns the full path. -func (devices *DeviceSetDM) ensureImage(name string, size int64) (string, error) { +func (devices *DeviceSet) ensureImage(name string, size int64) (string, error) { dirname := devices.loopbackDir() filename := path.Join(dirname, name) @@ -116,19 +116,19 @@ func (devices *DeviceSetDM) ensureImage(name string, size int64) (string, error) return filename, nil } -func (devices *DeviceSetDM) allocateDeviceId() int { +func (devices *DeviceSet) allocateDeviceId() int { // TODO: Add smarter reuse of deleted devices id := devices.nextFreeDevice devices.nextFreeDevice = devices.nextFreeDevice + 1 return id } -func (devices *DeviceSetDM) allocateTransactionId() uint64 { +func (devices *DeviceSet) allocateTransactionId() uint64 { devices.NewTransactionId = devices.NewTransactionId + 1 return devices.NewTransactionId } -func (devices *DeviceSetDM) saveMetadata() error { +func (devices *DeviceSet) saveMetadata() error { jsonData, err := json.Marshal(devices.MetaData) if err != nil { utils.Debugf("\n--->Err: %s\n", err) @@ -171,7 +171,7 @@ func (devices *DeviceSetDM) saveMetadata() error { return nil } -func (devices *DeviceSetDM) registerDevice(id int, hash string, size uint64) (*DevInfo, error) { +func (devices *DeviceSet) registerDevice(id int, hash string, size uint64) (*DevInfo, error) { utils.Debugf("registerDevice(%v, %v)", id, hash) info := &DevInfo{ Hash: hash, @@ -192,7 +192,7 @@ func (devices *DeviceSetDM) registerDevice(id int, hash string, size uint64) (*D return info, nil } -func (devices *DeviceSetDM) activateDeviceIfNeeded(hash string) error { +func (devices *DeviceSet) activateDeviceIfNeeded(hash string) error { utils.Debugf("activateDeviceIfNeeded(%v)", hash) info := devices.Devices[hash] if info == nil { @@ -206,7 +206,7 @@ func (devices *DeviceSetDM) activateDeviceIfNeeded(hash string) error { return activateDevice(devices.getPoolDevName(), info.Name(), info.DeviceId, info.Size) } -func (devices *DeviceSetDM) createFilesystem(info *DevInfo) error { +func (devices *DeviceSet) createFilesystem(info *DevInfo) error { devname := info.DevName() err := exec.Command("mkfs.ext4", "-E", "discard,lazy_itable_init=0,lazy_journal_init=0", devname).Run() @@ -220,7 +220,7 @@ func (devices *DeviceSetDM) createFilesystem(info *DevInfo) error { return nil } -func (devices *DeviceSetDM) loadMetaData() error { +func (devices *DeviceSet) loadMetaData() error { utils.Debugf("loadMetadata()") defer utils.Debugf("loadMetadata END") _, _, _, params, err := getStatus(devices.getPoolName()) @@ -266,7 +266,7 @@ func (devices *DeviceSetDM) loadMetaData() error { return nil } -func (devices *DeviceSetDM) setupBaseImage() error { +func (devices *DeviceSet) setupBaseImage() error { oldInfo := devices.Devices[""] if oldInfo != nil && oldInfo.Initialized { return nil @@ -335,7 +335,7 @@ func setCloseOnExec(name string) { } } -func (devices *DeviceSetDM) log(level int, file string, line int, dmError int, message string) { +func (devices *DeviceSet) log(level int, file string, line int, dmError int, message string) { if level >= 7 { return // Ignore _LOG_DEBUG } @@ -343,7 +343,7 @@ func (devices *DeviceSetDM) log(level int, file string, line int, dmError int, m utils.Debugf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message) } -func (devices *DeviceSetDM) initDevmapper() error { +func (devices *DeviceSet) initDevmapper() error { logInit(devices) // Make sure the sparse images exist in /devicemapper/data and @@ -428,7 +428,7 @@ func (devices *DeviceSetDM) initDevmapper() error { return nil } -func (devices *DeviceSetDM) AddDevice(hash, baseHash string) error { +func (devices *DeviceSet) AddDevice(hash, baseHash string) error { devices.Lock() defer devices.Unlock() @@ -462,7 +462,7 @@ func (devices *DeviceSetDM) AddDevice(hash, baseHash string) error { return nil } -func (devices *DeviceSetDM) removeDevice(hash string) error { +func (devices *DeviceSet) removeDevice(hash string) error { info := devices.Devices[hash] if info == nil { return fmt.Errorf("hash %s doesn't exists", hash) @@ -501,7 +501,7 @@ func (devices *DeviceSetDM) removeDevice(hash string) error { return nil } -func (devices *DeviceSetDM) RemoveDevice(hash string) error { +func (devices *DeviceSet) RemoveDevice(hash string) error { devices.Lock() defer devices.Unlock() @@ -513,7 +513,7 @@ func (devices *DeviceSetDM) RemoveDevice(hash string) error { return devices.removeDevice(hash) } -func (devices *DeviceSetDM) deactivateDevice(hash string) error { +func (devices *DeviceSet) deactivateDevice(hash string) error { utils.Debugf("[devmapper] deactivateDevice(%s)", hash) defer utils.Debugf("[devmapper] deactivateDevice END") var devname string @@ -543,7 +543,7 @@ func (devices *DeviceSetDM) deactivateDevice(hash string) error { // waitRemove blocks until either: // a) the device registered at - is removed, // or b) the 1 second timeout expires. -func (devices *DeviceSetDM) waitRemove(hash string) error { +func (devices *DeviceSet) waitRemove(hash string) error { utils.Debugf("[deviceset %s] waitRemove(%s)", devices.devicePrefix, hash) defer utils.Debugf("[deviceset %s] waitRemove END", devices.devicePrefix, hash) devname, err := devices.byHash(hash) @@ -573,7 +573,7 @@ func (devices *DeviceSetDM) waitRemove(hash string) error { // waitClose blocks until either: // a) the device registered at - is closed, // or b) the 1 second timeout expires. -func (devices *DeviceSetDM) waitClose(hash string) error { +func (devices *DeviceSet) waitClose(hash string) error { devname, err := devices.byHash(hash) if err != nil { return err @@ -600,7 +600,7 @@ func (devices *DeviceSetDM) waitClose(hash string) error { // FIXME: it seems probably cleaner to register the pool in devices.Devices, // but I am afraid of arcane implications deep in the devicemapper code, // so this will do. -func (devices *DeviceSetDM) byHash(hash string) (devname string, err error) { +func (devices *DeviceSet) byHash(hash string) (devname string, err error) { if hash == "pool" { return devices.getPoolDevName(), nil } @@ -611,7 +611,7 @@ func (devices *DeviceSetDM) byHash(hash string) (devname string, err error) { return info.Name(), nil } -func (devices *DeviceSetDM) Shutdown() error { +func (devices *DeviceSet) Shutdown() error { utils.Debugf("[deviceset %s] shutdown()", devices.devicePrefix) defer utils.Debugf("[deviceset %s] shutdown END", devices.devicePrefix) devices.Lock() @@ -650,7 +650,7 @@ func (devices *DeviceSetDM) Shutdown() error { return nil } -func (devices *DeviceSetDM) MountDevice(hash, path string) error { +func (devices *DeviceSet) MountDevice(hash, path string) error { devices.Lock() defer devices.Unlock() @@ -681,7 +681,7 @@ func (devices *DeviceSetDM) MountDevice(hash, path string) error { return nil } -func (devices *DeviceSetDM) UnmountDevice(hash, path string, deactivate bool) error { +func (devices *DeviceSet) UnmountDevice(hash, path string, deactivate bool) error { utils.Debugf("[devmapper] UnmountDevice(hash=%s path=%s)", hash, path) defer utils.Debugf("[devmapper] UnmountDevice END") devices.Lock() @@ -712,7 +712,7 @@ func (devices *DeviceSetDM) UnmountDevice(hash, path string, deactivate bool) er return nil } -func (devices *DeviceSetDM) HasDevice(hash string) bool { +func (devices *DeviceSet) HasDevice(hash string) bool { devices.Lock() defer devices.Unlock() @@ -722,7 +722,7 @@ func (devices *DeviceSetDM) HasDevice(hash string) bool { return devices.Devices[hash] != nil } -func (devices *DeviceSetDM) HasInitializedDevice(hash string) bool { +func (devices *DeviceSet) HasInitializedDevice(hash string) bool { devices.Lock() defer devices.Unlock() @@ -734,7 +734,7 @@ func (devices *DeviceSetDM) HasInitializedDevice(hash string) bool { return info != nil && info.Initialized } -func (devices *DeviceSetDM) HasActivatedDevice(hash string) bool { +func (devices *DeviceSet) HasActivatedDevice(hash string) bool { devices.Lock() defer devices.Unlock() @@ -750,7 +750,7 @@ func (devices *DeviceSetDM) HasActivatedDevice(hash string) bool { return devinfo != nil && devinfo.Exists != 0 } -func (devices *DeviceSetDM) SetInitialized(hash string) error { +func (devices *DeviceSet) SetInitialized(hash string) error { devices.Lock() defer devices.Unlock() @@ -774,7 +774,7 @@ func (devices *DeviceSetDM) SetInitialized(hash string) error { return nil } -func (devices *DeviceSetDM) ensureInit() error { +func (devices *DeviceSet) ensureInit() error { if !devices.initialized { devices.initialized = true if err := devices.initDevmapper(); err != nil { @@ -785,10 +785,10 @@ func (devices *DeviceSetDM) ensureInit() error { return nil } -func NewDeviceSetDM(root string) *DeviceSetDM { +func NewDeviceSet(root string) *DeviceSet { SetDevDir("/dev") - return &DeviceSetDM{ + return &DeviceSet{ initialized: false, root: root, MetaData: MetaData{Devices: make(map[string]*DevInfo)}, diff --git a/components/engine/devmapper/devmapper.go b/components/engine/devmapper/devmapper.go index bfd733defb..da83d32ccc 100644 --- a/components/engine/devmapper/devmapper.go +++ b/components/engine/devmapper/devmapper.go @@ -660,7 +660,7 @@ func activateDevice(poolName string, name string, deviceId int, size uint64) err return nil } -func (devices *DeviceSetDM) createSnapDevice(poolName string, deviceId int, baseName string, baseDeviceId int) error { +func (devices *DeviceSet) createSnapDevice(poolName string, deviceId int, baseName string, baseDeviceId int) error { devinfo, _ := getInfo(baseName) doSuspend := devinfo != nil && devinfo.Exists != 0 diff --git a/components/engine/devmapper/docker-device-tool/device_tool.go b/components/engine/devmapper/docker-device-tool/device_tool.go index 28bdf56074..f66762da5e 100644 --- a/components/engine/devmapper/docker-device-tool/device_tool.go +++ b/components/engine/devmapper/docker-device-tool/device_tool.go @@ -12,7 +12,7 @@ func usage() { } func main() { - devices := devmapper.NewDeviceSetDM("/var/lib/docker") + devices := devmapper.NewDeviceSet("/var/lib/docker") if len(os.Args) < 2 { usage() diff --git a/components/engine/image.go b/components/engine/image.go index 71e65e4533..ee79123728 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -335,7 +335,7 @@ func (image *Image) applyLayer(layer, target string) error { return nil } -func (image *Image) ensureImageDevice(devices *devmapper.DeviceSetDM) error { +func (image *Image) ensureImageDevice(devices *devmapper.DeviceSet) error { if devices.HasInitializedDevice(image.ID) { return nil } diff --git a/components/engine/runtime.go b/components/engine/runtime.go index df83e90fbe..d2705ee3ef 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -43,7 +43,7 @@ type Runtime struct { volumes *Graph srv *Server Dns []string - deviceSet *devmapper.DeviceSetDM + deviceSet *devmapper.DeviceSet } var sysInitPath string @@ -86,7 +86,7 @@ func (runtime *Runtime) getContainerElement(id string) *list.Element { return nil } -func (runtime *Runtime) GetDeviceSet() (*devmapper.DeviceSetDM, error) { +func (runtime *Runtime) GetDeviceSet() (*devmapper.DeviceSet, error) { if runtime.deviceSet == nil { return nil, fmt.Errorf("No device set available") } @@ -612,7 +612,7 @@ func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) { if err != nil { return nil, err } - deviceSet := devmapper.NewDeviceSetDM(root) + deviceSet := devmapper.NewDeviceSet(root) // Initialize devicemapper deviceSet runtime := &Runtime{ root: root, diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index a0ed14f706..9a42784be1 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -195,7 +195,7 @@ func setupBaseImage() { // Create a device, which triggers the initiation of the base FS // This avoids other tests doing this and timing out - deviceset := devmapper.NewDeviceSetDM(unitTestStoreBase) + deviceset := devmapper.NewDeviceSet(unitTestStoreBase) if err := deviceset.AddDevice("init", ""); err != nil { log.Fatalf("Unable to setup the base image: %s", err) } From 1f332202760877cb6aa1428228c40e3ff1e796e4 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 18 Oct 2013 00:58:20 +0000 Subject: [PATCH 171/301] devicemapper: Add fixme Upstream-commit: d034aafac7a9db601b8affd61a28c762be6b6c31 Component: engine --- components/engine/image.go | 1 + 1 file changed, 1 insertion(+) diff --git a/components/engine/image.go b/components/engine/image.go index ee79123728..f5057b672f 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -61,6 +61,7 @@ func LoadImage(root string) (*Image, error) { } // Check that the filesystem layer exists + // FIXME: once an image is added into device mapper, the layer is no longer needed 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) From 4e4f3e50fd776f84531393417c550fb5c322eaf5 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 18 Oct 2013 02:32:13 +0000 Subject: [PATCH 172/301] hack: fail tests if there are leftover temp files before or after Upstream-commit: 5cd7de5de8fddd5868c6b85ed1e4f46228c82672 Component: engine --- components/engine/hack/make/test | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/components/engine/hack/make/test b/components/engine/hack/make/test index 44f8bd6c3e..99412491cb 100644 --- a/components/engine/hack/make/test +++ b/components/engine/hack/make/test @@ -31,4 +31,26 @@ find_test_dirs() { sort -u } + +check_test_garbage() { + nGarbage=`find /tmp/ -name 'docker-*' | wc -l` + if [ $nGarbage -gt 0 ]; then + ( + cat <&1 + fi +} + +check_test_garbage bundle_test +check_test_garbage From 66093eff84eb05b627738c6bb644a8721ee53aa3 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 18 Oct 2013 05:19:22 +0000 Subject: [PATCH 173/301] devmapper: rename deviceset_devmapper.go to deviceset.go Upstream-commit: 85a36b3b53f39e13bba9d69a486758b68301d503 Component: engine --- .../engine/devmapper/{deviceset_devmapper.go => deviceset.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename components/engine/devmapper/{deviceset_devmapper.go => deviceset.go} (100%) diff --git a/components/engine/devmapper/deviceset_devmapper.go b/components/engine/devmapper/deviceset.go similarity index 100% rename from components/engine/devmapper/deviceset_devmapper.go rename to components/engine/devmapper/deviceset.go From e20e669277a8f79b85180080c605ac4991bde322 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 18 Oct 2013 06:44:30 +0000 Subject: [PATCH 174/301] Increase readbility of unit tests by using mkRuntime everywhere Upstream-commit: 07e09d57af29bf9b59945ab2f31c4dc5ab52bb5b Component: engine --- components/engine/api_test.go | 5 +---- components/engine/buildfile_test.go | 30 ++++++----------------------- 2 files changed, 7 insertions(+), 28 deletions(-) diff --git a/components/engine/api_test.go b/components/engine/api_test.go index 35ecf574e0..6ac9421ea9 100644 --- a/components/engine/api_test.go +++ b/components/engine/api_test.go @@ -449,10 +449,7 @@ func TestGetContainersChanges(t *testing.T) { func TestGetContainersTop(t *testing.T) { t.Skip("Fixme. Skipping test for now. Reported error when testing using dind: 'api_test.go:527: Expected 2 processes, found 0.'") - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } + runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{runtime: runtime} diff --git a/components/engine/buildfile_test.go b/components/engine/buildfile_test.go index c3881a214f..1af457158a 100644 --- a/components/engine/buildfile_test.go +++ b/components/engine/buildfile_test.go @@ -229,10 +229,7 @@ func TestBuild(t *testing.T) { func buildImage(context testContextTemplate, t *testing.T, srv *Server, useCache bool) *Image { if srv == nil { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } + runtime := mkRuntime(t) defer nuke(runtime) srv = &Server{ @@ -370,10 +367,7 @@ func TestBuildEntrypoint(t *testing.T) { // testing #1405 - config.Cmd does not get cleaned up if // utilizing cache func TestBuildEntrypointRunCleanup(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } + runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{ @@ -402,10 +396,7 @@ func TestBuildEntrypointRunCleanup(t *testing.T) { } func TestBuildImageWithCache(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } + runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{ @@ -433,10 +424,7 @@ func TestBuildImageWithCache(t *testing.T) { } func TestBuildImageWithoutCache(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } + runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{ @@ -464,10 +452,7 @@ func TestBuildImageWithoutCache(t *testing.T) { } func TestForbiddenContextPath(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } + runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{ @@ -513,10 +498,7 @@ func TestForbiddenContextPath(t *testing.T) { } func TestBuildADDFileNotFound(t *testing.T) { - runtime, err := newTestRuntime() - if err != nil { - t.Fatal(err) - } + runtime := mkRuntime(t) defer nuke(runtime) srv := &Server{ From cc3628cab5dd47fcbb63bea4ff85a11044287cb8 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 18 Oct 2013 06:47:08 +0000 Subject: [PATCH 175/301] hack: encode the name of the current test in temporary directories, for easier tracking Upstream-commit: 5f58a1fbe4733c1415046d6e16afd17a41b595a8 Component: engine --- components/engine/utils_test.go | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/components/engine/utils_test.go b/components/engine/utils_test.go index 8d9aafbf6b..a149e679f6 100644 --- a/components/engine/utils_test.go +++ b/components/engine/utils_test.go @@ -2,22 +2,38 @@ package docker import ( "github.com/dotcloud/docker/utils" + "fmt" "io" "io/ioutil" "os" "path" "strings" "testing" + "runtime" ) // This file contains utility functions for docker's unit test suite. // It has to be named XXX_test.go, apparently, in other to access private functions // from other XXX_test.go functions. +var globalTestID string + // Create a temporary runtime suitable for unit testing. // Call t.Fatal() at the first error. func mkRuntime(f Fataler) *Runtime { - runtime, err := newTestRuntime() + // Use the caller function name as a prefix. + // This helps trace temp directories back to their test. + pc, _, _, _ := runtime.Caller(1) + callerLongName := runtime.FuncForPC(pc).Name() + parts := strings.Split(callerLongName, ".") + callerShortName := parts[len(parts) - 1] + if globalTestID == "" { + globalTestID = GenerateID()[:4] + } + prefix := fmt.Sprintf("docker-test%s-%s-", globalTestID, callerShortName) + utils.Debugf("prefix = '%s'", prefix) + + runtime, err := newTestRuntime(prefix) if err != nil { f.Fatal(err) } @@ -30,9 +46,13 @@ type Fataler interface { Fatal(args ...interface{}) } -func newTestRuntime() (runtime *Runtime, err error) { +func newTestRuntime(prefix string) (runtime *Runtime, err error) { + if prefix == "" { + prefix = "docker-test-" + } + utils.Debugf("prefix = %s", prefix) utils.Debugf("newTestRuntime start") - root, err := ioutil.TempDir("", "docker-test") + root, err := ioutil.TempDir("", prefix) defer func() { utils.Debugf("newTestRuntime: %s", root) }() From b73358fb51723c3e4ec1149dcfe6d1195a9a102a Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 18 Oct 2013 06:48:20 +0000 Subject: [PATCH 176/301] devmapper: the tests are fast enough that we no longer need a workaround to avoid timeouts Upstream-commit: cad913c57bbd1d9f9b3b2284ee9e2c262734047b Component: engine --- components/engine/runtime_test.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index 9a42784be1..cc1b22ba59 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -193,13 +193,6 @@ func setupBaseImage() { log.Fatalf("Unable to create a runtime for tests:", err) } - // Create a device, which triggers the initiation of the base FS - // This avoids other tests doing this and timing out - deviceset := devmapper.NewDeviceSet(unitTestStoreBase) - if err := deviceset.AddDevice("init", ""); err != nil { - log.Fatalf("Unable to setup the base image: %s", err) - } - // Create the "Server" srv := &Server{ runtime: runtime, From c58812571236de1193467c6d223f69ba06931875 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 18 Oct 2013 07:09:13 +0000 Subject: [PATCH 177/301] devmapper: cleanup error reporting Upstream-commit: 0ad35c67462ce3343b99d40ea78755d2ad92a754 Component: engine --- components/engine/devmapper/deviceset.go | 30 ++++++++---------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/components/engine/devmapper/deviceset.go b/components/engine/devmapper/deviceset.go index ec8cc7d879..528af73597 100644 --- a/components/engine/devmapper/deviceset.go +++ b/components/engine/devmapper/deviceset.go @@ -131,40 +131,33 @@ func (devices *DeviceSet) allocateTransactionId() uint64 { func (devices *DeviceSet) saveMetadata() error { jsonData, err := json.Marshal(devices.MetaData) if err != nil { - utils.Debugf("\n--->Err: %s\n", err) - return err + return fmt.Errorf("Error encoding metaadata to json: %s", err) } tmpFile, err := ioutil.TempFile(filepath.Dir(devices.jsonFile()), ".json") if err != nil { - utils.Debugf("\n--->Err: %s\n", err) - return err + return fmt.Errorf("Error creating metadata file: %s", err) } n, err := tmpFile.Write(jsonData) if err != nil { - utils.Debugf("\n--->Err: %s\n", err) - return err + return fmt.Errorf("Error writing metadata to %s: %s", tmpFile.Name(), err) } if n < len(jsonData) { return io.ErrShortWrite } if err := tmpFile.Sync(); err != nil { - utils.Debugf("\n--->Err: %s\n", err) - return err + return fmt.Errorf("Error syncing metadata file %s: %s", tmpFile.Name(), err) } if err := tmpFile.Close(); err != nil { - utils.Debugf("\n--->Err: %s\n", err) - return err + return fmt.Errorf("Error closing metadata file %s: %s", tmpFile.Name(), err) } if err := os.Rename(tmpFile.Name(), devices.jsonFile()); err != nil { - utils.Debugf("\n--->Err: %s\n", err) - return err + return fmt.Errorf("Error committing metadata file", err) } if devices.NewTransactionId != devices.TransactionId { if err = setTransactionId(devices.getPoolDevName(), devices.TransactionId, devices.NewTransactionId); err != nil { - utils.Debugf("\n--->Err: %s\n", err) - return err + return fmt.Errorf("Error setting devmapper transition ID: %s", err) } devices.TransactionId = devices.NewTransactionId } @@ -655,13 +648,11 @@ func (devices *DeviceSet) MountDevice(hash, path string, readOnly bool) error { defer devices.Unlock() if err := devices.ensureInit(); err != nil { - utils.Debugf("\n--->Err: %s\n", err) - return err + return fmt.Errorf("Error initializing devmapper: %s", err) } if err := devices.activateDeviceIfNeeded(hash); err != nil { - utils.Debugf("\n--->Err: %s\n", err) - return err + return fmt.Errorf("Error activating devmapper device for '%s': %s", hash, err) } info := devices.Devices[hash] @@ -677,8 +668,7 @@ func (devices *DeviceSet) MountDevice(hash, path string, readOnly bool) error { err = syscall.Mount(info.DevName(), path, "ext4", flags, "") } if err != nil { - utils.Debugf("\n--->Err: %s\n", err) - return err + return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), path, err) } count := devices.activeMounts[path] From e658fc1a1deb4108843a712527f8b0d2c6fc4cf3 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 18 Oct 2013 08:22:42 +0000 Subject: [PATCH 178/301] devmapper: clearer make the 'unknown base hash' error message more understandable Upstream-commit: dfb77274cef85c0561d98de2eee8b2562c329d1f Component: engine --- components/engine/devmapper/deviceset.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/engine/devmapper/deviceset.go b/components/engine/devmapper/deviceset.go index e08d4629f6..75ead65ec7 100644 --- a/components/engine/devmapper/deviceset.go +++ b/components/engine/devmapper/deviceset.go @@ -449,8 +449,7 @@ func (devices *DeviceSet) AddDevice(hash, baseHash string) error { baseInfo := devices.Devices[baseHash] if baseInfo == nil { - utils.Debugf("Base Hash not found") - return fmt.Errorf("Unknown base hash %s", baseHash) + return fmt.Errorf("Error adding device for '%s': can't find device for parent '%s'", hash, baseHash) } deviceId := devices.allocateDeviceId() From 5be5b89b7a3638571431b8b94d751ba8b926713d Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 18 Oct 2013 08:30:53 +0000 Subject: [PATCH 179/301] devmapper: use major,minor of underlying device in devmapper device name, for readability. Upstream-commit: 4756ad248a6b8c70a7f4d6cb1aacd6e60e27b1e6 Component: engine --- components/engine/devmapper/deviceset.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/components/engine/devmapper/deviceset.go b/components/engine/devmapper/deviceset.go index 75ead65ec7..f9526a634d 100644 --- a/components/engine/devmapper/deviceset.go +++ b/components/engine/devmapper/deviceset.go @@ -376,7 +376,11 @@ func (devices *DeviceSet) initDevmapper() error { sysSt := st.Sys().(*syscall.Stat_t) // "reg-" stands for "regular file". // In the future we might use "dev-" for "device file", etc. - devices.devicePrefix = fmt.Sprintf("docker-reg-%d-%d", sysSt.Dev, sysSt.Ino) + // docker-maj,min[-inode] stands for: + // - Managed by docker + // - The target of this device is at major and minor + // - If is defined, use that file inside the device as a loopback image. Otherwise use the device itself. + devices.devicePrefix = fmt.Sprintf("docker-%d,%d-%d", sysSt.Dev >>8, sysSt.Dev & 0xff, sysSt.Ino) utils.Debugf("Generated prefix: %s", devices.devicePrefix) // Check for the existence of the device -pool From 1357281d037fcd6c4f94dbc168ec285641e71e98 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 18 Oct 2013 11:38:21 +0200 Subject: [PATCH 180/301] devmapper: Fix major/minor extraction The way devices are mapped is a bit more complex than before. This implements the method from new_decode_dev in: https://github.com/mirrors/linux-2.6/blob/master/include/linux/kdev_t.h Which is what is needed on kernels > 2.6 Upstream-commit: 572b1fd9be69c20a98d885e18b327031ef4f4b51 Component: engine --- components/engine/devmapper/deviceset.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/components/engine/devmapper/deviceset.go b/components/engine/devmapper/deviceset.go index f9526a634d..26594c54dc 100644 --- a/components/engine/devmapper/deviceset.go +++ b/components/engine/devmapper/deviceset.go @@ -349,6 +349,14 @@ func (devices *DeviceSet) log(level int, file string, line int, dmError int, mes utils.Debugf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message) } +func major (device uint64) uint64 { + return (device >> 8) & 0xfff +} + +func minor (device uint64) uint64 { + return (device & 0xff) | ((device >> 12) & 0xfff00) +} + func (devices *DeviceSet) initDevmapper() error { logInit(devices) @@ -380,7 +388,7 @@ func (devices *DeviceSet) initDevmapper() error { // - Managed by docker // - The target of this device is at major and minor // - If is defined, use that file inside the device as a loopback image. Otherwise use the device itself. - devices.devicePrefix = fmt.Sprintf("docker-%d,%d-%d", sysSt.Dev >>8, sysSt.Dev & 0xff, sysSt.Ino) + devices.devicePrefix = fmt.Sprintf("docker-%d,%d-%d", major(sysSt.Dev), minor(sysSt.Dev), sysSt.Ino) utils.Debugf("Generated prefix: %s", devices.devicePrefix) // Check for the existence of the device -pool From e878830be2bf9fadd41c0523760a2292828248a7 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 18 Oct 2013 11:39:47 +0200 Subject: [PATCH 181/301] devmapper: Fix prefix name to work with udev Udev escapes "," used in device names to 0\x2c which breaks libdevmapper. Instead use : to escape minor and minor which works. Upstream-commit: 2812baf3957391bf8871bd56c00a58f4fbc0b715 Component: engine --- components/engine/devmapper/deviceset.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/devmapper/deviceset.go b/components/engine/devmapper/deviceset.go index 26594c54dc..0717722e87 100644 --- a/components/engine/devmapper/deviceset.go +++ b/components/engine/devmapper/deviceset.go @@ -388,7 +388,7 @@ func (devices *DeviceSet) initDevmapper() error { // - Managed by docker // - The target of this device is at major and minor // - If is defined, use that file inside the device as a loopback image. Otherwise use the device itself. - devices.devicePrefix = fmt.Sprintf("docker-%d,%d-%d", major(sysSt.Dev), minor(sysSt.Dev), sysSt.Ino) + devices.devicePrefix = fmt.Sprintf("docker-%d:%d-%d", major(sysSt.Dev), minor(sysSt.Dev), sysSt.Ino) utils.Debugf("Generated prefix: %s", devices.devicePrefix) // Check for the existence of the device -pool From 439158e160b9e060592ea148adf27b800765806d Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 18 Oct 2013 12:29:16 -0700 Subject: [PATCH 182/301] Switch back some Errorf to Debugf. Upstream-commit: c2175ae736e6b77119ec92a832e325ec076a1e78 Component: engine --- components/engine/buildfile.go | 6 ++++-- components/engine/commands.go | 6 +++++- components/engine/container.go | 5 +++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/components/engine/buildfile.go b/components/engine/buildfile.go index db1fdf9e72..f910bf10bc 100644 --- a/components/engine/buildfile.go +++ b/components/engine/buildfile.go @@ -176,7 +176,9 @@ func (b *buildFile) CmdEnv(args string) error { func (b *buildFile) CmdCmd(args string) error { var cmd []string if err := json.Unmarshal([]byte(args), &cmd); err != nil { - utils.Errorf("Error unmarshalling: %s, setting cmd to /bin/sh -c", err) + // If the unmarshal fails, it is not an error, we just use the + // args as a string. + utils.Debugf("Error unmarshalling: %s, setting cmd to /bin/sh -c", err) cmd = []string{"/bin/sh", "-c", args} } if err := b.commit("", cmd, fmt.Sprintf("CMD %v", cmd)); err != nil { @@ -296,7 +298,7 @@ func (b *buildFile) addContext(container *Container, orig, dest string) error { } // First try to unpack the source as an archive } else if err := UntarPath(origPath, destPath); err != nil { - utils.Errorf("Couldn't untar %s to %s: %s", origPath, destPath, err) + utils.Debugf("[tar] Not a directory nor a tar archive. Copying as a file. Untar error from %s to %s: %s", origPath, destPath, err) // If that fails, just copy it as a regular file if err := os.MkdirAll(path.Dir(destPath), 0755); err != nil { return err diff --git a/components/engine/commands.go b/components/engine/commands.go index b72c2c1516..a3bbec6588 100644 --- a/components/engine/commands.go +++ b/components/engine/commands.go @@ -1539,7 +1539,11 @@ func (cli *DockerCli) CmdRun(args ...string) error { if config.AttachStdin || config.AttachStdout || config.AttachStderr { if config.Tty { if err := cli.monitorTtySize(runResult.ID); err != nil { - utils.Errorf("Error monitoring TTY size: %s\n", err) + // When running the test suite, there is no terminal, just pipes. + // Discard the error then. + if os.Getenv("TEST") != "1" { + utils.Errorf("Error monitoring TTY size: %s\n", err) + } } } diff --git a/components/engine/container.go b/components/engine/container.go index f133bce142..f499559f8e 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -979,12 +979,13 @@ func (container *Container) monitor(hostConfig *HostConfig) { // If the command does not exists, try to wait via lxc if container.cmd == nil { if err := container.waitLxc(); err != nil { - utils.Errorf("%s: Process: %s", container.ID, err) + // Discard the error as any signals or non 0 returns will generate an error + utils.Debugf("%s: Process: %s", container.ShortID(), err) } } else { if err := container.cmd.Wait(); err != nil { // Discard the error as any signals or non 0 returns will generate an error - utils.Errorf("%s: Process: %s", container.ID, err) + utils.Debugf("%s: Process: %s", container.ShortID(), err) } } utils.Debugf("Process finished") From 91978e153350ba69485661d27534a5611d7218d5 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 22 Oct 2013 11:13:24 +0200 Subject: [PATCH 183/301] Fix typo that made change detection break We were comparing the old and old mtimes rather than the old and the new. This meant we missed some file changes where only the mtime changed. Upstream-commit: bc1c5ddf2ebbb74dd2e2bc2d4a7a24c75edf6877 Component: engine --- components/engine/changes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/changes.go b/components/engine/changes.go index 765dca77b6..95e86399d0 100644 --- a/components/engine/changes.go +++ b/components/engine/changes.go @@ -119,7 +119,7 @@ func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) { // breaks down is if some code intentionally hides a change by setting // back mtime oldMtime := syscall.NsecToTimeval(oldStat.Mtim.Nano()) - newMtime := syscall.NsecToTimeval(oldStat.Mtim.Nano()) + newMtime := syscall.NsecToTimeval(newStat.Mtim.Nano()) if oldStat.Mode != newStat.Mode || oldStat.Uid != newStat.Uid || oldStat.Gid != newStat.Gid || From f514bb46a54f22388688ef9bbe5fe4ae5dc4402f Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 31 Oct 2013 18:07:54 -0700 Subject: [PATCH 184/301] Initial work on moving aufs to a subpackage Upstream-commit: 699a1074fb9cdf323044f8c132d7130f6a2945bc Component: engine --- components/engine/aufs/aufs.go | 78 ++++++++++++++++++++ components/engine/{ => aufs}/mount.go | 2 +- components/engine/{ => aufs}/mount_darwin.go | 2 +- components/engine/{ => aufs}/mount_linux.go | 2 +- components/engine/container.go | 22 +----- components/engine/graph.go | 7 +- components/engine/graph_test.go | 11 ++- components/engine/graphbackend/backend.go | 16 ++++ components/engine/image.go | 73 ++---------------- components/engine/runtime.go | 44 ++++++++++- 10 files changed, 161 insertions(+), 96 deletions(-) create mode 100644 components/engine/aufs/aufs.go rename components/engine/{ => aufs}/mount.go (98%) rename components/engine/{ => aufs}/mount_darwin.go (92%) rename components/engine/{ => aufs}/mount_linux.go (92%) create mode 100644 components/engine/graphbackend/backend.go diff --git a/components/engine/aufs/aufs.go b/components/engine/aufs/aufs.go new file mode 100644 index 0000000000..766091cf04 --- /dev/null +++ b/components/engine/aufs/aufs.go @@ -0,0 +1,78 @@ +package aufs + +import ( + "fmt" + "github.com/dotcloud/docker/graphbackend" + "log" + "os" + "os/exec" + "path" +) + +type AufsBackend struct { +} + +// Return a new AUFS backend +// An error is returned if AUFS is not supported +func NewBackend() (*AufsBackend, error) { + return &AufsBackend{}, nil +} + +func (a *AufsBackend) Mount(img graphbackend.Image, root string) error { + layers, err := img.Layers() + if err != nil { + return err + } + + target := path.Join(root, "rootfs") + rw := path.Join(root, "rw") + + // Create the target directories if they don't exist + if err := os.Mkdir(target, 0755); err != nil && !os.IsExist(err) { + return err + } + if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { + return err + } + if err := a.aufsMount(layers, rw, target); err != nil { + return err + } + return nil +} + +func (a *AufsBackend) Unmount(root string) error { + target := path.Join(root, "rootfs") + if _, err := os.Stat(target); err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + return Unmount(target) +} + +func (a *AufsBackend) Mounted(root string) (bool, error) { + return Mounted(path.Join(root, "rootfs")) +} + +func (a *AufsBackend) aufsMount(ro []string, rw, target string) error { + rwBranch := fmt.Sprintf("%v=rw", rw) + roBranches := "" + for _, layer := range ro { + roBranches += fmt.Sprintf("%v=ro+wh:", layer) + } + branches := fmt.Sprintf("br:%v:%v,xino=/dev/shm/aufs.xino", rwBranch, roBranches) + + //if error, try to load aufs kernel module + if err := mount("none", target, "aufs", 0, branches); err != nil { + log.Printf("Kernel does not support AUFS, trying to load the AUFS module with modprobe...") + if err := exec.Command("modprobe", "aufs").Run(); err != nil { + return fmt.Errorf("Unable to load the AUFS module") + } + log.Printf("...module loaded.") + if err := mount("none", target, "aufs", 0, branches); err != nil { + return fmt.Errorf("Unable to mount using aufs") + } + } + return nil +} diff --git a/components/engine/mount.go b/components/engine/aufs/mount.go similarity index 98% rename from components/engine/mount.go rename to components/engine/aufs/mount.go index f4a4dfbae1..15cb2da93f 100644 --- a/components/engine/mount.go +++ b/components/engine/aufs/mount.go @@ -1,4 +1,4 @@ -package docker +package aufs import ( "fmt" diff --git a/components/engine/mount_darwin.go b/components/engine/aufs/mount_darwin.go similarity index 92% rename from components/engine/mount_darwin.go rename to components/engine/aufs/mount_darwin.go index aeac78cda5..ce448036f2 100644 --- a/components/engine/mount_darwin.go +++ b/components/engine/aufs/mount_darwin.go @@ -1,4 +1,4 @@ -package docker +package aufs import "errors" diff --git a/components/engine/mount_linux.go b/components/engine/aufs/mount_linux.go similarity index 92% rename from components/engine/mount_linux.go rename to components/engine/aufs/mount_linux.go index 0efb253003..7122b73cea 100644 --- a/components/engine/mount_linux.go +++ b/components/engine/aufs/mount_linux.go @@ -1,4 +1,4 @@ -package docker +package aufs import "syscall" diff --git a/components/engine/container.go b/components/engine/container.go index 9448932674..e2214842d3 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -1381,19 +1381,11 @@ func (container *Container) EnsureMounted() error { } func (container *Container) Mount() error { - image, err := container.GetImage() - if err != nil { - return err - } - return image.Mount(container.RootfsPath(), container.rwPath()) + return container.runtime.Mount(container) } func (container *Container) Changes() ([]Change, error) { - image, err := container.GetImage() - if err != nil { - return nil, err - } - return image.Changes(container.rwPath()) + return container.runtime.Changes(container) } func (container *Container) GetImage() (*Image, error) { @@ -1404,17 +1396,11 @@ func (container *Container) GetImage() (*Image, error) { } func (container *Container) Mounted() (bool, error) { - return Mounted(container.RootfsPath()) + return container.runtime.Mounted(container) } func (container *Container) Unmount() error { - if _, err := os.Stat(container.RootfsPath()); err != nil { - if os.IsNotExist(err) { - return nil - } - return err - } - return Unmount(container.RootfsPath()) + return container.runtime.Unmount(container) } // ShortID returns a shorthand version of the container's id for convenience. diff --git a/components/engine/graph.go b/components/engine/graph.go index d4ce12e67b..266bef5a57 100644 --- a/components/engine/graph.go +++ b/components/engine/graph.go @@ -2,6 +2,7 @@ package docker import ( "fmt" + "github.com/dotcloud/docker/graphbackend" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -16,11 +17,12 @@ import ( type Graph struct { Root string idIndex *utils.TruncIndex + backend graphbackend.GraphBackend } // 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, backend graphbackend.GraphBackend) (*Graph, error) { abspath, err := filepath.Abs(root) if err != nil { return nil, err @@ -32,6 +34,7 @@ func NewGraph(root string) (*Graph, error) { graph := &Graph{ Root: abspath, idIndex: utils.NewTruncIndex(), + backend: backend, } if err := graph.restore(); err != nil { return nil, err @@ -238,7 +241,7 @@ func (graph *Graph) getDockerInitLayer() (string, error) { 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 NewGraph(path.Join(graph.Root, "_tmp"), graph.backend) } // Check if given error is "not empty". diff --git a/components/engine/graph_test.go b/components/engine/graph_test.go index 471016938d..cc4c6614f6 100644 --- a/components/engine/graph_test.go +++ b/components/engine/graph_test.go @@ -4,6 +4,7 @@ import ( "archive/tar" "bytes" "errors" + "github.com/dotcloud/docker/aufs" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -144,12 +145,12 @@ func TestMount(t *testing.T) { if err := os.MkdirAll(rw, 0700); err != nil { t.Fatal(err) } - if err := image.Mount(rootfs, rw); err != nil { + if err := graph.backend.Mount(image, tmp); err != nil { t.Fatal(err) } // FIXME: test for mount contents defer func() { - if err := Unmount(rootfs); err != nil { + if err := graph.backend.Unmount(tmp); err != nil { t.Error(err) } }() @@ -294,7 +295,11 @@ func tempGraph(t *testing.T) *Graph { if err != nil { t.Fatal(err) } - graph, err := NewGraph(tmp) + backend, err := aufs.NewBackend() + if err != nil { + t.Fatal(err) + } + graph, err := NewGraph(tmp, backend) if err != nil { t.Fatal(err) } diff --git a/components/engine/graphbackend/backend.go b/components/engine/graphbackend/backend.go new file mode 100644 index 0000000000..33360ad5cb --- /dev/null +++ b/components/engine/graphbackend/backend.go @@ -0,0 +1,16 @@ +package graphbackend + +type Image interface { + Layers() ([]string, error) +} + +type GraphBackend interface { + // Create(img *Image) error + // Delete(img *Image) error + Mount(img Image, root string) error + Unmount(root string) error + Mounted(root string) (bool, error) + // UnmountAll(img *Image) error + // Changes(img *Image, dest string) ([]Change, error) + // Layer(img *Image, dest string) (Archive, error) +} diff --git a/components/engine/image.go b/components/engine/image.go index a62724803f..6d6f404a6b 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -8,9 +8,7 @@ import ( "github.com/dotcloud/docker/utils" "io" "io/ioutil" - "log" "os" - "os/exec" "path" "path/filepath" "strconv" @@ -136,31 +134,6 @@ func jsonPath(root string) string { return path.Join(root, "json") } -func MountAUFS(ro []string, rw string, target string) error { - // FIXME: Now mount the layers - rwBranch := fmt.Sprintf("%v=rw", rw) - roBranches := "" - for _, layer := range ro { - roBranches += fmt.Sprintf("%v=ro+wh:", layer) - } - branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches) - - branches += ",xino=/dev/shm/aufs.xino" - - //if error, try to load aufs kernel module - if err := mount("none", target, "aufs", 0, branches); err != nil { - log.Printf("Kernel does not support AUFS, trying to load the AUFS module with modprobe...") - if err := exec.Command("modprobe", "aufs").Run(); err != nil { - return fmt.Errorf("Unable to load the AUFS module") - } - log.Printf("...module loaded.") - if err := mount("none", target, "aufs", 0, branches); err != nil { - return fmt.Errorf("Unable to mount using aufs") - } - } - return nil -} - // TarLayer returns a tar archive of the image's filesystem layer. func (image *Image) TarLayer(compression Compression) (Archive, error) { layerPath, err := image.layer() @@ -170,37 +143,6 @@ func (image *Image) TarLayer(compression Compression) (Archive, error) { return Tar(layerPath, compression) } -func (image *Image) Mount(root, rw string) error { - if mounted, err := Mounted(root); err != nil { - return err - } else if mounted { - return fmt.Errorf("%s is already mounted", root) - } - layers, err := image.layers() - if err != nil { - return err - } - // Create the target directories if they don't exist - if err := os.Mkdir(root, 0755); err != nil && !os.IsExist(err) { - return err - } - if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { - return err - } - if err := MountAUFS(layers, rw, root); err != nil { - return err - } - return nil -} - -func (image *Image) Changes(rw string) ([]Change, error) { - layers, err := image.layers() - if err != nil { - return nil, err - } - return Changes(layers, rw) -} - func (image *Image) ShortID() string { return utils.TruncateID(image.ID) } @@ -243,7 +185,11 @@ func (img *Image) History() ([]*Image, error) { // layers returns all the filesystem layers needed to mount an image // FIXME: @shykes refactor this function with the new error handling // (I'll do it if I have time tonight, I focus on the rest) -func (img *Image) layers() ([]string, error) { +func (img *Image) Layers() ([]string, error) { + if img.graph == nil { + + return nil, fmt.Errorf("Can't lookup dockerinit layer of unregistered image") + } var list []string var e error if err := img.WalkHistory( @@ -265,7 +211,7 @@ func (img *Image) layers() ([]string, error) { } // Inject the dockerinit layer (empty place-holder for mount-binding dockerinit) - if dockerinitLayer, err := img.getDockerInitLayer(); err != nil { + if dockerinitLayer, err := img.graph.getDockerInitLayer(); err != nil { return nil, err } else { list = append([]string{dockerinitLayer}, list...) @@ -299,13 +245,6 @@ func (img *Image) GetParent() (*Image, error) { return img.graph.Get(img.Parent) } -func (img *Image) getDockerInitLayer() (string, error) { - if img.graph == nil { - return "", fmt.Errorf("Can't lookup dockerinit layer of unregistered image") - } - return img.graph.getDockerInitLayer() -} - func (img *Image) root() (string, error) { if img.graph == nil { return "", fmt.Errorf("Can't lookup root of unregistered image") diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 10ab7a249f..e2ee188391 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -5,6 +5,7 @@ import ( "container/list" "database/sql" "fmt" + "github.com/dotcloud/docker/aufs" "github.com/dotcloud/docker/gograph" "github.com/dotcloud/docker/utils" "io" @@ -581,12 +582,16 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) { return nil, err } - - g, err := NewGraph(path.Join(config.GraphPath, "graph")) + backend, err := aufs.NewBackend() if err != nil { return nil, err } - volumes, err := NewGraph(path.Join(config.GraphPath, "volumes")) + + g, err := NewGraph(path.Join(config.GraphPath, "graph"), backend) + if err != nil { + return nil, err + } + volumes, err := NewGraph(path.Join(config.GraphPath, "volumes"), backend) if err != nil { return nil, err } @@ -644,6 +649,39 @@ func (runtime *Runtime) Close() error { 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() + if err != nil { + return err + } + return runtime.graph.backend.Mount(img, container.root) +} + +func (runtime *Runtime) Unmount(container *Container) error { + return runtime.graph.backend.Unmount(container.root) +} + +func (runtime *Runtime) Mounted(container *Container) (bool, error) { + return runtime.graph.backend.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()) +} + // History is a convenience type for storing a list of containers, // ordered by creation date. type History []*Container From 2e8d2b4ea3fb7fd114c2d49b6aadf0c665d24334 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 1 Nov 2013 04:52:39 +0000 Subject: [PATCH 185/301] Re-incorporate ChangesDirs() for the use of the devmapper backend, without changing existing prototypes. This is a neutral change. The primary aufs backend is still in place and not affected. Upstream-commit: a5031d47d9ca283c1fd9db1e94cd19f9b9a73af9 Component: engine --- components/engine/changes.go | 102 +++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/components/engine/changes.go b/components/engine/changes.go index 43573cd606..7a88ef0d01 100644 --- a/components/engine/changes.go +++ b/components/engine/changes.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" "strings" + "syscall" ) type ChangeType int @@ -104,3 +105,104 @@ func Changes(layers []string, rw string) ([]Change, error) { } return changes, nil } + +func ChangesDirs(newDir, oldDir string) ([]Change, error) { + var changes []Change + err := filepath.Walk(newDir, func(newPath string, f os.FileInfo, err error) error { + if err != nil { + return err + } + + var newStat syscall.Stat_t + err = syscall.Lstat(newPath, &newStat) + if err != nil { + return err + } + + // Rebase path + relPath, err := filepath.Rel(newDir, newPath) + if err != nil { + return err + } + relPath = filepath.Join("/", relPath) + + // Skip root + if relPath == "/" || relPath == "/.docker-id" { + return nil + } + + change := Change{ + Path: relPath, + } + + oldPath := filepath.Join(oldDir, relPath) + + var oldStat = &syscall.Stat_t{} + err = syscall.Lstat(oldPath, oldStat) + if err != nil { + if !os.IsNotExist(err) { + return err + } + oldStat = nil + } + + if oldStat == nil { + change.Kind = ChangeAdd + changes = append(changes, change) + } else { + if oldStat.Ino != newStat.Ino || + oldStat.Mode != newStat.Mode || + oldStat.Uid != newStat.Uid || + oldStat.Gid != newStat.Gid || + oldStat.Rdev != newStat.Rdev || + oldStat.Size != newStat.Size || + oldStat.Blocks != newStat.Blocks || + oldStat.Mtim != newStat.Mtim || + oldStat.Ctim != newStat.Ctim { + change.Kind = ChangeModify + changes = append(changes, change) + } + } + + return nil + }) + if err != nil { + return nil, err + } + err = filepath.Walk(oldDir, func(oldPath string, f os.FileInfo, err error) error { + if err != nil { + return err + } + + // Rebase path + relPath, err := filepath.Rel(oldDir, oldPath) + if err != nil { + return err + } + relPath = filepath.Join("/", relPath) + + // Skip root + if relPath == "/" { + return nil + } + + change := Change{ + Path: relPath, + } + + newPath := filepath.Join(newDir, relPath) + + var newStat = &syscall.Stat_t{} + err = syscall.Lstat(newPath, newStat) + if err != nil && os.IsNotExist(err) { + change.Kind = ChangeDelete + changes = append(changes, change) + } + + return nil + }) + if err != nil { + return nil, err + } + return changes, nil +} From 6fb0ff4f9a3ee0a470e8f3d48c8d1f456ef54f58 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 1 Nov 2013 05:06:35 +0000 Subject: [PATCH 186/301] archive.ApplyLayer(): parse a tar archive as a standard aufs-compatible diff, and apply it on a directory Upstream-commit: 318dd33fb778282c3012ee06d892d6375e98cd6b Component: engine --- components/engine/archive/diff.go | 59 +++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 components/engine/archive/diff.go diff --git a/components/engine/archive/diff.go b/components/engine/archive/diff.go new file mode 100644 index 0000000000..3154a44f36 --- /dev/null +++ b/components/engine/archive/diff.go @@ -0,0 +1,59 @@ +package archive + +import ( + "path/filepath" + "log" + "os" + "strings" +) + +// ApplyLayer parses a diff in the standard layer format from `layer`, and +// applies it to the directory `dest`. +func ApplyLayer(dest string, layer Archive) error { + // Poor man's diff applyer in 2 steps: + + // Step 1: untar everything in place + if err := Untar(layer, dest); err != nil { + return err + } + + // Step 2: walk for whiteouts and apply them, removing them in the process + err := filepath.Walk(dest, func(fullPath string, f os.FileInfo, err error) error { + if err != nil { + return err + } + + // Rebase path + path, err := filepath.Rel(dest, fullPath) + if err != nil { + return err + } + path = filepath.Join("/", path) + + // Skip AUFS metadata + if matched, err := filepath.Match("/.wh..wh.*", path); err != nil { + return err + } else if matched { + log.Printf("Removing aufs metadata %s", fullPath) + _ = os.Remove(fullPath) + } + + filename := filepath.Base(path) + if strings.HasPrefix(filename, ".wh.") { + rmTargetName := filename[len(".wh."):] + rmTargetPath := filepath.Join(filepath.Dir(fullPath), rmTargetName) + // Remove the file targeted by the whiteout + log.Printf("Removing whiteout target %s", rmTargetPath) + _ = os.Remove(rmTargetPath) + // Remove the whiteout itself + log.Printf("Removing whiteout %s", fullPath) + _ = os.Remove(fullPath) + } + return nil + }) + if err != nil { + return err + } + return nil +} + From b76d80893d8298a09952c0616d6b2e7e2a153e83 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 1 Nov 2013 05:06:49 +0000 Subject: [PATCH 187/301] Work In Progress: devmapper backend plugin Upstream-commit: 86dd2473c1351df87520111b1b1ba9953f9e5f09 Component: engine --- components/engine/devmapper/plugin.go | 72 +++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 components/engine/devmapper/plugin.go diff --git a/components/engine/devmapper/plugin.go b/components/engine/devmapper/plugin.go new file mode 100644 index 0000000000..e0534b4dca --- /dev/null +++ b/components/engine/devmapper/plugin.go @@ -0,0 +1,72 @@ +package devmapper + +import ( + "github.com/dotcloud/docker/archive" +) + +type Image interface { + ID() string + Parent() (Image, error) + Path() string +} + +type DMBackend struct { + *DeviceSet +} + +func (b *DMBackend) Init(home string) error { + b.DeviceSet = NewDeviceSet(home) + if err := b.DeviceSet.ensureInit(); err != nil { + return err + } + return nil +} + +func (b *DMBackend) Shutdown() error { + return b.DeviceSet.Shutdown() +} + +func (b *DMBackend) Create(img image, layer archive.Archive) error { + // Determine the source of the snapshot (parent id or init device) + var parentID string + if parent, err := img.Parent(); err != nil { + return err + } else if parent != nil { + parentID = parent.ID() + } + // Create the device for this image by snapshotting source + if err := b.DeviceSet.AddDevice(img.ID(), parentID); err != nil { + return err + } + // Mount the device in rootfs + mp := b.mountpoint(id) + if err := os.MkdirAll(mp); err != nil { + return err + } + if err := b.DeviceSet.MountDevice(id, mp, false); err != nil { + return err + } + // Apply the layer as a diff + if layer != nil { + if err := archive.ApplyLayer(mp, layer); err != nil { + return err + } + } + return nil +} + + +func (b *DMBackend) mountpoint(id string) string { + if b.home == "" { + return "" + } + return path.Join(b.home, "mnt", id) +} + +func (b *DMBackend) Changes(img *Image, dest string) ([]Change, error) { + return nil, fmt.Errorf("Not implemented") +} + +func (b *DMBackend) Layer(img *Image, dest string) (Archive, error) { + return nil, fmt.Errorf("Not implemented") +} From 9c7bb08099627bef0a9a3beebb047b83da099f8a Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 1 Nov 2013 19:04:08 +0000 Subject: [PATCH 188/301] Work In Progress: fix devmapper plugin, first independent test passes. Upstream-commit: d64df7c7659560d520a7d8ad8987083e4d52cefc Component: engine --- components/engine/devmapper/plugin.go | 40 ++++++++++---- components/engine/devmapper/plugin_test.go | 61 ++++++++++++++++++++++ 2 files changed, 90 insertions(+), 11 deletions(-) create mode 100644 components/engine/devmapper/plugin_test.go diff --git a/components/engine/devmapper/plugin.go b/components/engine/devmapper/plugin.go index e0534b4dca..5013f1a47e 100644 --- a/components/engine/devmapper/plugin.go +++ b/components/engine/devmapper/plugin.go @@ -1,32 +1,50 @@ package devmapper import ( + "fmt" + "os" + "path" "github.com/dotcloud/docker/archive" ) +// Placeholder interfaces, to be replaced +// at integration. + type Image interface { ID() string Parent() (Image, error) Path() string } +type Change interface { + +} + +// End of placeholder interfaces. + + + type DMBackend struct { *DeviceSet + home string } -func (b *DMBackend) Init(home string) error { - b.DeviceSet = NewDeviceSet(home) - if err := b.DeviceSet.ensureInit(); err != nil { - return err +func Init(home string) (*DMBackend, error) { + b := &DMBackend{ + DeviceSet: NewDeviceSet(home), + home: home, } - return nil + if err := b.DeviceSet.ensureInit(); err != nil { + return nil, err + } + return b, nil } -func (b *DMBackend) Shutdown() error { +func (b *DMBackend) Cleanup() error { return b.DeviceSet.Shutdown() } -func (b *DMBackend) Create(img image, layer archive.Archive) error { +func (b *DMBackend) Create(img Image, layer archive.Archive) error { // Determine the source of the snapshot (parent id or init device) var parentID string if parent, err := img.Parent(); err != nil { @@ -39,11 +57,11 @@ func (b *DMBackend) Create(img image, layer archive.Archive) error { return err } // Mount the device in rootfs - mp := b.mountpoint(id) - if err := os.MkdirAll(mp); err != nil { + mp := b.mountpoint(img.ID()) + if err := os.MkdirAll(mp, 0700); err != nil { return err } - if err := b.DeviceSet.MountDevice(id, mp, false); err != nil { + if err := b.DeviceSet.MountDevice(img.ID(), mp, false); err != nil { return err } // Apply the layer as a diff @@ -67,6 +85,6 @@ func (b *DMBackend) Changes(img *Image, dest string) ([]Change, error) { return nil, fmt.Errorf("Not implemented") } -func (b *DMBackend) Layer(img *Image, dest string) (Archive, error) { +func (b *DMBackend) Layer(img *Image, dest string) (archive.Archive, error) { return nil, fmt.Errorf("Not implemented") } diff --git a/components/engine/devmapper/plugin_test.go b/components/engine/devmapper/plugin_test.go new file mode 100644 index 0000000000..08df3c3171 --- /dev/null +++ b/components/engine/devmapper/plugin_test.go @@ -0,0 +1,61 @@ +package devmapper + +import ( + "io/ioutil" + _ "os" + "testing" +) + +type TestImage struct { + id string + path string +} + +func (img *TestImage) ID() string { + return img.id +} + +func (img *TestImage) Path() string { + return img.path +} + +func (img *TestImage) Parent() (Image, error) { + return nil, nil +} + + + +func mkTestImage(t *testing.T) Image { + return &TestImage{ + path: mkTestDirectory(t), + id: "4242", + } +} + +func mkTestDirectory(t *testing.T) string { + dir, err := ioutil.TempDir("", "docker-test-devmapper-") + if err != nil { + t.Fatal(err) + } + return dir +} + +func TestInit(t *testing.T) { + home := mkTestDirectory(t) + // defer os.RemoveAll(home) + plugin, err := Init(home) + if err != nil { + t.Fatal(err) + } + defer func() { + return + if err := plugin.Cleanup(); err != nil { + t.Fatal(err) + } + }() + img := mkTestImage(t) + // defer os.RemoveAll(img.(*TestImage).path) + if err := plugin.Create(img, nil); err != nil { + t.Fatal(err) + } +} From b69b588093b7e3d7b98e48c77410163c67d9d253 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 1 Nov 2013 19:30:30 +0000 Subject: [PATCH 189/301] devmapper: rename Create() to OnCreate(), cleanup tests Upstream-commit: d2c2c2c1162498e6f7c2b9298795e3ef77b6523b Component: engine --- components/engine/devmapper/plugin.go | 2 +- components/engine/devmapper/plugin_test.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/engine/devmapper/plugin.go b/components/engine/devmapper/plugin.go index 5013f1a47e..67252eda38 100644 --- a/components/engine/devmapper/plugin.go +++ b/components/engine/devmapper/plugin.go @@ -44,7 +44,7 @@ func (b *DMBackend) Cleanup() error { return b.DeviceSet.Shutdown() } -func (b *DMBackend) Create(img Image, layer archive.Archive) error { +func (b *DMBackend) OnCreate(img Image, layer archive.Archive) error { // Determine the source of the snapshot (parent id or init device) var parentID string if parent, err := img.Parent(); err != nil { diff --git a/components/engine/devmapper/plugin_test.go b/components/engine/devmapper/plugin_test.go index 08df3c3171..5f80c1dab2 100644 --- a/components/engine/devmapper/plugin_test.go +++ b/components/engine/devmapper/plugin_test.go @@ -2,7 +2,7 @@ package devmapper import ( "io/ioutil" - _ "os" + "os" "testing" ) @@ -42,7 +42,7 @@ func mkTestDirectory(t *testing.T) string { func TestInit(t *testing.T) { home := mkTestDirectory(t) - // defer os.RemoveAll(home) + defer os.RemoveAll(home) plugin, err := Init(home) if err != nil { t.Fatal(err) @@ -54,8 +54,8 @@ func TestInit(t *testing.T) { } }() img := mkTestImage(t) - // defer os.RemoveAll(img.(*TestImage).path) - if err := plugin.Create(img, nil); err != nil { + defer os.RemoveAll(img.(*TestImage).path) + if err := plugin.OnCreate(img, nil); err != nil { t.Fatal(err) } } From 35f9c13fe5d2742b2535dc88bf0641fd42415ba7 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 2 Nov 2013 21:25:06 +0000 Subject: [PATCH 190/301] devmapper: implement OnRemove Upstream-commit: d23b9e87344bd7c153e05abdd05cc28840814a22 Component: engine --- components/engine/devmapper/plugin.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/components/engine/devmapper/plugin.go b/components/engine/devmapper/plugin.go index 67252eda38..a3ba53d896 100644 --- a/components/engine/devmapper/plugin.go +++ b/components/engine/devmapper/plugin.go @@ -73,6 +73,13 @@ func (b *DMBackend) OnCreate(img Image, layer archive.Archive) error { return nil } +func (b *DMBackend) OnRemove(img Image) error { + id := img.ID() + if err := b.DeviceSet.RemoveDevice(id); err != nil { + return fmt.Errorf("Unable to remove device for %v: %v", id, err) + } + return nil +} func (b *DMBackend) mountpoint(id string) string { if b.home == "" { From a4e732c90bcb671e97c650b971694ff703ea4417 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 4 Nov 2013 01:54:51 +0000 Subject: [PATCH 191/301] Rename graph backends to 'drivers' which is probably more self-explanatory Upstream-commit: ff42748bc567745198b33baa697338dd697fb621 Component: engine --- components/engine/aufs/aufs.go | 20 +++++++++---------- components/engine/graph.go | 10 +++++----- components/engine/graph_test.go | 6 +++--- .../backend.go => graphdriver/driver.go} | 4 ++-- components/engine/runtime.go | 12 +++++------ 5 files changed, 26 insertions(+), 26 deletions(-) rename components/engine/{graphbackend/backend.go => graphdriver/driver.go} (87%) diff --git a/components/engine/aufs/aufs.go b/components/engine/aufs/aufs.go index 766091cf04..528a77f59f 100644 --- a/components/engine/aufs/aufs.go +++ b/components/engine/aufs/aufs.go @@ -2,23 +2,23 @@ package aufs import ( "fmt" - "github.com/dotcloud/docker/graphbackend" + "github.com/dotcloud/docker/graphdriver" "log" "os" "os/exec" "path" ) -type AufsBackend struct { +type AufsDriver struct { } -// Return a new AUFS backend -// An error is returned if AUFS is not supported -func NewBackend() (*AufsBackend, error) { - return &AufsBackend{}, nil +// New returns a new AUFS driver. +// An error is returned if AUFS is not supported. +func New() (*AufsDriver, error) { + return &AufsDriver{}, nil } -func (a *AufsBackend) Mount(img graphbackend.Image, root string) error { +func (a *AufsDriver) Mount(img graphdriver.Image, root string) error { layers, err := img.Layers() if err != nil { return err @@ -40,7 +40,7 @@ func (a *AufsBackend) Mount(img graphbackend.Image, root string) error { return nil } -func (a *AufsBackend) Unmount(root string) error { +func (a *AufsDriver) Unmount(root string) error { target := path.Join(root, "rootfs") if _, err := os.Stat(target); err != nil { if os.IsNotExist(err) { @@ -51,11 +51,11 @@ func (a *AufsBackend) Unmount(root string) error { return Unmount(target) } -func (a *AufsBackend) Mounted(root string) (bool, error) { +func (a *AufsDriver) Mounted(root string) (bool, error) { return Mounted(path.Join(root, "rootfs")) } -func (a *AufsBackend) aufsMount(ro []string, rw, target string) error { +func (a *AufsDriver) aufsMount(ro []string, rw, target string) error { rwBranch := fmt.Sprintf("%v=rw", rw) roBranches := "" for _, layer := range ro { diff --git a/components/engine/graph.go b/components/engine/graph.go index 266bef5a57..e3ed1fc377 100644 --- a/components/engine/graph.go +++ b/components/engine/graph.go @@ -2,7 +2,7 @@ package docker import ( "fmt" - "github.com/dotcloud/docker/graphbackend" + "github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -17,12 +17,12 @@ import ( type Graph struct { Root string idIndex *utils.TruncIndex - backend graphbackend.GraphBackend + driver graphdriver.Driver } // NewGraph instantiates a new graph at the given root path in the filesystem. // `root` will be created if it doesn't exist. -func NewGraph(root string, backend graphbackend.GraphBackend) (*Graph, error) { +func NewGraph(root string, driver graphdriver.Driver) (*Graph, error) { abspath, err := filepath.Abs(root) if err != nil { return nil, err @@ -34,7 +34,7 @@ func NewGraph(root string, backend graphbackend.GraphBackend) (*Graph, error) { graph := &Graph{ Root: abspath, idIndex: utils.NewTruncIndex(), - backend: backend, + driver: driver, } if err := graph.restore(); err != nil { return nil, err @@ -241,7 +241,7 @@ func (graph *Graph) getDockerInitLayer() (string, error) { 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"), graph.backend) + return NewGraph(path.Join(graph.Root, "_tmp"), graph.driver) } // Check if given error is "not empty". diff --git a/components/engine/graph_test.go b/components/engine/graph_test.go index cc4c6614f6..7c13deab1a 100644 --- a/components/engine/graph_test.go +++ b/components/engine/graph_test.go @@ -145,12 +145,12 @@ func TestMount(t *testing.T) { if err := os.MkdirAll(rw, 0700); err != nil { t.Fatal(err) } - if err := graph.backend.Mount(image, tmp); err != nil { + if err := graph.driver.Mount(image, tmp); err != nil { t.Fatal(err) } // FIXME: test for mount contents defer func() { - if err := graph.backend.Unmount(tmp); err != nil { + if err := graph.driver.Unmount(tmp); err != nil { t.Error(err) } }() @@ -295,7 +295,7 @@ func tempGraph(t *testing.T) *Graph { if err != nil { t.Fatal(err) } - backend, err := aufs.NewBackend() + backend, err := aufs.New() if err != nil { t.Fatal(err) } diff --git a/components/engine/graphbackend/backend.go b/components/engine/graphdriver/driver.go similarity index 87% rename from components/engine/graphbackend/backend.go rename to components/engine/graphdriver/driver.go index 33360ad5cb..5570865e84 100644 --- a/components/engine/graphbackend/backend.go +++ b/components/engine/graphdriver/driver.go @@ -1,10 +1,10 @@ -package graphbackend +package graphdriver type Image interface { Layers() ([]string, error) } -type GraphBackend interface { +type Driver interface { // Create(img *Image) error // Delete(img *Image) error Mount(img Image, root string) error diff --git a/components/engine/runtime.go b/components/engine/runtime.go index e2ee188391..78154b15c9 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -582,16 +582,16 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) { return nil, err } - backend, err := aufs.NewBackend() + driver, err := aufs.New() if err != nil { return nil, err } - g, err := NewGraph(path.Join(config.GraphPath, "graph"), backend) + g, err := NewGraph(path.Join(config.GraphPath, "graph"), driver) if err != nil { return nil, err } - volumes, err := NewGraph(path.Join(config.GraphPath, "volumes"), backend) + volumes, err := NewGraph(path.Join(config.GraphPath, "volumes"), driver) if err != nil { return nil, err } @@ -659,15 +659,15 @@ func (runtime *Runtime) Mount(container *Container) error { if err != nil { return err } - return runtime.graph.backend.Mount(img, container.root) + return runtime.graph.driver.Mount(img, container.root) } func (runtime *Runtime) Unmount(container *Container) error { - return runtime.graph.backend.Unmount(container.root) + return runtime.graph.driver.Unmount(container.root) } func (runtime *Runtime) Mounted(container *Container) (bool, error) { - return runtime.graph.backend.Mounted(container.root) + return runtime.graph.driver.Mounted(container.root) } func (runtime *Runtime) Changes(container *Container) ([]Change, error) { From 2347b141fc3588a6e819ecbdbf5f3a5eac9b5175 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 4 Nov 2013 17:22:43 +0000 Subject: [PATCH 192/301] devmapper: renamed DMBackend to Driver Upstream-commit: b77c5c59840a731c721bbce37765cd475c2cee23 Component: engine --- components/engine/devmapper/plugin.go | 36 +++++++++++++-------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/components/engine/devmapper/plugin.go b/components/engine/devmapper/plugin.go index a3ba53d896..d52d52b784 100644 --- a/components/engine/devmapper/plugin.go +++ b/components/engine/devmapper/plugin.go @@ -24,27 +24,27 @@ type Change interface { -type DMBackend struct { +type Driver struct { *DeviceSet home string } -func Init(home string) (*DMBackend, error) { - b := &DMBackend{ +func Init(home string) (*Driver, error) { + d := &Driver{ DeviceSet: NewDeviceSet(home), home: home, } - if err := b.DeviceSet.ensureInit(); err != nil { + if err := d.DeviceSet.ensureInit(); err != nil { return nil, err } - return b, nil + return d, nil } -func (b *DMBackend) Cleanup() error { - return b.DeviceSet.Shutdown() +func (d *Driver) Cleanup() error { + return d.DeviceSet.Shutdown() } -func (b *DMBackend) OnCreate(img Image, layer archive.Archive) error { +func (d *Driver) OnCreate(img Image, layer archive.Archive) error { // Determine the source of the snapshot (parent id or init device) var parentID string if parent, err := img.Parent(); err != nil { @@ -53,15 +53,15 @@ func (b *DMBackend) OnCreate(img Image, layer archive.Archive) error { parentID = parent.ID() } // Create the device for this image by snapshotting source - if err := b.DeviceSet.AddDevice(img.ID(), parentID); err != nil { + if err := d.DeviceSet.AddDevice(img.ID(), parentID); err != nil { return err } // Mount the device in rootfs - mp := b.mountpoint(img.ID()) + mp := d.mountpoint(img.ID()) if err := os.MkdirAll(mp, 0700); err != nil { return err } - if err := b.DeviceSet.MountDevice(img.ID(), mp, false); err != nil { + if err := d.DeviceSet.MountDevice(img.ID(), mp, false); err != nil { return err } // Apply the layer as a diff @@ -73,25 +73,25 @@ func (b *DMBackend) OnCreate(img Image, layer archive.Archive) error { return nil } -func (b *DMBackend) OnRemove(img Image) error { +func (d *Driver) OnRemove(img Image) error { id := img.ID() - if err := b.DeviceSet.RemoveDevice(id); err != nil { + if err := d.DeviceSet.RemoveDevice(id); err != nil { return fmt.Errorf("Unable to remove device for %v: %v", id, err) } return nil } -func (b *DMBackend) mountpoint(id string) string { - if b.home == "" { +func (d *Driver) mountpoint(id string) string { + if d.home == "" { return "" } - return path.Join(b.home, "mnt", id) + return path.Join(d.home, "mnt", id) } -func (b *DMBackend) Changes(img *Image, dest string) ([]Change, error) { +func (d *Driver) Changes(img *Image, dest string) ([]Change, error) { return nil, fmt.Errorf("Not implemented") } -func (b *DMBackend) Layer(img *Image, dest string) (archive.Archive, error) { +func (d *Driver) Layer(img *Image, dest string) (archive.Archive, error) { return nil, fmt.Errorf("Not implemented") } From 582c8c2bcd4f405b187d00e28b08dacf268c1cbb Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 4 Nov 2013 17:23:46 +0000 Subject: [PATCH 193/301] devmapper: rename plugin*.go to driver*.go Upstream-commit: 6c77f2c1894e906a28a64a4991b4fc2c89cd9ad6 Component: engine --- components/engine/devmapper/{plugin.go => driver.go} | 0 components/engine/devmapper/{plugin_test.go => driver_test.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename components/engine/devmapper/{plugin.go => driver.go} (100%) rename components/engine/devmapper/{plugin_test.go => driver_test.go} (100%) diff --git a/components/engine/devmapper/plugin.go b/components/engine/devmapper/driver.go similarity index 100% rename from components/engine/devmapper/plugin.go rename to components/engine/devmapper/driver.go diff --git a/components/engine/devmapper/plugin_test.go b/components/engine/devmapper/driver_test.go similarity index 100% rename from components/engine/devmapper/plugin_test.go rename to components/engine/devmapper/driver_test.go From 57c35f0f444cc2b33315f915cc28486eba76929f Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 4 Nov 2013 15:39:16 -0700 Subject: [PATCH 194/301] Update Dockerfile to consistently use tabs as the command argument separator Upstream-commit: 24dd838aeefcf4ea1f5a950a9fe0af075f00e2bf Component: engine --- components/engine/Dockerfile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/components/engine/Dockerfile b/components/engine/Dockerfile index ff80e4ad9c..43d7e61eb6 100644 --- a/components/engine/Dockerfile +++ b/components/engine/Dockerfile @@ -23,7 +23,7 @@ # docker hack/release.sh # -docker-version 0.6.1 +docker-version 0.6.1 from ubuntu:12.04 maintainer Solomon Hykes @@ -33,13 +33,13 @@ run apt-get update run apt-get install -y -q curl run apt-get install -y -q git run apt-get install -y -q mercurial -run apt-get install -y -q build-essential libsqlite3-dev +run apt-get install -y -q build-essential libsqlite3-dev # Install Go run curl -s https://go.googlecode.com/files/go1.2rc2.src.tar.gz | tar -v -C /usr/local -xz env PATH /usr/local/go/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin env GOPATH /go:/go/src/github.com/dotcloud/docker/vendor -run cd /usr/local/go/src && ./make.bash && go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std +run cd /usr/local/go/src && ./make.bash && go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std # Ubuntu stuff run apt-get install -y -q ruby1.9.3 rubygems libffi-dev @@ -61,7 +61,7 @@ volume /var/lib/docker workdir /go/src/github.com/dotcloud/docker # Wrap all commands in the "docker-in-docker" script to allow nested containers -entrypoint ["hack/dind"] +entrypoint ["hack/dind"] # Upload docker source -add . /go/src/github.com/dotcloud/docker +add . /go/src/github.com/dotcloud/docker From 2e3d85e90f97047aa5e56be9df8ae814eb7d2624 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 4 Nov 2013 16:11:33 -0700 Subject: [PATCH 195/301] Add libdevmapper static compilation to Dockerfile for device-mapper Upstream-commit: d11f75b5050944e373125008be1c9683797c72a9 Component: engine --- components/engine/Dockerfile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/components/engine/Dockerfile b/components/engine/Dockerfile index 43d7e61eb6..f20fc7a3dc 100644 --- a/components/engine/Dockerfile +++ b/components/engine/Dockerfile @@ -57,6 +57,15 @@ run apt-get install -y -q iptables run apt-get install -y -q lxc run apt-get install -y -q aufs-tools +# Get lvm2 source for compiling statically +run git clone git://git.fedorahosted.org/git/lvm2.git /usr/local/lvm2 && cd /usr/local/lvm2 && git checkout v2_02_103 +# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags +# note: we can't use "git clone -b" above because it requires at least git 1.7.10 to be able to use that on a tag instead of a branch and we only have 1.7.9.5 + +# Compile and install lvm2 +run cd /usr/local/lvm2 && ./configure --enable-static_link && make device-mapper && make install_device-mapper +# see https://git.fedorahosted.org/cgit/lvm2.git/tree/INSTALL + volume /var/lib/docker workdir /go/src/github.com/dotcloud/docker From 14efe59bac2b2c776f51491b279a5df36c7ced4e Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 4 Nov 2013 15:22:34 -0800 Subject: [PATCH 196/301] Add graph driver registration Upstream-commit: 752bfba2c5d6030d6fe3d615d0c2afd2696e7b20 Component: engine --- components/engine/aufs/aufs.go | 10 ++++- components/engine/devmapper/driver.go | 16 ++++---- components/engine/graph.go | 16 ++++++-- components/engine/graphdriver/driver.go | 50 +++++++++++++++++++++++++ components/engine/runtime.go | 9 +---- 5 files changed, 82 insertions(+), 19 deletions(-) diff --git a/components/engine/aufs/aufs.go b/components/engine/aufs/aufs.go index 528a77f59f..9ee6848210 100644 --- a/components/engine/aufs/aufs.go +++ b/components/engine/aufs/aufs.go @@ -9,12 +9,20 @@ import ( "path" ) +func init() { + graphdriver.Register("aufs", Init) +} + type AufsDriver struct { } // New returns a new AUFS driver. // An error is returned if AUFS is not supported. -func New() (*AufsDriver, error) { +func Init(root string) (graphdriver.Driver, error) { + // Try to load the aufs kernel module + if err := exec.Command("modprobe", "aufs").Run(); err != nil { + return nil, err + } return &AufsDriver{}, nil } diff --git a/components/engine/devmapper/driver.go b/components/engine/devmapper/driver.go index d52d52b784..1eaa0ab76f 100644 --- a/components/engine/devmapper/driver.go +++ b/components/engine/devmapper/driver.go @@ -2,11 +2,16 @@ package devmapper import ( "fmt" + "github.com/dotcloud/docker/archive" + "github.com/dotcloud/docker/graphdriver" "os" "path" - "github.com/dotcloud/docker/archive" ) +func init() { + graphdriver.Register("devicemapper", Init) +} + // Placeholder interfaces, to be replaced // at integration. @@ -17,22 +22,19 @@ type Image interface { } type Change interface { - } // End of placeholder interfaces. - - type Driver struct { *DeviceSet home string } -func Init(home string) (*Driver, error) { +func Init(home string) (graphdriver.Driver, error) { d := &Driver{ DeviceSet: NewDeviceSet(home), - home: home, + home: home, } if err := d.DeviceSet.ensureInit(); err != nil { return nil, err @@ -64,7 +66,7 @@ func (d *Driver) OnCreate(img Image, layer archive.Archive) error { if err := d.DeviceSet.MountDevice(img.ID(), mp, false); err != nil { return err } - // Apply the layer as a diff + // Apply the layer as a diff if layer != nil { if err := archive.ApplyLayer(mp, layer); err != nil { return err diff --git a/components/engine/graph.go b/components/engine/graph.go index 449c3871e8..660352b60c 100644 --- a/components/engine/graph.go +++ b/components/engine/graph.go @@ -3,6 +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" "io" @@ -18,12 +20,12 @@ import ( type Graph struct { Root string idIndex *utils.TruncIndex - driver graphdriver.Driver + driver graphdriver.Driver } // NewGraph instantiates a new graph at the given root path in the filesystem. // `root` will be created if it doesn't exist. -func NewGraph(root string, driver graphdriver.Driver) (*Graph, error) { +func NewGraph(root string) (*Graph, error) { abspath, err := filepath.Abs(root) if err != nil { return nil, err @@ -32,10 +34,16 @@ func NewGraph(root string, driver graphdriver.Driver) (*Graph, error) { if err := os.MkdirAll(root, 0700); err != nil && !os.IsExist(err) { return nil, err } + + driver, err := graphdriver.New(root) + if err != nil { + return nil, err + } + graph := &Graph{ Root: abspath, idIndex: utils.NewTruncIndex(), - driver: driver, + driver: driver, } if err := graph.restore(); err != nil { return nil, err @@ -242,7 +250,7 @@ func (graph *Graph) getDockerInitLayer() (string, error) { 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"), graph.driver) + return NewGraph(path.Join(graph.Root, "_tmp")) } // Check if given error is "not empty". diff --git a/components/engine/graphdriver/driver.go b/components/engine/graphdriver/driver.go index 5570865e84..fe6fc56202 100644 --- a/components/engine/graphdriver/driver.go +++ b/components/engine/graphdriver/driver.go @@ -1,5 +1,55 @@ package graphdriver +import ( + "fmt" +) + +type InitFunc func(root string) (Driver, error) + +var ( + // All registred drivers + drivers map[string]InitFunc + // Slice of drivers that should be used in an order + priority = []string{ + "aufs", + "devicemapper", + } +) + +func Register(name string, initFunc InitFunc) error { + if _, exists := drivers[name]; exists { + return fmt.Errorf("Name already registered %s", name) + } + drivers[name] = initFunc + + return nil +} + +func New(root string) (Driver, error) { + var driver Driver + var lastError error + // Check for priority drivers first + for _, name := range priority { + if initFunc, exists := drivers[name]; exists { + driver, lastError = initFunc(root) + if lastError != nil { + continue + } + return driver, nil + } + } + + // Check all registered drivers if no priority driver is found + for _, initFunc := range drivers { + driver, lastError = initFunc(root) + if lastError != nil { + continue + } + return driver, nil + } + return nil, lastError +} + type Image interface { Layers() ([]string, error) } diff --git a/components/engine/runtime.go b/components/engine/runtime.go index a040b5aa53..6acea49ad3 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -5,7 +5,6 @@ import ( "container/list" "database/sql" "fmt" - "github.com/dotcloud/docker/aufs" "github.com/dotcloud/docker/gograph" "github.com/dotcloud/docker/utils" "io" @@ -574,16 +573,12 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) { return nil, err } - driver, err := aufs.New() - if err != nil { - return nil, err - } - g, err := NewGraph(path.Join(config.Root, "graph"), driver) + g, err := NewGraph(path.Join(config.Root, "graph")) if err != nil { return nil, err } - volumes, err := NewGraph(path.Join(config.Root, "volumes"), driver) + volumes, err := NewGraph(path.Join(config.Root, "volumes")) if err != nil { return nil, err } From cb784bb36ff72638fc87b649a5ae879eaf36d6e2 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 4 Nov 2013 20:51:12 -0800 Subject: [PATCH 197/301] Finalize driver interfaces Upstream-commit: ed572b457d28101b6ef465e5a3e221ee1fc7d48a Component: engine --- components/engine/aufs/aufs.go | 81 ++++++++++++++++++++++--- components/engine/graphdriver/driver.go | 35 ++++++----- components/engine/image.go | 37 ----------- 3 files changed, 92 insertions(+), 61 deletions(-) diff --git a/components/engine/aufs/aufs.go b/components/engine/aufs/aufs.go index 9ee6848210..4c14947a95 100644 --- a/components/engine/aufs/aufs.go +++ b/components/engine/aufs/aufs.go @@ -2,6 +2,7 @@ package aufs import ( "fmt" + "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/graphdriver" "log" "os" @@ -14,6 +15,7 @@ func init() { } type AufsDriver struct { + root string } // New returns a new AUFS driver. @@ -23,17 +25,54 @@ func Init(root string) (graphdriver.Driver, error) { if err := exec.Command("modprobe", "aufs").Run(); err != nil { return nil, err } - return &AufsDriver{}, nil + return &AufsDriver{root}, nil } -func (a *AufsDriver) Mount(img graphdriver.Image, root string) error { - layers, err := img.Layers() +func (a *AufsDriver) OnCreate(dir graphdriver.Dir, layer archive.Archive) error { + tmp := path.Join(os.TempDir(), dir.ID()) + if err := os.MkdirAll(tmp, 0755); err != nil { + return err + } + defer os.RemoveAll(tmp) + + layerRoot := path.Join(a.root, dir.ID()) + if err := os.MkdirAll(layerRoot, 0755); err != nil { + return err + } + + if layer != nil { + if err := archive.Untar(layer, tmp); err != nil { + return err + } + } + + if err := os.Rename(tmp, layerRoot); err != nil { + return err + } + return nil +} + +func (a *AufsDriver) OnRemove(dir graphdriver.Dir) error { + tmp := path.Join(os.TempDir(), dir.ID()) + + if err := os.MkdirAll(tmp, 0755); err != nil { + return err + } + + if err := os.Rename(path.Join(a.root, dir.ID()), tmp); err != nil { + return err + } + return os.RemoveAll(tmp) +} + +func (a *AufsDriver) OnMount(dir graphdriver.Dir, dest string) error { + layers, err := a.getLayers(dir) if err != nil { return err } - target := path.Join(root, "rootfs") - rw := path.Join(root, "rw") + target := path.Join(dest, "rootfs") + rw := path.Join(dest, "rw") // Create the target directories if they don't exist if err := os.Mkdir(target, 0755); err != nil && !os.IsExist(err) { @@ -48,8 +87,8 @@ func (a *AufsDriver) Mount(img graphdriver.Image, root string) error { return nil } -func (a *AufsDriver) Unmount(root string) error { - target := path.Join(root, "rootfs") +func (a *AufsDriver) OnUnmount(dest string) error { + target := path.Join(dest, "rootfs") if _, err := os.Stat(target); err != nil { if os.IsNotExist(err) { return nil @@ -59,8 +98,32 @@ func (a *AufsDriver) Unmount(root string) error { return Unmount(target) } -func (a *AufsDriver) Mounted(root string) (bool, error) { - return Mounted(path.Join(root, "rootfs")) +func (a *AufsDriver) Mounted(dest string) (bool, error) { + return Mounted(path.Join(dest, "rootfs")) +} + +func (a *AufsDriver) Layer(dir graphdriver.Dir, dest string) (archive.Archive, error) { + return nil, fmt.Errorf("not implemented") +} + +func (a *AufsDriver) Cleanup() error { + return nil +} + +func (a *AufsDriver) getLayers(dir graphdriver.Dir) ([]string, error) { + var ( + err error + layers = []string{} + current = dir + ) + + for current != nil { + layers = append(layers, current.Path()) + if current, err = current.Parent(); err != nil { + return nil, err + } + } + return layers, nil } func (a *AufsDriver) aufsMount(ro []string, rw, target string) error { diff --git a/components/engine/graphdriver/driver.go b/components/engine/graphdriver/driver.go index fe6fc56202..422be0296f 100644 --- a/components/engine/graphdriver/driver.go +++ b/components/engine/graphdriver/driver.go @@ -2,10 +2,30 @@ package graphdriver import ( "fmt" + "github.com/dotcloud/docker/archive" ) type InitFunc func(root string) (Driver, error) +type Dir interface { + ID() string + Path() string + Parent() (Dir, error) +} + +type Driver interface { + OnCreate(dir Dir, layer archive.Archive) error + OnRemove(dir Dir) error + + OnMount(dir Dir, dest string) error + OnUnmount(dest string) error + Mounted(dest string) (bool, error) + + Layer(dir Dir, dest string) (archive.Archive, error) + + Cleanup() error +} + var ( // All registred drivers drivers map[string]InitFunc @@ -49,18 +69,3 @@ func New(root string) (Driver, error) { } return nil, lastError } - -type Image interface { - Layers() ([]string, error) -} - -type Driver interface { - // Create(img *Image) error - // Delete(img *Image) error - Mount(img Image, root string) error - Unmount(root string) error - Mounted(root string) (bool, error) - // UnmountAll(img *Image) error - // Changes(img *Image, dest string) ([]Change, error) - // Layer(img *Image, dest string) (Archive, error) -} diff --git a/components/engine/image.go b/components/engine/image.go index dc3eb3c92a..5c672adb4b 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -183,43 +183,6 @@ func (img *Image) History() ([]*Image, error) { return parents, nil } -// layers returns all the filesystem layers needed to mount an image -// FIXME: @shykes refactor this function with the new error handling -// (I'll do it if I have time tonight, I focus on the rest) -func (img *Image) Layers() ([]string, error) { - if img.graph == nil { - - return nil, fmt.Errorf("Can't lookup dockerinit layer of unregistered image") - } - var list []string - var e error - if err := img.WalkHistory( - func(img *Image) (err error) { - if layer, err := img.layer(); err != nil { - e = err - } else if layer != "" { - list = append(list, layer) - } - return err - }, - ); err != nil { - return nil, err - } else if e != nil { // Did an error occur inside the handler? - return nil, e - } - if len(list) == 0 { - return nil, fmt.Errorf("No layer found for image %s\n", img.ID) - } - - // Inject the dockerinit layer (empty place-holder for mount-binding dockerinit) - if dockerinitLayer, err := img.graph.getDockerInitLayer(); err != nil { - return nil, err - } else { - list = append([]string{dockerinitLayer}, list...) - } - return list, nil -} - func (img *Image) WalkHistory(handler func(*Image) error) (err error) { currentImg := img for currentImg != nil { From a48448b288310abfb83f5ca14a742a72cffd3197 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 7 Nov 2013 20:30:56 +0000 Subject: [PATCH 198/301] Internal cleanup: use the mounted container filesystem instead of manipulating the aufs layers directly Upstream-commit: 77ae9789d3e1cfb895de9eaf343866f984c92668 Component: engine --- components/engine/api_test.go | 12 +++--------- components/engine/container.go | 11 +++++++---- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/components/engine/api_test.go b/components/engine/api_test.go index 34c89f8294..864628a762 100644 --- a/components/engine/api_test.go +++ b/components/engine/api_test.go @@ -676,7 +676,7 @@ func TestPostContainersCreate(t *testing.T) { t.Fatal(err) } - if _, err := os.Stat(path.Join(container.rwPath(), "test")); err != nil { + if _, err := os.Stat(path.Join(container.RootfsPath(), "test")); err != nil { if os.IsNotExist(err) { utils.Debugf("Err: %s", err) t.Fatalf("The test file has not been created") @@ -1145,7 +1145,7 @@ func TestDeleteContainers(t *testing.T) { t.Fatalf("The container as not been deleted") } - if _, err := os.Stat(path.Join(container.rwPath(), "test")); err == nil { + if _, err := os.Stat(path.Join(container.RootfsPath(), "test")); err == nil { t.Fatalf("The test file has not been deleted") } } @@ -1276,13 +1276,7 @@ func TestDeleteImages(t *testing.T) { t.Errorf("Expected %d image, %d found", len(initialImages), len(images)) } - /* if c := runtime.Get(container.Id); c != nil { - t.Fatalf("The container as not been deleted") - } - - if _, err := os.Stat(path.Join(container.rwPath(), "test")); err == nil { - t.Fatalf("The test file has not been deleted") - } */ + // FIXME: check that container has been deleted, and its filesystem too } func TestJsonContentType(t *testing.T) { diff --git a/components/engine/container.go b/components/engine/container.go index 6994c3b460..f71c1db3fd 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -380,12 +380,11 @@ func (settings *NetworkSettings) PortMappingAPI() []APIPort { // Inject the io.Reader at the given path. Note: do not close the reader func (container *Container) Inject(file io.Reader, pth string) error { - // Make sure the directory exists - if err := os.MkdirAll(path.Join(container.rwPath(), path.Dir(pth)), 0755); err != nil { - return err + if err := container.EnsureMounted(); err != nil { + return fmt.Errorf("inject: error mounting container %s: %s", container.ID, err) } // FIXME: Handle permissions/already existing dest - dest, err := os.Create(path.Join(container.rwPath(), pth)) + dest, err := os.Create(path.Join(container.RootfsPath(), pth)) if err != nil { return err } @@ -1463,6 +1462,10 @@ func (container *Container) GetSize() (int64, int64) { return nil }) + 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()) if err == nil { filepath.Walk(container.RootfsPath(), func(path string, fileInfo os.FileInfo, err error) error { From 4629ddf3bed1bc579146f18ad27c6d7217736eb1 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 7 Nov 2013 20:31:50 +0000 Subject: [PATCH 199/301] Fix a crash in graphdriver init Upstream-commit: a63ff8da46e11c857cd3d743d72d211c96b637e4 Component: engine --- components/engine/graphdriver/driver.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/engine/graphdriver/driver.go b/components/engine/graphdriver/driver.go index 422be0296f..b17ff75a0c 100644 --- a/components/engine/graphdriver/driver.go +++ b/components/engine/graphdriver/driver.go @@ -36,6 +36,10 @@ var ( } ) +func init() { + drivers = make(map[string]InitFunc) +} + func Register(name string, initFunc InitFunc) error { if _, exists := drivers[name]; exists { return fmt.Errorf("Name already registered %s", name) From 65b7bbeca7bc0e9dc84f7ca7d254712ba8e74cf8 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 7 Nov 2013 22:46:44 +0100 Subject: [PATCH 200/301] archive.ApplyLayer() remove files recursively Some aufs metadata are directories, and whiteouts can be for directories, so use os.RemoveAll() for these. Upstream-commit: 484804abffda2692492e295d4691f90564d05eb2 Component: engine --- components/engine/archive/diff.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/archive/diff.go b/components/engine/archive/diff.go index 3154a44f36..38cbc3ed64 100644 --- a/components/engine/archive/diff.go +++ b/components/engine/archive/diff.go @@ -35,7 +35,7 @@ func ApplyLayer(dest string, layer Archive) error { return err } else if matched { log.Printf("Removing aufs metadata %s", fullPath) - _ = os.Remove(fullPath) + _ = os.RemoveAll(fullPath) } filename := filepath.Base(path) @@ -47,7 +47,7 @@ func ApplyLayer(dest string, layer Archive) error { _ = os.Remove(rmTargetPath) // Remove the whiteout itself log.Printf("Removing whiteout %s", fullPath) - _ = os.Remove(fullPath) + _ = os.RemoveAll(fullPath) } return nil }) From 7e3e167f73fd5223d13962932a30bdcaa98bbc27 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 7 Nov 2013 20:34:01 +0000 Subject: [PATCH 201/301] 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 +} From 5d51812837fddfcecfe10ecc85b42872a05ce805 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 7 Nov 2013 22:37:33 +0000 Subject: [PATCH 202/301] devmapper: port to the new driver API and reactivate driver Upstream-commit: 4a8a7d4edd53251c8de7ea087b092869e15621d5 Component: engine --- components/engine/devmapper/driver.go | 78 +++++++++------------- components/engine/devmapper/driver_test.go | 44 ++++-------- components/engine/devmapper/mount.go | 27 ++++++++ components/engine/runtime.go | 1 + 4 files changed, 71 insertions(+), 79 deletions(-) create mode 100644 components/engine/devmapper/mount.go diff --git a/components/engine/devmapper/driver.go b/components/engine/devmapper/driver.go index 1eaa0ab76f..7a594db623 100644 --- a/components/engine/devmapper/driver.go +++ b/components/engine/devmapper/driver.go @@ -15,15 +15,6 @@ func init() { // Placeholder interfaces, to be replaced // at integration. -type Image interface { - ID() string - Parent() (Image, error) - Path() string -} - -type Change interface { -} - // End of placeholder interfaces. type Driver struct { @@ -46,54 +37,45 @@ func (d *Driver) Cleanup() error { return d.DeviceSet.Shutdown() } -func (d *Driver) OnCreate(img Image, layer archive.Archive) error { - // Determine the source of the snapshot (parent id or init device) - var parentID string - if parent, err := img.Parent(); err != nil { - return err - } else if parent != nil { - parentID = parent.ID() - } - // Create the device for this image by snapshotting source - if err := d.DeviceSet.AddDevice(img.ID(), parentID); err != nil { - return err - } - // Mount the device in rootfs - mp := d.mountpoint(img.ID()) - if err := os.MkdirAll(mp, 0700); err != nil { - return err - } - if err := d.DeviceSet.MountDevice(img.ID(), mp, false); err != nil { - return err - } - // Apply the layer as a diff - if layer != nil { - if err := archive.ApplyLayer(mp, layer); err != nil { - return err - } - } - return nil +func (d *Driver) Create(id string, parent string) error { + return d.DeviceSet.AddDevice(id, parent) } -func (d *Driver) OnRemove(img Image) error { - id := img.ID() - if err := d.DeviceSet.RemoveDevice(id); err != nil { - return fmt.Errorf("Unable to remove device for %v: %v", id, err) - } - return nil +func (d *Driver) Remove(id string) error { + return d.DeviceSet.RemoveDevice(id) } -func (d *Driver) mountpoint(id string) string { - if d.home == "" { - return "" +func (d *Driver) Get(id string) (string, error) { + mp := path.Join(d.home, "mnt", id) + if err := d.mount(id, mp); err != nil { + return "", err } - return path.Join(d.home, "mnt", id) + return mp, nil } -func (d *Driver) Changes(img *Image, dest string) ([]Change, error) { +func (d *Driver) Diff(id string) (archive.Archive, error) { return nil, fmt.Errorf("Not implemented") } -func (d *Driver) Layer(img *Image, dest string) (archive.Archive, error) { +func (d *Driver) DiffSize(id string) (int64, error) { + return -1, fmt.Errorf("Not implemented") +} + +func (d *Driver) Changes(id string) ([]graphdriver.Change, error) { return nil, fmt.Errorf("Not implemented") } + +func (d *Driver) mount(id, mp string) error { + // Create the target directories if they don't exist + if err := os.MkdirAll(mp, 0755); err != nil && !os.IsExist(err) { + return err + } + // If mountpoint is already mounted, do nothing + if mounted, err := Mounted(mp); err != nil { + return fmt.Errorf("Error checking mountpoint: %s", err) + } else if mounted { + return nil + } + // Mount the device + return d.DeviceSet.MountDevice(id, mp, false) +} diff --git a/components/engine/devmapper/driver_test.go b/components/engine/devmapper/driver_test.go index 5f80c1dab2..dadf2715ec 100644 --- a/components/engine/devmapper/driver_test.go +++ b/components/engine/devmapper/driver_test.go @@ -6,32 +6,6 @@ import ( "testing" ) -type TestImage struct { - id string - path string -} - -func (img *TestImage) ID() string { - return img.id -} - -func (img *TestImage) Path() string { - return img.path -} - -func (img *TestImage) Parent() (Image, error) { - return nil, nil -} - - - -func mkTestImage(t *testing.T) Image { - return &TestImage{ - path: mkTestDirectory(t), - id: "4242", - } -} - func mkTestDirectory(t *testing.T) string { dir, err := ioutil.TempDir("", "docker-test-devmapper-") if err != nil { @@ -43,19 +17,27 @@ func mkTestDirectory(t *testing.T) string { func TestInit(t *testing.T) { home := mkTestDirectory(t) defer os.RemoveAll(home) - plugin, err := Init(home) + driver, err := Init(home) if err != nil { t.Fatal(err) } defer func() { return - if err := plugin.Cleanup(); err != nil { + if err := driver.Cleanup(); err != nil { t.Fatal(err) } }() - img := mkTestImage(t) - defer os.RemoveAll(img.(*TestImage).path) - if err := plugin.OnCreate(img, nil); err != nil { + id := "foo" + if err := driver.Create(id, ""); err != nil { t.Fatal(err) } + dir, err := driver.Get(id) + if err != nil { + t.Fatal(err) + } + if st, err := os.Stat(dir); err != nil { + t.Fatal(err) + } else if !st.IsDir() { + t.Fatalf("Get(%V) did not return a directory", id) + } } diff --git a/components/engine/devmapper/mount.go b/components/engine/devmapper/mount.go new file mode 100644 index 0000000000..e3a303e507 --- /dev/null +++ b/components/engine/devmapper/mount.go @@ -0,0 +1,27 @@ +package devmapper + +import ( + "os" + "path/filepath" + "syscall" +) + +// FIXME: this is copy-pasted from the aufs driver. +// It should be moved into the core. + +func Mounted(mountpoint string) (bool, error) { + mntpoint, err := os.Stat(mountpoint) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, err + } + parent, err := os.Stat(filepath.Join(mountpoint, "..")) + if err != nil { + return false, err + } + mntpointSt := mntpoint.Sys().(*syscall.Stat_t) + parentSt := parent.Sys().(*syscall.Stat_t) + return mntpointSt.Dev != parentSt.Dev, nil +} diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 4d46d8fbab..80adf8330f 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -8,6 +8,7 @@ import ( "github.com/dotcloud/docker/gograph" "github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/graphdriver" + _ "github.com/dotcloud/docker/devmapper" "io" "io/ioutil" "log" From 783f80151249b24ecbfbc9c0ec6e2caed0b3a734 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 7 Nov 2013 23:58:03 +0000 Subject: [PATCH 203/301] gofmt Upstream-commit: f1127b9308fbb4d4e21c3c5567ace9fdda82559d Component: engine --- components/engine/archive/diff.go | 3 +-- components/engine/container.go | 6 ++--- components/engine/devmapper/deviceset.go | 34 ++++++++++++------------ components/engine/graph.go | 3 +-- components/engine/runtime.go | 9 +++---- components/engine/server.go | 2 +- components/engine/utils.go | 2 +- 7 files changed, 28 insertions(+), 31 deletions(-) diff --git a/components/engine/archive/diff.go b/components/engine/archive/diff.go index 3154a44f36..20bd332a9b 100644 --- a/components/engine/archive/diff.go +++ b/components/engine/archive/diff.go @@ -1,9 +1,9 @@ package archive import ( - "path/filepath" "log" "os" + "path/filepath" "strings" ) @@ -56,4 +56,3 @@ func ApplyLayer(dest string, layer Archive) error { } return nil } - diff --git a/components/engine/container.go b/components/engine/container.go index b45192e305..a39b91c10d 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -7,9 +7,9 @@ import ( "flag" "fmt" "github.com/dotcloud/docker/archive" + "github.com/dotcloud/docker/graphdriver" // FIXME: graphdriver.Change is a placeholder for archive.Change "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" @@ -26,8 +26,8 @@ import ( ) type Container struct { - root string // Path to the "home" of the container, including metadata. - rootfs string // Path to the root filesystem of the container. + root string // Path to the "home" of the container, including metadata. + rootfs string // Path to the root filesystem of the container. ID string diff --git a/components/engine/devmapper/deviceset.go b/components/engine/devmapper/deviceset.go index 0717722e87..a80f2a93e7 100644 --- a/components/engine/devmapper/deviceset.go +++ b/components/engine/devmapper/deviceset.go @@ -23,11 +23,11 @@ var ( ) type DevInfo struct { - Hash string `json:"-"` - DeviceId int `json:"device_id"` - Size uint64 `json:"size"` - TransactionId uint64 `json:"transaction_id"` - Initialized bool `json:"initialized"` + Hash string `json:"-"` + DeviceId int `json:"device_id"` + Size uint64 `json:"size"` + TransactionId uint64 `json:"transaction_id"` + Initialized bool `json:"initialized"` devices *DeviceSet `json:"-"` } @@ -48,16 +48,16 @@ type DeviceSet struct { } type DiskUsage struct { - Used uint64 + Used uint64 Total uint64 } type Status struct { - PoolName string - DataLoopback string + PoolName string + DataLoopback string MetadataLoopback string - Data DiskUsage - Metadata DiskUsage + Data DiskUsage + Metadata DiskUsage } func getDevName(name string) string { @@ -349,11 +349,11 @@ func (devices *DeviceSet) log(level int, file string, line int, dmError int, mes utils.Debugf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message) } -func major (device uint64) uint64 { - return (device >> 8) & 0xfff +func major(device uint64) uint64 { + return (device >> 8) & 0xfff } -func minor (device uint64) uint64 { +func minor(device uint64) uint64 { return (device & 0xff) | ((device >> 12) & 0xfff00) } @@ -798,22 +798,22 @@ func (devices *DeviceSet) Status() *Status { devices.Lock() defer devices.Unlock() - status := &Status {} + status := &Status{} if err := devices.ensureInit(); err != nil { return status } status.PoolName = devices.getPoolName() - status.DataLoopback = path.Join( devices.loopbackDir(), "data") - status.MetadataLoopback = path.Join( devices.loopbackDir(), "metadata") + status.DataLoopback = path.Join(devices.loopbackDir(), "data") + status.MetadataLoopback = path.Join(devices.loopbackDir(), "metadata") _, totalSizeInSectors, _, params, err := getStatus(devices.getPoolName()) if err == nil { var transactionId, dataUsed, dataTotal, metadataUsed, metadataTotal uint64 if _, err := fmt.Sscanf(params, "%d %d/%d %d/%d", &transactionId, &metadataUsed, &metadataTotal, &dataUsed, &dataTotal); err == nil { // Convert from blocks to bytes - blockSizeInSectors := totalSizeInSectors / dataTotal; + blockSizeInSectors := totalSizeInSectors / dataTotal status.Data.Used = dataUsed * blockSizeInSectors * 512 status.Data.Total = dataTotal * blockSizeInSectors * 512 diff --git a/components/engine/graph.go b/components/engine/graph.go index 21647bba82..54618ead5a 100644 --- a/components/engine/graph.go +++ b/components/engine/graph.go @@ -3,8 +3,8 @@ package docker import ( "fmt" "github.com/dotcloud/docker/archive" - "github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/graphdriver" + "github.com/dotcloud/docker/utils" "io" "io/ioutil" "os" @@ -33,7 +33,6 @@ func NewGraph(root string, driver graphdriver.Driver) (*Graph, error) { return nil, err } - graph := &Graph{ Root: abspath, idIndex: utils.NewTruncIndex(), diff --git a/components/engine/runtime.go b/components/engine/runtime.go index c5b9c238f5..60c2bb7cc8 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -5,10 +5,10 @@ import ( "container/list" "database/sql" "fmt" - "github.com/dotcloud/docker/gograph" - "github.com/dotcloud/docker/utils" - "github.com/dotcloud/docker/graphdriver" _ "github.com/dotcloud/docker/devmapper" + "github.com/dotcloud/docker/gograph" + "github.com/dotcloud/docker/graphdriver" + "github.com/dotcloud/docker/utils" "io" "io/ioutil" "log" @@ -682,7 +682,6 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { return nil, err } - runtime := &Runtime{ repository: runtimeRepo, containers: list.New(), @@ -694,7 +693,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { volumes: volumes, config: config, containerGraph: graph, - driver: driver, + driver: driver, } if err := runtime.restore(); err != nil { diff --git a/components/engine/server.go b/components/engine/server.go index 43a2a238e3..08ce22afcf 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -9,9 +9,9 @@ import ( "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/gograph" + "github.com/dotcloud/docker/graphdriver" // FIXME: graphdriver.Change is a placeholder for archive.Change "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" diff --git a/components/engine/utils.go b/components/engine/utils.go index 693bae4604..643187b847 100644 --- a/components/engine/utils.go +++ b/components/engine/utils.go @@ -25,10 +25,10 @@ import ( "fmt" "github.com/dotcloud/docker/namesgenerator" "github.com/dotcloud/docker/utils" - "strconv" "io" "io/ioutil" "os" + "strconv" "strings" "syscall" ) From 914824bce7e476cd7ae99521261ecf62a4a8d087 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 8 Nov 2013 00:35:26 +0000 Subject: [PATCH 204/301] Move Change to the archive package, and fix a leftover merge in Container.Inject() Upstream-commit: 9ae4bcaaf8aa01da340341e47f99f83a59ce91dc Component: engine --- components/engine/{ => archive}/changes.go | 2 +- components/engine/container.go | 5 ++--- components/engine/devmapper/driver.go | 2 +- components/engine/graphdriver/driver.go | 8 ++------ components/engine/runtime.go | 3 ++- components/engine/server.go | 3 +-- components/engine/utils.go | 7 +++++++ 7 files changed, 16 insertions(+), 14 deletions(-) rename components/engine/{ => archive}/changes.go (99%) diff --git a/components/engine/changes.go b/components/engine/archive/changes.go similarity index 99% rename from components/engine/changes.go rename to components/engine/archive/changes.go index 7a88ef0d01..504f4a2cf2 100644 --- a/components/engine/changes.go +++ b/components/engine/archive/changes.go @@ -1,4 +1,4 @@ -package docker +package archive import ( "fmt" diff --git a/components/engine/container.go b/components/engine/container.go index a39b91c10d..7b79cabbfb 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -7,7 +7,6 @@ import ( "flag" "fmt" "github.com/dotcloud/docker/archive" - "github.com/dotcloud/docker/graphdriver" // FIXME: graphdriver.Change is a placeholder for archive.Change "github.com/dotcloud/docker/term" "github.com/dotcloud/docker/utils" "github.com/kr/pty" @@ -397,7 +396,7 @@ func (container *Container) Inject(file io.Reader, pth string) error { } // Return error if path exists - if _, err := os.Stat(path.Join(container.rwPath(), pth)); err == nil { + if _, err := os.Stat(path.Join(container.RootfsPath(), pth)); err == nil { // Since err is nil, the path could be stat'd and it exists return fmt.Errorf("%s exists", pth) } else if !os.IsNotExist(err) { @@ -1402,7 +1401,7 @@ func (container *Container) Mount() error { return container.runtime.Mount(container) } -func (container *Container) Changes() ([]graphdriver.Change, error) { +func (container *Container) Changes() ([]archive.Change, error) { return container.runtime.Changes(container) } diff --git a/components/engine/devmapper/driver.go b/components/engine/devmapper/driver.go index 7a594db623..909f3cf974 100644 --- a/components/engine/devmapper/driver.go +++ b/components/engine/devmapper/driver.go @@ -61,7 +61,7 @@ func (d *Driver) DiffSize(id string) (int64, error) { return -1, fmt.Errorf("Not implemented") } -func (d *Driver) Changes(id string) ([]graphdriver.Change, error) { +func (d *Driver) Changes(id string) ([]archive.Change, error) { return nil, fmt.Errorf("Not implemented") } diff --git a/components/engine/graphdriver/driver.go b/components/engine/graphdriver/driver.go index 5a83259324..9165065f27 100644 --- a/components/engine/graphdriver/driver.go +++ b/components/engine/graphdriver/driver.go @@ -5,12 +5,8 @@ import ( "github.com/dotcloud/docker/archive" ) -type InitFunc func(root string) (Driver, error) -// FIXME: this is a temporary placeholder for archive.Change -// (to be merged from master) -type Change interface { -} +type InitFunc func(root string) (Driver, error) type Driver interface { Create(id, parent string) error @@ -20,7 +16,7 @@ type Driver interface { Diff(id string) (archive.Archive, error) DiffSize(id string) (bytes int64, err error) - Changes(id string) ([]Change, error) + Changes(id string) ([]archive.Change, error) Cleanup() error } diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 60c2bb7cc8..75bae02934 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -5,6 +5,7 @@ import ( "container/list" "database/sql" "fmt" + "github.com/dotcloud/docker/archive" _ "github.com/dotcloud/docker/devmapper" "github.com/dotcloud/docker/gograph" "github.com/dotcloud/docker/graphdriver" @@ -728,7 +729,7 @@ func (runtime *Runtime) Unmount(container *Container) error { return nil } -func (runtime *Runtime) Changes(container *Container) ([]graphdriver.Change, error) { +func (runtime *Runtime) Changes(container *Container) ([]archive.Change, error) { return runtime.driver.Changes(container.ID) } diff --git a/components/engine/server.go b/components/engine/server.go index 08ce22afcf..6a94c8ae2c 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -9,7 +9,6 @@ import ( "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/gograph" - "github.com/dotcloud/docker/graphdriver" // FIXME: graphdriver.Change is a placeholder for archive.Change "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/utils" "io" @@ -449,7 +448,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) ([]graphdriver.Change, error) { +func (srv *Server) ContainerChanges(name string) ([]archive.Change, error) { if container := srv.runtime.Get(name); container != nil { return container.Changes() } diff --git a/components/engine/utils.go b/components/engine/utils.go index 643187b847..a5df3d59a3 100644 --- a/components/engine/utils.go +++ b/components/engine/utils.go @@ -23,6 +23,7 @@ btrfs_reflink(int fd_out, int fd_in) import "C" import ( "fmt" + "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/namesgenerator" "github.com/dotcloud/docker/utils" "io" @@ -33,6 +34,12 @@ import ( "syscall" ) + +type Change struct { + archive.Change +} + + // Compare two Config struct. Do not compare the "Image" nor "Hostname" fields // If OpenStdin is set, then it differs func CompareConfig(a, b *Config) bool { From 7362e77db493ec894c6d9d63096c04b068d7d982 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 8 Nov 2013 00:35:26 +0000 Subject: [PATCH 205/301] Move Change to the archive package Upstream-commit: 01990b65a3cf3da5e04aa6612a17ead2a6cac2c9 Component: engine --- components/engine/utils.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/engine/utils.go b/components/engine/utils.go index a5df3d59a3..433d00ad65 100644 --- a/components/engine/utils.go +++ b/components/engine/utils.go @@ -34,12 +34,10 @@ import ( "syscall" ) - type Change struct { archive.Change } - // Compare two Config struct. Do not compare the "Image" nor "Hostname" fields // If OpenStdin is set, then it differs func CompareConfig(a, b *Config) bool { From 681ed9aee047436c62d94f7852f556e73d5c3bbe Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 7 Nov 2013 04:33:31 -0800 Subject: [PATCH 206/301] Update aufs driver to new interface spec Upstream-commit: b8b509e1c8c9b9def04850eaad8369b9ec6d9e0a Component: engine --- components/engine/aufs/aufs.go | 244 +++++++++++++++----- components/engine/aufs/aufs_test.go | 332 ++++++++++++++++++++++++++++ components/engine/aufs/dirs.go | 47 ++++ components/engine/aufs/mount.go | 1 + 4 files changed, 565 insertions(+), 59 deletions(-) create mode 100644 components/engine/aufs/aufs_test.go create mode 100644 components/engine/aufs/dirs.go diff --git a/components/engine/aufs/aufs.go b/components/engine/aufs/aufs.go index 4c14947a95..8258312de4 100644 --- a/components/engine/aufs/aufs.go +++ b/components/engine/aufs/aufs.go @@ -1,9 +1,30 @@ +/* + +aufs driver directory structure + +. +├── layers // Metadata of layers +│   ├── 1 +│   ├── 2 +│   └── 3 +├── diffs // Content of the layer +│   ├── 1 // Contains layers that need to be mounted for the id +│   ├── 2 +│   └── 3 +└── mnt // Mount points for the rw layers to be mounted + ├── 1 + ├── 2 + └── 3 + +*/ + package aufs import ( "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/graphdriver" + "github.com/dotcloud/docker/utils" "log" "os" "os/exec" @@ -25,105 +46,210 @@ func Init(root string) (graphdriver.Driver, error) { if err := exec.Command("modprobe", "aufs").Run(); err != nil { return nil, err } + paths := []string{ + "mnt", + "diff", + "layers", + } + + // Create the root aufs driver dir and return + // if it already exists + // If not populate the dir structure + aufsPath := path.Join(root, "aufs") + if err := os.Mkdir(aufsPath, 0755); err != nil { + if os.IsExist(err) { + return &AufsDriver{root}, nil + } + return nil, err + } + + for _, p := range paths { + if err := os.MkdirAll(path.Join(aufsPath, p), 0755); err != nil { + return nil, err + } + } return &AufsDriver{root}, nil } -func (a *AufsDriver) OnCreate(dir graphdriver.Dir, layer archive.Archive) error { - tmp := path.Join(os.TempDir(), dir.ID()) - if err := os.MkdirAll(tmp, 0755); err != nil { +func (a *AufsDriver) rootPath() string { + return path.Join(a.root, "aufs") +} + +func (a *AufsDriver) String() string { + return "aufs" +} + +// Three folders are created for each id +// mnt, layers, and diff +func (a *AufsDriver) Create(id, parent string) error { + if err := a.createDirsFor(id); err != nil { return err } - defer os.RemoveAll(tmp) - - layerRoot := path.Join(a.root, dir.ID()) - if err := os.MkdirAll(layerRoot, 0755); err != nil { + // Write the layers metadata + f, err := os.Create(path.Join(a.rootPath(), "layers", id)) + if err != nil { return err } + defer f.Close() - if layer != nil { - if err := archive.Untar(layer, tmp); err != nil { + if parent != "" { + ids, err := getParentIds(a.rootPath(), parent) + if err != nil { return err } - } - if err := os.Rename(tmp, layerRoot); err != nil { - return err + fmt.Fprintln(f, parent) + for _, i := range ids { + fmt.Fprintln(f, i) + } } return nil } -func (a *AufsDriver) OnRemove(dir graphdriver.Dir) error { - tmp := path.Join(os.TempDir(), dir.ID()) - - if err := os.MkdirAll(tmp, 0755); err != nil { - return err +func (a *AufsDriver) createDirsFor(id string) error { + paths := []string{ + "mnt", + "diff", } - if err := os.Rename(path.Join(a.root, dir.ID()), tmp); err != nil { - return err + for _, p := range paths { + dir := path.Join(a.rootPath(), p, id) + if err := os.MkdirAll(dir, 0755); err != nil { + return err + } } - return os.RemoveAll(tmp) + return nil } -func (a *AufsDriver) OnMount(dir graphdriver.Dir, dest string) error { - layers, err := a.getLayers(dir) +// Unmount and remove the dir information +func (a *AufsDriver) Remove(id string) error { + // Make sure the dir is umounted first + mntPoint := path.Join(a.rootPath(), "mnt", id) + if err := a.unmount(mntPoint); err != nil { + return err + } + tmpDirs := []string{ + "mnt", + "diff", + } + + // Remove the dirs atomically + for _, p := range tmpDirs { + tmp := path.Join(os.TempDir(), p, id) + if err := os.MkdirAll(tmp, 0755); err != nil { + return err + } + realPath := path.Join(a.rootPath(), p, id) + if err := os.Rename(realPath, tmp); err != nil { + return err + } + defer os.RemoveAll(tmp) + } + + // Remove the layers file for the id + return os.Remove(path.Join(a.rootPath(), "layers", id)) +} + +// Return the rootfs path for the id +// This will mount the dir at it's given path +func (a *AufsDriver) Get(id string) (string, error) { + ids, err := getParentIds(a.rootPath(), id) + if err != nil { + if !os.IsNotExist(err) { + return "", err + } + ids = []string{} + } + + // If a dir does not have a parent ( no layers )do not try to mount + // just return the diff path to the data + out := path.Join(a.rootPath(), "diff", id) + if len(ids) > 0 { + out = path.Join(a.rootPath(), "mnt", id) + if err := a.mount(id); err != nil { + return "", err + } + } + return out, nil +} + +// Returns an archive of the contents for the id +func (a *AufsDriver) Diff(id string) (archive.Archive, error) { + p, err := a.Get(id) + if err != nil { + return nil, err + } + return archive.Tar(p, archive.Uncompressed) +} + +// Returns the size of the contents for the id +func (a *AufsDriver) DiffSize(id string) (int64, error) { + p, err := a.Get(id) + if err != nil { + return -1, err + } + return utils.TreeSize(p) +} + +func (a *AufsDriver) Changes(id string) ([]archive.Change, error) { + return nil, nil +} + +func (a *AufsDriver) mount(id string) error { + // If the id is mounted or we get an error return + if mounted, err := a.mounted(id); err != nil || mounted { + return err + } + + parentIds, err := getParentIds(a.rootPath(), id) if err != nil { return err } - - target := path.Join(dest, "rootfs") - rw := path.Join(dest, "rw") - - // Create the target directories if they don't exist - if err := os.Mkdir(target, 0755); err != nil && !os.IsExist(err) { - return err + if len(parentIds) == 0 { + return fmt.Errorf("Dir %s does not have any parent layers", id) } - if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { - return err + var ( + target = path.Join(a.rootPath(), "mnt", id) + rw = path.Join(a.rootPath(), "diff", id) + layers = make([]string, len(parentIds)) + ) + + // Get the diff paths for all the parent ids + for i, p := range parentIds { + layers[i] = path.Join(a.rootPath(), "diff", p) } + if err := a.aufsMount(layers, rw, target); err != nil { return err } return nil } -func (a *AufsDriver) OnUnmount(dest string) error { - target := path.Join(dest, "rootfs") - if _, err := os.Stat(target); err != nil { - if os.IsNotExist(err) { - return nil - } +func (a *AufsDriver) unmount(id string) error { + if mounted, err := a.mounted(id); err != nil || !mounted { return err } + target := path.Join(a.rootPath(), "mnt", id) return Unmount(target) } -func (a *AufsDriver) Mounted(dest string) (bool, error) { - return Mounted(path.Join(dest, "rootfs")) -} - -func (a *AufsDriver) Layer(dir graphdriver.Dir, dest string) (archive.Archive, error) { - return nil, fmt.Errorf("not implemented") +func (a *AufsDriver) mounted(id string) (bool, error) { + target := path.Join(a.rootPath(), "mnt", id) + return Mounted(target) } +// During cleanup aufs needs to unmount all mountpoints func (a *AufsDriver) Cleanup() error { - return nil -} - -func (a *AufsDriver) getLayers(dir graphdriver.Dir) ([]string, error) { - var ( - err error - layers = []string{} - current = dir - ) - - for current != nil { - layers = append(layers, current.Path()) - if current, err = current.Parent(); err != nil { - return nil, err + ids, err := loadIds(path.Join(a.rootPath(), "layers")) + if err != nil { + return err + } + for _, id := range ids { + if err := a.unmount(id); err != nil { + return err } } - return layers, nil + return nil } func (a *AufsDriver) aufsMount(ro []string, rw, target string) error { @@ -142,7 +268,7 @@ func (a *AufsDriver) aufsMount(ro []string, rw, target string) error { } log.Printf("...module loaded.") if err := mount("none", target, "aufs", 0, branches); err != nil { - return fmt.Errorf("Unable to mount using aufs") + return fmt.Errorf("Unable to mount using aufs %s", err) } } return nil diff --git a/components/engine/aufs/aufs_test.go b/components/engine/aufs/aufs_test.go new file mode 100644 index 0000000000..d3de559fb1 --- /dev/null +++ b/components/engine/aufs/aufs_test.go @@ -0,0 +1,332 @@ +package aufs + +import ( + "os" + "path" + "testing" +) + +var ( + tmp = path.Join(os.TempDir(), "aufs-tests") +) + +func newDriver(t *testing.T) *AufsDriver { + if err := os.MkdirAll(tmp, 0755); err != nil { + t.Fatal(err) + } + + d, err := Init(tmp) + if err != nil { + t.Fatal(err) + } + return d.(*AufsDriver) +} + +func TestNewAufsDriver(t *testing.T) { + if err := os.MkdirAll(tmp, 0755); err != nil { + t.Fatal(err) + } + + d, err := Init(tmp) + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmp) + if d == nil { + t.Fatalf("Driver should not be nil") + } +} + +func TestAufsString(t *testing.T) { + d := newDriver(t) + defer os.RemoveAll(tmp) + + if d.String() != "aufs" { + t.Fatalf("Expected aufs got %s", d.String()) + } +} + +func TestCreateDirStructure(t *testing.T) { + newDriver(t) + defer os.RemoveAll(tmp) + + paths := []string{ + "mnt", + "layers", + "diff", + } + + for _, p := range paths { + if _, err := os.Stat(path.Join(tmp, "aufs", p)); err != nil { + t.Fatal(err) + } + } +} + +// We should be able to create two drivers with the same dir structure +func TestNewDriverFromExistingDir(t *testing.T) { + if err := os.MkdirAll(tmp, 0755); err != nil { + t.Fatal(err) + } + + if _, err := Init(tmp); err != nil { + t.Fatal(err) + } + if _, err := Init(tmp); err != nil { + t.Fatal(err) + } + os.RemoveAll(tmp) +} + +func TestCreateNewDir(t *testing.T) { + d := newDriver(t) + defer os.RemoveAll(tmp) + + if err := d.Create("1", ""); err != nil { + t.Fatal(err) + } +} + +func TestCreateNewDirStructure(t *testing.T) { + d := newDriver(t) + defer os.RemoveAll(tmp) + + if err := d.Create("1", ""); err != nil { + t.Fatal(err) + } + + paths := []string{ + "mnt", + "diff", + "layers", + } + + for _, p := range paths { + if _, err := os.Stat(path.Join(tmp, "aufs", p, "1")); err != nil { + t.Fatal(err) + } + } +} + +func TestRemoveImage(t *testing.T) { + d := newDriver(t) + defer os.RemoveAll(tmp) + + if err := d.Create("1", ""); err != nil { + t.Fatal(err) + } + + if err := d.Remove("1"); err != nil { + t.Fatal(err) + } + + paths := []string{ + "mnt", + "diff", + "layers", + } + + for _, p := range paths { + if _, err := os.Stat(path.Join(tmp, "aufs", p, "1")); err == nil { + t.Fatalf("Error should not be nil because dirs with id 1 should be delted: %s", p) + } + } +} + +func TestGetWithoutParent(t *testing.T) { + d := newDriver(t) + defer os.RemoveAll(tmp) + + if err := d.Create("1", ""); err != nil { + t.Fatal(err) + } + + diffPath, err := d.Get("1") + if err != nil { + t.Fatal(err) + } + expected := path.Join(tmp, "aufs", "diff", "1") + if diffPath != expected { + t.Fatalf("Expected path %s got %s", expected, diffPath) + } +} + +func TestCleanupWithNoDirs(t *testing.T) { + d := newDriver(t) + defer os.RemoveAll(tmp) + + if err := d.Cleanup(); err != nil { + t.Fatal(err) + } +} + +func TestCleanupWithDir(t *testing.T) { + d := newDriver(t) + defer os.RemoveAll(tmp) + + if err := d.Create("1", ""); err != nil { + t.Fatal(err) + } + + if err := d.Cleanup(); err != nil { + t.Fatal(err) + } +} + +func TestMountedFalseResponse(t *testing.T) { + d := newDriver(t) + defer os.RemoveAll(tmp) + + if err := d.Create("1", ""); err != nil { + t.Fatal(err) + } + + response, err := d.mounted("1") + if err != nil { + t.Fatal(err) + } + + if response != false { + t.Fatalf("Response if dir id 1 is mounted should be false") + } +} + +func TestMountedTrueReponse(t *testing.T) { + d := newDriver(t) + defer os.RemoveAll(tmp) + defer d.Cleanup() + + if err := d.Create("1", ""); err != nil { + t.Fatal(err) + } + if err := d.Create("2", "1"); err != nil { + t.Fatal(err) + } + + _, err := d.Get("2") + if err != nil { + t.Fatal(err) + } + + response, err := d.mounted("2") + if err != nil { + t.Fatal(err) + } + + if response != true { + t.Fatalf("Response if dir id 2 is mounted should be true") + } +} + +func TestMountWithParent(t *testing.T) { + d := newDriver(t) + defer os.RemoveAll(tmp) + + if err := d.Create("1", ""); err != nil { + t.Fatal(err) + } + if err := d.Create("2", "1"); err != nil { + t.Fatal(err) + } + + mntPath, err := d.Get("2") + if err != nil { + t.Fatal(err) + } + if mntPath == "" { + t.Fatal("mntPath should not be empty string") + } + + expected := path.Join(tmp, "aufs", "mnt", "2") + if mntPath != expected { + t.Fatalf("Expected %s got %s", expected, mntPath) + } + + if err := d.Cleanup(); err != nil { + t.Fatal(err) + } +} + +func TestCreateWithInvalidParent(t *testing.T) { + d := newDriver(t) + defer os.RemoveAll(tmp) + + if err := d.Create("1", "docker"); err == nil { + t.Fatalf("Error should not be nil with parent does not exist") + } +} + +func TestGetDiff(t *testing.T) { + d := newDriver(t) + defer os.RemoveAll(tmp) + + if err := d.Create("1", ""); err != nil { + t.Fatal(err) + } + + diffPath, err := d.Get("1") + if err != nil { + t.Fatal(err) + } + + // Add a file to the diff path with a fixed size + size := int64(1024) + + f, err := os.Create(path.Join(diffPath, "test_file")) + if err != nil { + t.Fatal(err) + } + if err := f.Truncate(size); err != nil { + t.Fatal(err) + } + f.Close() + + a, err := d.Diff("1") + if err != nil { + t.Fatal(err) + } + if a == nil { + t.Fatalf("Archive should not be nil") + } +} + +/* FIXME: How to properly test this? +func TestDiffSize(t *testing.T) { + d := newDriver(t) + defer os.RemoveAll(tmp) + + if err := d.Create("1", ""); err != nil { + t.Fatal(err) + } + + diffPath, err := d.Get("1") + if err != nil { + t.Fatal(err) + } + + // Add a file to the diff path with a fixed size + size := int64(1024) + + f, err := os.Create(path.Join(diffPath, "test_file")) + if err != nil { + t.Fatal(err) + } + f.Truncate(size) + s, err := f.Stat() + if err != nil { + t.Fatal(err) + } + size = s.Size() + if err := f.Close(); err != nil { + t.Fatal(err) + } + + diffSize, err := d.DiffSize("1") + if err != nil { + t.Fatal(err) + } + if diffSize != size { + t.Fatalf("Expected size to be %d got %d", size, diffSize) + } +} +*/ diff --git a/components/engine/aufs/dirs.go b/components/engine/aufs/dirs.go new file mode 100644 index 0000000000..dc0007b905 --- /dev/null +++ b/components/engine/aufs/dirs.go @@ -0,0 +1,47 @@ +package aufs + +import ( + "bufio" + "io/ioutil" + "os" + "path" +) + +// Return all the directories +func loadIds(root string) ([]string, error) { + dirs, err := ioutil.ReadDir(root) + if err != nil { + return nil, err + } + out := []string{} + for _, d := range dirs { + if !d.IsDir() { + out = append(out, d.Name()) + } + } + return out, nil +} + +// Read the layers file for the current id and return all the +// layers represented by new lines in the file +// +// If there are no lines in the file then the id has no parent +// and an empty slice is returned. +func getParentIds(root, id string) ([]string, error) { + f, err := os.Open(path.Join(root, "layers", id)) + if err != nil { + return nil, err + } + defer f.Close() + + out := []string{} + s := bufio.NewScanner(f) + + for s.Scan() { + if err := s.Err(); err != nil { + return nil, err + } + out = append(out, s.Text()) + } + return out, nil +} diff --git a/components/engine/aufs/mount.go b/components/engine/aufs/mount.go index 15cb2da93f..4355d3b77d 100644 --- a/components/engine/aufs/mount.go +++ b/components/engine/aufs/mount.go @@ -49,5 +49,6 @@ func Mounted(mountpoint string) (bool, error) { } mntpointSt := mntpoint.Sys().(*syscall.Stat_t) parentSt := parent.Sys().(*syscall.Stat_t) + return mntpointSt.Dev != parentSt.Dev, nil } From fb05315d0344ddd15e0f2a1f9e0ffe00bebf6b76 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 7 Nov 2013 17:01:57 -0800 Subject: [PATCH 207/301] Fix test for aufs graph and add aufs driver import Upstream-commit: 12ffead71a06e5cf0ea4b1e6ce19231dceb265dc Component: engine --- components/engine/graph_test.go | 6 +++--- components/engine/runtime.go | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/components/engine/graph_test.go b/components/engine/graph_test.go index 7e306c815e..ab4168026b 100644 --- a/components/engine/graph_test.go +++ b/components/engine/graph_test.go @@ -146,12 +146,12 @@ func TestMount(t *testing.T) { if err := os.MkdirAll(rw, 0700); err != nil { t.Fatal(err) } - if err := graph.driver.Mount(image, tmp); err != nil { + if _, err := graph.driver.Get(image.ID); err != nil { t.Fatal(err) } // FIXME: test for mount contents defer func() { - if err := graph.driver.Unmount(tmp); err != nil { + if err := graph.driver.Cleanup(); err != nil { t.Error(err) } }() @@ -296,7 +296,7 @@ func tempGraph(t *testing.T) *Graph { if err != nil { t.Fatal(err) } - backend, err := aufs.New() + backend, err := aufs.Init(path.Join(tmp, "driver")) if err != nil { t.Fatal(err) } diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 75bae02934..b727cd65ae 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -6,6 +6,7 @@ import ( "database/sql" "fmt" "github.com/dotcloud/docker/archive" + _ "github.com/dotcloud/docker/aufs" _ "github.com/dotcloud/docker/devmapper" "github.com/dotcloud/docker/gograph" "github.com/dotcloud/docker/graphdriver" @@ -635,6 +636,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { if err != nil { return nil, err } + utils.Debugf("Using graph driver %s", driver) runtimeRepo := path.Join(config.Root, "containers") From a38c2eb35b0c274b1362a9f4a875f7cf92372350 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 8 Nov 2013 01:52:56 +0000 Subject: [PATCH 208/301] New 'dummy' driver uses plain directories and regular copy Upstream-commit: 81674fbbdf657de2c1d13b7c84a41c9edd3c8aa1 Component: engine --- components/engine/graphdriver/driver.go | 1 + components/engine/graphdriver/dummy/driver.go | 84 +++++++++++++++++++ components/engine/runtime.go | 1 + 3 files changed, 86 insertions(+) create mode 100644 components/engine/graphdriver/dummy/driver.go diff --git a/components/engine/graphdriver/driver.go b/components/engine/graphdriver/driver.go index 9165065f27..1f777ccde7 100644 --- a/components/engine/graphdriver/driver.go +++ b/components/engine/graphdriver/driver.go @@ -28,6 +28,7 @@ var ( priority = []string{ "aufs", "devicemapper", + "dummy", } ) diff --git a/components/engine/graphdriver/dummy/driver.go b/components/engine/graphdriver/dummy/driver.go new file mode 100644 index 0000000000..21db4f9859 --- /dev/null +++ b/components/engine/graphdriver/dummy/driver.go @@ -0,0 +1,84 @@ +package dummy + +import ( + "github.com/dotcloud/docker/archive" + "github.com/dotcloud/docker/graphdriver" + "os" + "path" + "fmt" +) + +func init() { + graphdriver.Register("dummy", Init) +} + +func Init(home string) (graphdriver.Driver, error) { + d := &Driver{ + home: home, + } + return d, nil +} + +type Driver struct { + home string +} + +func (d *Driver) Cleanup() error { + return nil +} + +func (d *Driver) Create(id string, parent string) error { + dir := d.dir(id) + if err := os.MkdirAll(path.Dir(dir), 0700); err != nil { + return err + } + if err := os.Mkdir(dir, 0700); err != nil { + return err + } + if parent == "" { + return nil + } + parentDir, err := d.Get(parent) + if err != nil { + return fmt.Errorf("%s: %s", parent, err) + } + if err := archive.CopyWithTar(parentDir, dir); err != nil { + return err + } + return nil +} + +func (d *Driver) dir(id string) string { + return path.Join(d.home, "dir", path.Base(id)) +} + + +func (d *Driver) Remove(id string) error { + if _, err := os.Stat(d.dir(id)); err != nil { + return err + } + return os.RemoveAll(d.dir(id)) +} + +func (d *Driver) Get(id string) (string, error) { + dir := d.dir(id) + if st, err := os.Stat(dir); err != nil { + return "", err + } else if !st.IsDir() { + return "", fmt.Errorf("%s: not a directory", dir) + } + return dir, nil +} + +func (d *Driver) Diff(id string) (archive.Archive, error) { + return nil, fmt.Errorf("Not implemented") +} + +func (d *Driver) DiffSize(id string) (int64, error) { + return -1, fmt.Errorf("Not implemented") +} + +func (d *Driver) Changes(id string) ([]archive.Change, error) { + return nil, fmt.Errorf("Not implemented") +} + diff --git a/components/engine/runtime.go b/components/engine/runtime.go index b727cd65ae..ce6d2ef3e3 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -10,6 +10,7 @@ import ( _ "github.com/dotcloud/docker/devmapper" "github.com/dotcloud/docker/gograph" "github.com/dotcloud/docker/graphdriver" + _ "github.com/dotcloud/docker/graphdriver/dummy" "github.com/dotcloud/docker/utils" "io" "io/ioutil" From 2835e8d724de2da38637d0c54d9aa73f0c81f25c Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 7 Nov 2013 17:57:14 -0800 Subject: [PATCH 209/301] Improve aufs detection by looking at proc fs Upstream-commit: 043a57617139dd4afefc2099e3bd85c8acaf0d68 Component: engine --- components/engine/aufs/aufs.go | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/components/engine/aufs/aufs.go b/components/engine/aufs/aufs.go index 8258312de4..761ac075af 100644 --- a/components/engine/aufs/aufs.go +++ b/components/engine/aufs/aufs.go @@ -21,6 +21,7 @@ aufs driver directory structure package aufs import ( + "bufio" "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/graphdriver" @@ -29,6 +30,7 @@ import ( "os" "os/exec" "path" + "strings" ) func init() { @@ -43,7 +45,7 @@ type AufsDriver struct { // An error is returned if AUFS is not supported. func Init(root string) (graphdriver.Driver, error) { // Try to load the aufs kernel module - if err := exec.Command("modprobe", "aufs").Run(); err != nil { + if err := supportsAufs(); err != nil { return nil, err } paths := []string{ @@ -71,6 +73,25 @@ func Init(root string) (graphdriver.Driver, error) { return &AufsDriver{root}, nil } +// Return a nil error if the kernel supports aufs +// We cannot modprobe because inside dind modprobe fails +// to run +func supportsAufs() error { + f, err := os.Open("/proc/filesystems") + if err != nil { + return err + } + defer f.Close() + + s := bufio.NewScanner(f) + for s.Scan() { + if strings.Contains(s.Text(), "aufs") { + return nil + } + } + return fmt.Errorf("AUFS was not found in /proc/filesystems") +} + func (a *AufsDriver) rootPath() string { return path.Join(a.root, "aufs") } From e1bd820a04a05b3f2b7c7b02c645c57d4ed053ce Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 8 Nov 2013 02:13:11 +0000 Subject: [PATCH 210/301] Fix a bug which caused Graph.Register to fail when Graph.Mktemp() returns an existing directory. Upstream-commit: 1764cf1990791dd15636f6e906960ad43d5689ad Component: engine --- components/engine/image.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/components/engine/image.go b/components/engine/image.go index e954e7b022..5f27a89719 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -62,12 +62,6 @@ func LoadImage(root string) (*Image, 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) - } else if !os.IsNotExist(err) { - return err - } // Store the layer layer := rootfs if err := os.MkdirAll(layer, 0755); err != nil { From 07ffba21a63ed9e80d2d622ab1354c4a2a5e7563 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 8 Nov 2013 02:48:52 +0000 Subject: [PATCH 211/301] Don't rely on drivers for diff and export Upstream-commit: 99210c9c6efbe10df922faa66fa3232ab898ef7b Component: engine --- components/engine/archive/changes.go | 22 ++++++++++++++++++++++ components/engine/container.go | 9 ++++++++- components/engine/runtime.go | 10 +++++++++- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/components/engine/archive/changes.go b/components/engine/archive/changes.go index 504f4a2cf2..a03172115f 100644 --- a/components/engine/archive/changes.go +++ b/components/engine/archive/changes.go @@ -206,3 +206,25 @@ func ChangesDirs(newDir, oldDir string) ([]Change, error) { } return changes, nil } + + +func ExportChanges(root, rw string) (Archive, error) { + changes, err := ChangesDirs(root, rw) + if err != nil { + return nil, err + } + files := make([]string, 0) + deletions := make([]string, 0) + for _, change := range changes { + if change.Kind == ChangeModify || change.Kind == ChangeAdd { + files = append(files, change.Path) + } + if change.Kind == ChangeDelete { + base := filepath.Base(change.Path) + dir := filepath.Dir(change.Path) + deletions = append(deletions, filepath.Join(dir, ".wh."+base)) + } + } + return TarFilter(root, Uncompressed, files, false, deletions) +} + diff --git a/components/engine/container.go b/components/engine/container.go index 7b79cabbfb..f388a18e2f 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -1363,10 +1363,17 @@ func (container *Container) Resize(h, w int) error { } func (container *Container) ExportRw() (archive.Archive, error) { + if err := container.EnsureMounted(); err != nil { + return nil, err + } if container.runtime == nil { return nil, fmt.Errorf("Can't load storage driver for unregistered container %s", container.ID) } - return container.runtime.driver.Diff(container.ID) + imgDir, err := container.runtime.driver.Get(container.Image) + if err != nil { + return nil, err + } + return archive.ExportChanges(container.RootfsPath(), imgDir) } func (container *Container) Export() (archive.Archive, error) { diff --git a/components/engine/runtime.go b/components/engine/runtime.go index ce6d2ef3e3..7bc5084dab 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -733,7 +733,15 @@ func (runtime *Runtime) Unmount(container *Container) error { } func (runtime *Runtime) Changes(container *Container) ([]archive.Change, error) { - return runtime.driver.Changes(container.ID) + cDir, err := runtime.driver.Get(container.ID) + if err != nil { + return nil, fmt.Errorf("Error getting container rootfs %s from driver %s: %s", container.ID, container.runtime.driver, err) + } + initDir, err := runtime.driver.Get(container.ID + "-init") + if err != nil { + return nil, fmt.Errorf("Error getting container init rootfs %s from driver %s: %s", container.ID, container.runtime.driver, err) + } + return archive.ChangesDirs(cDir, initDir) } func linkLxcStart(root string) error { From f7fbe4b3770e557c77fea3769a3ad4488057f6cb Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 8 Nov 2013 02:49:08 +0000 Subject: [PATCH 212/301] Don't hardcode dependency on aufs in the tests Upstream-commit: 1e5c61041f54de1146d8edb44e85b9419f4f6342 Component: engine --- components/engine/graph_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/graph_test.go b/components/engine/graph_test.go index ab4168026b..2775650c79 100644 --- a/components/engine/graph_test.go +++ b/components/engine/graph_test.go @@ -5,7 +5,7 @@ import ( "bytes" "errors" "github.com/dotcloud/docker/archive" - "github.com/dotcloud/docker/aufs" + "github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -296,7 +296,7 @@ func tempGraph(t *testing.T) *Graph { if err != nil { t.Fatal(err) } - backend, err := aufs.Init(path.Join(tmp, "driver")) + backend, err := graphdriver.New(tmp) if err != nil { t.Fatal(err) } From 54325d7ac7a73b466131d1a896ea7cfd02a0a92c Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 8 Nov 2013 02:49:32 +0000 Subject: [PATCH 213/301] Set DOCKER_DRIVER to override the choice of driver (aufs, devicemapper or dummy) Upstream-commit: 8c21d2acd33c2bbaba224e19fbef30a3ba8788df Component: engine --- components/engine/graphdriver/driver.go | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/components/engine/graphdriver/driver.go b/components/engine/graphdriver/driver.go index 1f777ccde7..bebbf02d3d 100644 --- a/components/engine/graphdriver/driver.go +++ b/components/engine/graphdriver/driver.go @@ -3,6 +3,7 @@ package graphdriver import ( "fmt" "github.com/dotcloud/docker/archive" + "os" ) @@ -45,18 +46,27 @@ func Register(name string, initFunc InitFunc) error { return nil } +func getDriver(name, home string) (Driver, error) { + if initFunc, exists := drivers[name]; exists { + return initFunc(home) + } + return nil, fmt.Errorf("No such driver: %s", name) +} + func New(root string) (Driver, error) { var driver Driver var lastError error + // Use environment variable DOCKER_DRIVER to force a choice of driver + if name := os.Getenv("DOCKER_DRIVER"); name != "" { + return getDriver(name, root) + } // Check for priority drivers first for _, name := range priority { - if initFunc, exists := drivers[name]; exists { - driver, lastError = initFunc(root) - if lastError != nil { - continue - } - return driver, nil + driver, lastError = getDriver(name, root) + if lastError != nil { + continue } + return driver, nil } // Check all registered drivers if no priority driver is found From 491fee7d48ee51da03bd5467c0b9441b1d9fd4c6 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 7 Nov 2013 19:02:15 -0800 Subject: [PATCH 214/301] Fix aufs error at startup Add String methods to other drivers Upstream-commit: 51c93c0f3318efa95a02d25677a1a4837f1af9f6 Component: engine --- components/engine/aufs/aufs.go | 2 +- components/engine/devmapper/driver.go | 4 ++++ components/engine/graphdriver/driver.go | 3 ++- components/engine/graphdriver/dummy/driver.go | 10 ++++++---- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/components/engine/aufs/aufs.go b/components/engine/aufs/aufs.go index 761ac075af..f4cb52dd9b 100644 --- a/components/engine/aufs/aufs.go +++ b/components/engine/aufs/aufs.go @@ -58,7 +58,7 @@ func Init(root string) (graphdriver.Driver, error) { // if it already exists // If not populate the dir structure aufsPath := path.Join(root, "aufs") - if err := os.Mkdir(aufsPath, 0755); err != nil { + if err := os.MkdirAll(aufsPath, 0755); err != nil { if os.IsExist(err) { return &AufsDriver{root}, nil } diff --git a/components/engine/devmapper/driver.go b/components/engine/devmapper/driver.go index 909f3cf974..c4bc5dcff2 100644 --- a/components/engine/devmapper/driver.go +++ b/components/engine/devmapper/driver.go @@ -33,6 +33,10 @@ func Init(home string) (graphdriver.Driver, error) { return d, nil } +func (d *Driver) String() string { + return "devicemapper" +} + func (d *Driver) Cleanup() error { return d.DeviceSet.Shutdown() } diff --git a/components/engine/graphdriver/driver.go b/components/engine/graphdriver/driver.go index bebbf02d3d..3a44f86a09 100644 --- a/components/engine/graphdriver/driver.go +++ b/components/engine/graphdriver/driver.go @@ -3,10 +3,10 @@ package graphdriver import ( "fmt" "github.com/dotcloud/docker/archive" + "github.com/dotcloud/docker/utils" "os" ) - type InitFunc func(root string) (Driver, error) type Driver interface { @@ -64,6 +64,7 @@ func New(root string) (Driver, error) { for _, name := range priority { driver, lastError = getDriver(name, root) if lastError != nil { + utils.Debugf("Error loading driver %s: %s", name, lastError) continue } return driver, nil diff --git a/components/engine/graphdriver/dummy/driver.go b/components/engine/graphdriver/dummy/driver.go index 21db4f9859..986c89ae24 100644 --- a/components/engine/graphdriver/dummy/driver.go +++ b/components/engine/graphdriver/dummy/driver.go @@ -1,11 +1,11 @@ package dummy import ( + "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/graphdriver" "os" "path" - "fmt" ) func init() { @@ -14,7 +14,7 @@ func init() { func Init(home string) (graphdriver.Driver, error) { d := &Driver{ - home: home, + home: home, } return d, nil } @@ -23,6 +23,10 @@ type Driver struct { home string } +func (d *Driver) String() string { + return "dummy" +} + func (d *Driver) Cleanup() error { return nil } @@ -52,7 +56,6 @@ func (d *Driver) dir(id string) string { return path.Join(d.home, "dir", path.Base(id)) } - func (d *Driver) Remove(id string) error { if _, err := os.Stat(d.dir(id)); err != nil { return err @@ -81,4 +84,3 @@ func (d *Driver) DiffSize(id string) (int64, error) { func (d *Driver) Changes(id string) ([]archive.Change, error) { return nil, fmt.Errorf("Not implemented") } - From d04279439333a8b7e2f19f69e6e91a4e909cf1c2 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 8 Nov 2013 10:17:51 -0800 Subject: [PATCH 215/301] Do not remove mountpoint on umount Upstream-commit: 52f31657cc03c0097e6fd0d5e1d2c10fb31d2b95 Component: engine --- components/engine/aufs/aufs.go | 3 +- components/engine/aufs/aufs_test.go | 43 ++++++++++++++++++++++++++++- components/engine/aufs/mount.go | 19 +------------ 3 files changed, 44 insertions(+), 21 deletions(-) diff --git a/components/engine/aufs/aufs.go b/components/engine/aufs/aufs.go index f4cb52dd9b..932e415e08 100644 --- a/components/engine/aufs/aufs.go +++ b/components/engine/aufs/aufs.go @@ -145,8 +145,7 @@ func (a *AufsDriver) createDirsFor(id string) error { // Unmount and remove the dir information func (a *AufsDriver) Remove(id string) error { // Make sure the dir is umounted first - mntPoint := path.Join(a.rootPath(), "mnt", id) - if err := a.unmount(mntPoint); err != nil { + if err := a.unmount(id); err != nil { return err } tmpDirs := []string{ diff --git a/components/engine/aufs/aufs_test.go b/components/engine/aufs/aufs_test.go index d3de559fb1..0afaf9b785 100644 --- a/components/engine/aufs/aufs_test.go +++ b/components/engine/aufs/aufs_test.go @@ -229,6 +229,12 @@ func TestMountWithParent(t *testing.T) { t.Fatal(err) } + defer func() { + if err := d.Cleanup(); err != nil { + t.Fatal(err) + } + }() + mntPath, err := d.Get("2") if err != nil { t.Fatal(err) @@ -241,8 +247,43 @@ func TestMountWithParent(t *testing.T) { if mntPath != expected { t.Fatalf("Expected %s got %s", expected, mntPath) } +} - if err := d.Cleanup(); err != nil { +func TestRemoveMountedDir(t *testing.T) { + d := newDriver(t) + defer os.RemoveAll(tmp) + + if err := d.Create("1", ""); err != nil { + t.Fatal(err) + } + if err := d.Create("2", "1"); err != nil { + t.Fatal(err) + } + + defer func() { + if err := d.Cleanup(); err != nil { + t.Fatal(err) + } + }() + + mntPath, err := d.Get("2") + if err != nil { + t.Fatal(err) + } + if mntPath == "" { + t.Fatal("mntPath should not be empty string") + } + + mounted, err := d.mounted("2") + if err != nil { + t.Fatal(err) + } + + if !mounted { + t.Fatalf("Dir id 2 should be mounted") + } + + if err := d.Remove("2"); err != nil { t.Fatal(err) } } diff --git a/components/engine/aufs/mount.go b/components/engine/aufs/mount.go index 4355d3b77d..6f3476f99c 100644 --- a/components/engine/aufs/mount.go +++ b/components/engine/aufs/mount.go @@ -1,13 +1,11 @@ package aufs import ( - "fmt" "github.com/dotcloud/docker/utils" "os" "os/exec" "path/filepath" "syscall" - "time" ) func Unmount(target string) error { @@ -17,22 +15,7 @@ func Unmount(target string) error { if err := syscall.Unmount(target, 0); err != nil { return err } - // Even though we just unmounted the filesystem, AUFS will prevent deleting the mntpoint - // for some time. We'll just keep retrying until it succeeds. - for retries := 0; retries < 1000; retries++ { - err := os.Remove(target) - if err == nil { - // rm mntpoint succeeded - return nil - } - if os.IsNotExist(err) { - // mntpoint doesn't exist anymore. Success. - return nil - } - // fmt.Printf("(%v) Remove %v returned: %v\n", retries, target, err) - time.Sleep(10 * time.Millisecond) - } - return fmt.Errorf("Umount: Failed to umount %v", target) + return nil } func Mounted(mountpoint string) (bool, error) { From bc930472df894f15fe689ac916642d2349dceef6 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 8 Nov 2013 11:10:33 -0800 Subject: [PATCH 216/301] Implement changes for aufs driver Upstream-commit: ed1884461331d7c2d6561be30b09da9df6612d39 Component: engine --- components/engine/aufs/aufs.go | 45 +++++++++++++++-------- components/engine/aufs/aufs_test.go | 55 +++++++++++++++++++++++++++++ components/engine/aufs/dirs.go | 7 ++-- 3 files changed, 89 insertions(+), 18 deletions(-) diff --git a/components/engine/aufs/aufs.go b/components/engine/aufs/aufs.go index 932e415e08..7d4aea8594 100644 --- a/components/engine/aufs/aufs.go +++ b/components/engine/aufs/aufs.go @@ -119,9 +119,13 @@ func (a *AufsDriver) Create(id, parent string) error { return err } - fmt.Fprintln(f, parent) + if _, err := fmt.Fprintln(f, parent); err != nil { + return err + } for _, i := range ids { - fmt.Fprintln(f, i) + if _, err := fmt.Fprintln(f, i); err != nil { + return err + } } } return nil @@ -212,7 +216,28 @@ func (a *AufsDriver) DiffSize(id string) (int64, error) { } func (a *AufsDriver) Changes(id string) ([]archive.Change, error) { - return nil, nil + layers, err := a.getParentLayerPaths(id) + if err != nil { + return nil, err + } + return archive.Changes(layers, path.Join(a.rootPath(), "diff", id)) +} + +func (a *AufsDriver) getParentLayerPaths(id string) ([]string, error) { + parentIds, err := getParentIds(a.rootPath(), id) + if err != nil { + return nil, err + } + if len(parentIds) == 0 { + return nil, fmt.Errorf("Dir %s does not have any parent layers", id) + } + layers := make([]string, len(parentIds)) + + // Get the diff paths for all the parent ids + for i, p := range parentIds { + layers[i] = path.Join(a.rootPath(), "diff", p) + } + return layers, nil } func (a *AufsDriver) mount(id string) error { @@ -221,22 +246,14 @@ func (a *AufsDriver) mount(id string) error { return err } - parentIds, err := getParentIds(a.rootPath(), id) - if err != nil { - return err - } - if len(parentIds) == 0 { - return fmt.Errorf("Dir %s does not have any parent layers", id) - } var ( target = path.Join(a.rootPath(), "mnt", id) rw = path.Join(a.rootPath(), "diff", id) - layers = make([]string, len(parentIds)) ) - // Get the diff paths for all the parent ids - for i, p := range parentIds { - layers[i] = path.Join(a.rootPath(), "diff", p) + layers, err := a.getParentLayerPaths(id) + if err != nil { + return err } if err := a.aufsMount(layers, rw, target); err != nil { diff --git a/components/engine/aufs/aufs_test.go b/components/engine/aufs/aufs_test.go index 0afaf9b785..7f49aec921 100644 --- a/components/engine/aufs/aufs_test.go +++ b/components/engine/aufs/aufs_test.go @@ -1,6 +1,7 @@ package aufs import ( + "github.com/dotcloud/docker/archive" "os" "path" "testing" @@ -331,6 +332,60 @@ func TestGetDiff(t *testing.T) { } } +func TestChanges(t *testing.T) { + d := newDriver(t) + defer os.RemoveAll(tmp) + + if err := d.Create("1", ""); err != nil { + t.Fatal(err) + } + if err := d.Create("2", "1"); err != nil { + t.Fatal(err) + } + + defer func() { + if err := d.Cleanup(); err != nil { + t.Fatal(err) + } + }() + + mntPoint, err := d.Get("2") + if err != nil { + t.Fatal(err) + } + + // Create a file to save in the mountpoint + f, err := os.Create(path.Join(mntPoint, "test.txt")) + if err != nil { + t.Fatal(err) + } + + if _, err := f.WriteString("testline"); err != nil { + t.Fatal(err) + } + if err := f.Close(); err != nil { + t.Fatal(err) + } + + changes, err := d.Changes("2") + if err != nil { + t.Fatal(err) + } + if len(changes) != 1 { + t.Fatalf("Dir 2 should have one change from parent got %d", len(changes)) + } + change := changes[0] + + expectedPath := "/test.txt" + if change.Path != expectedPath { + t.Fatalf("Expected path %s got %s", expectedPath, change.Path) + } + + if change.Kind != archive.ChangeAdd { + t.Fatalf("Change kind should be ChangeAdd got %s", change.Kind) + } +} + /* FIXME: How to properly test this? func TestDiffSize(t *testing.T) { d := newDriver(t) diff --git a/components/engine/aufs/dirs.go b/components/engine/aufs/dirs.go index dc0007b905..fb9b81edd2 100644 --- a/components/engine/aufs/dirs.go +++ b/components/engine/aufs/dirs.go @@ -38,10 +38,9 @@ func getParentIds(root, id string) ([]string, error) { s := bufio.NewScanner(f) for s.Scan() { - if err := s.Err(); err != nil { - return nil, err + if t := s.Text(); t != "" { + out = append(out, s.Text()) } - out = append(out, s.Text()) } - return out, nil + return out, s.Err() } From d9e6045c49729bd3a93a1b02439bdadfebf25ed6 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 8 Nov 2013 11:36:58 -0800 Subject: [PATCH 217/301] Name sure drivers are confined into their own dir Upstream-commit: 08a276986c363c5b2e7435fb59935485c53aae59 Component: engine --- components/engine/aufs/aufs.go | 7 +++---- components/engine/aufs/aufs_test.go | 12 ++++++------ components/engine/graphdriver/driver.go | 3 ++- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/components/engine/aufs/aufs.go b/components/engine/aufs/aufs.go index 7d4aea8594..22fa487f3a 100644 --- a/components/engine/aufs/aufs.go +++ b/components/engine/aufs/aufs.go @@ -57,8 +57,7 @@ func Init(root string) (graphdriver.Driver, error) { // Create the root aufs driver dir and return // if it already exists // If not populate the dir structure - aufsPath := path.Join(root, "aufs") - if err := os.MkdirAll(aufsPath, 0755); err != nil { + if err := os.MkdirAll(root, 0755); err != nil { if os.IsExist(err) { return &AufsDriver{root}, nil } @@ -66,7 +65,7 @@ func Init(root string) (graphdriver.Driver, error) { } for _, p := range paths { - if err := os.MkdirAll(path.Join(aufsPath, p), 0755); err != nil { + if err := os.MkdirAll(path.Join(root, p), 0755); err != nil { return nil, err } } @@ -93,7 +92,7 @@ func supportsAufs() error { } func (a *AufsDriver) rootPath() string { - return path.Join(a.root, "aufs") + return a.root } func (a *AufsDriver) String() string { diff --git a/components/engine/aufs/aufs_test.go b/components/engine/aufs/aufs_test.go index 7f49aec921..b71f17f009 100644 --- a/components/engine/aufs/aufs_test.go +++ b/components/engine/aufs/aufs_test.go @@ -8,7 +8,7 @@ import ( ) var ( - tmp = path.Join(os.TempDir(), "aufs-tests") + tmp = path.Join(os.TempDir(), "aufs-tests", "aufs") ) func newDriver(t *testing.T) *AufsDriver { @@ -58,7 +58,7 @@ func TestCreateDirStructure(t *testing.T) { } for _, p := range paths { - if _, err := os.Stat(path.Join(tmp, "aufs", p)); err != nil { + if _, err := os.Stat(path.Join(tmp, p)); err != nil { t.Fatal(err) } } @@ -103,7 +103,7 @@ func TestCreateNewDirStructure(t *testing.T) { } for _, p := range paths { - if _, err := os.Stat(path.Join(tmp, "aufs", p, "1")); err != nil { + if _, err := os.Stat(path.Join(tmp, p, "1")); err != nil { t.Fatal(err) } } @@ -128,7 +128,7 @@ func TestRemoveImage(t *testing.T) { } for _, p := range paths { - if _, err := os.Stat(path.Join(tmp, "aufs", p, "1")); err == nil { + if _, err := os.Stat(path.Join(tmp, p, "1")); err == nil { t.Fatalf("Error should not be nil because dirs with id 1 should be delted: %s", p) } } @@ -146,7 +146,7 @@ func TestGetWithoutParent(t *testing.T) { if err != nil { t.Fatal(err) } - expected := path.Join(tmp, "aufs", "diff", "1") + expected := path.Join(tmp, "diff", "1") if diffPath != expected { t.Fatalf("Expected path %s got %s", expected, diffPath) } @@ -244,7 +244,7 @@ func TestMountWithParent(t *testing.T) { t.Fatal("mntPath should not be empty string") } - expected := path.Join(tmp, "aufs", "mnt", "2") + expected := path.Join(tmp, "mnt", "2") if mntPath != expected { t.Fatalf("Expected %s got %s", expected, mntPath) } diff --git a/components/engine/graphdriver/driver.go b/components/engine/graphdriver/driver.go index 3a44f86a09..7149e22236 100644 --- a/components/engine/graphdriver/driver.go +++ b/components/engine/graphdriver/driver.go @@ -5,6 +5,7 @@ import ( "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/utils" "os" + "path" ) type InitFunc func(root string) (Driver, error) @@ -48,7 +49,7 @@ func Register(name string, initFunc InitFunc) error { func getDriver(name, home string) (Driver, error) { if initFunc, exists := drivers[name]; exists { - return initFunc(home) + return initFunc(path.Join(home, name)) } return nil, fmt.Errorf("No such driver: %s", name) } From 4c9c43a611a65e1a8e0d27e451d0d54ce72bd94f Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 8 Nov 2013 11:56:34 -0800 Subject: [PATCH 218/301] Use tmp dir in driver home Upstream-commit: 6669c86fdf1ae07b66a6e178e269d797f6397bca Component: engine --- components/engine/aufs/aufs.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/engine/aufs/aufs.go b/components/engine/aufs/aufs.go index 22fa487f3a..f47c65e661 100644 --- a/components/engine/aufs/aufs.go +++ b/components/engine/aufs/aufs.go @@ -158,7 +158,10 @@ func (a *AufsDriver) Remove(id string) error { // Remove the dirs atomically for _, p := range tmpDirs { - tmp := path.Join(os.TempDir(), p, id) + // We need to use a temp dir in the same dir as the driver so Rename + // does not fall back to the slow copy if /tmp and the driver dir + // are on different devices + tmp := path.Join(a.rootPath(), "tmp", p, id) if err := os.MkdirAll(tmp, 0755); err != nil { return err } From c94da34819fcf99408f3f77d37c3c2279cd70ab6 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 8 Nov 2013 12:06:15 -0800 Subject: [PATCH 219/301] Make sure dirs are created before injecting file Upstream-commit: 20f690f1762e369e9ad761e166f22ff57a17275a Component: engine --- components/engine/container.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/components/engine/container.go b/components/engine/container.go index f388a18e2f..6dee2335ff 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -396,7 +396,8 @@ func (container *Container) Inject(file io.Reader, pth string) error { } // Return error if path exists - if _, err := os.Stat(path.Join(container.RootfsPath(), pth)); err == nil { + destPath := path.Join(container.RootfsPath(), pth) + if _, err := os.Stat(destPath); err == nil { // Since err is nil, the path could be stat'd and it exists return fmt.Errorf("%s exists", pth) } else if !os.IsNotExist(err) { @@ -405,10 +406,18 @@ func (container *Container) Inject(file io.Reader, pth string) error { return err } - dest, err := os.Create(path.Join(container.RootfsPath(), pth)) + + // Make sure the directory exists + if err := os.MkdirAll(path.Join(container.RootfsPath(), path.Dir(pth)), 0755); err != nil { + return err + } + + dest, err := os.Create(destPath) if err != nil { return err } + defer dest.Close() + if _, err := io.Copy(dest, file); err != nil { return err } From 4806ef83ebe98fabf12bed5e89d906f26f28355a Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 8 Nov 2013 12:25:17 -0800 Subject: [PATCH 220/301] Add unit test for child changes diff in aufs Upstream-commit: f512049c8f9e64848cd8b1be794619f01c5227f2 Component: engine --- components/engine/aufs/aufs_test.go | 40 +++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/components/engine/aufs/aufs_test.go b/components/engine/aufs/aufs_test.go index b71f17f009..e4250d9e7b 100644 --- a/components/engine/aufs/aufs_test.go +++ b/components/engine/aufs/aufs_test.go @@ -384,6 +384,46 @@ func TestChanges(t *testing.T) { if change.Kind != archive.ChangeAdd { t.Fatalf("Change kind should be ChangeAdd got %s", change.Kind) } + + if err := d.Create("3", "2"); err != nil { + t.Fatal(err) + } + mntPoint, err = d.Get("3") + if err != nil { + t.Fatal(err) + } + + // Create a file to save in the mountpoint + f, err = os.Create(path.Join(mntPoint, "test2.txt")) + if err != nil { + t.Fatal(err) + } + + if _, err := f.WriteString("testline"); err != nil { + t.Fatal(err) + } + if err := f.Close(); err != nil { + t.Fatal(err) + } + + changes, err = d.Changes("3") + if err != nil { + t.Fatal(err) + } + + if len(changes) != 1 { + t.Fatalf("Dir 2 should have one change from parent got %d", len(changes)) + } + change = changes[0] + + expectedPath = "/test2.txt" + if change.Path != expectedPath { + t.Fatalf("Expected path %s got %s", expectedPath, change.Path) + } + + if change.Kind != archive.ChangeAdd { + t.Fatalf("Change kind should be ChangeAdd got %s", change.Kind) + } } /* FIXME: How to properly test this? From f4f11a5ecbc0e657da0887eb30461cc9cf566a6c Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 8 Nov 2013 14:54:20 -0800 Subject: [PATCH 221/301] Allow driver to provide changes if it impl the Changer interface Upstream-commit: 95147675870e9e84deb354f09f0f670a5cb2b1e1 Component: engine --- components/engine/container_test.go | 4 ++-- components/engine/devmapper/driver.go | 4 ---- components/engine/graphdriver/driver.go | 5 ++++- components/engine/graphdriver/dummy/driver.go | 4 ---- components/engine/runtime.go | 3 +++ 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/components/engine/container_test.go b/components/engine/container_test.go index cbabffc364..6f39f9be8e 100644 --- a/components/engine/container_test.go +++ b/components/engine/container_test.go @@ -170,11 +170,11 @@ func TestDiff(t *testing.T) { // Commit the container rwTar, err := container1.ExportRw() if err != nil { - t.Error(err) + t.Fatal(err) } img, err := runtime.graph.Create(rwTar, container1, "unit test commited image - diff", "", nil) if err != nil { - t.Error(err) + t.Fatal(err) } // Create a new container from the commited image diff --git a/components/engine/devmapper/driver.go b/components/engine/devmapper/driver.go index c4bc5dcff2..e095b33be5 100644 --- a/components/engine/devmapper/driver.go +++ b/components/engine/devmapper/driver.go @@ -65,10 +65,6 @@ func (d *Driver) DiffSize(id string) (int64, error) { return -1, fmt.Errorf("Not implemented") } -func (d *Driver) Changes(id string) ([]archive.Change, error) { - return nil, fmt.Errorf("Not implemented") -} - func (d *Driver) mount(id, mp string) error { // Create the target directories if they don't exist if err := os.MkdirAll(mp, 0755); err != nil && !os.IsExist(err) { diff --git a/components/engine/graphdriver/driver.go b/components/engine/graphdriver/driver.go index 7149e22236..4bb129dfb2 100644 --- a/components/engine/graphdriver/driver.go +++ b/components/engine/graphdriver/driver.go @@ -18,11 +18,14 @@ type Driver interface { Diff(id string) (archive.Archive, error) DiffSize(id string) (bytes int64, err error) - Changes(id string) ([]archive.Change, error) Cleanup() error } +type Changer interface { + Changes(id string) ([]archive.Change, error) +} + var ( // All registred drivers drivers map[string]InitFunc diff --git a/components/engine/graphdriver/dummy/driver.go b/components/engine/graphdriver/dummy/driver.go index 986c89ae24..d62f7de27f 100644 --- a/components/engine/graphdriver/dummy/driver.go +++ b/components/engine/graphdriver/dummy/driver.go @@ -80,7 +80,3 @@ func (d *Driver) Diff(id string) (archive.Archive, error) { func (d *Driver) DiffSize(id string) (int64, error) { return -1, fmt.Errorf("Not implemented") } - -func (d *Driver) Changes(id string) ([]archive.Change, error) { - return nil, fmt.Errorf("Not implemented") -} diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 7bc5084dab..bda59afce5 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -733,6 +733,9 @@ func (runtime *Runtime) Unmount(container *Container) error { } func (runtime *Runtime) Changes(container *Container) ([]archive.Change, error) { + if changer, ok := runtime.driver.(graphdriver.Changer); ok { + return changer.Changes(container.ID) + } cDir, err := runtime.driver.Get(container.ID) if err != nil { return nil, fmt.Errorf("Error getting container rootfs %s from driver %s: %s", container.ID, container.runtime.driver, err) From 28000adccb31b1442847f721d206075db5c1114c Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 8 Nov 2013 15:32:50 -0800 Subject: [PATCH 222/301] Allow drivers to export their own diff Upstream-commit: 1eb00e1d5b375cb79d492f1c5cd95d7317bc543c Component: engine --- components/engine/container.go | 6 +----- components/engine/graphdriver/dummy/driver.go | 6 +++++- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/components/engine/container.go b/components/engine/container.go index 6dee2335ff..32359b9cb7 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -1378,11 +1378,7 @@ func (container *Container) ExportRw() (archive.Archive, error) { if container.runtime == nil { return nil, fmt.Errorf("Can't load storage driver for unregistered container %s", container.ID) } - imgDir, err := container.runtime.driver.Get(container.Image) - if err != nil { - return nil, err - } - return archive.ExportChanges(container.RootfsPath(), imgDir) + return container.runtime.driver.Diff(container.ID) } func (container *Container) Export() (archive.Archive, error) { diff --git a/components/engine/graphdriver/dummy/driver.go b/components/engine/graphdriver/dummy/driver.go index d62f7de27f..41a3e62a1f 100644 --- a/components/engine/graphdriver/dummy/driver.go +++ b/components/engine/graphdriver/dummy/driver.go @@ -74,7 +74,11 @@ func (d *Driver) Get(id string) (string, error) { } func (d *Driver) Diff(id string) (archive.Archive, error) { - return nil, fmt.Errorf("Not implemented") + p, err := d.Get(id) + if err != nil { + return nil, err + } + return archive.Tar(p, archive.Uncompressed) } func (d *Driver) DiffSize(id string) (int64, error) { From 911620a74d4ead507287c853050aa897589193de Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 8 Nov 2013 16:11:57 -0800 Subject: [PATCH 223/301] Do not pass container information when creating a volume Upstream-commit: ddb27268c98941b9f8a131b7b5403eb4d44beebb Component: engine --- components/engine/container.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/components/engine/container.go b/components/engine/container.go index f388a18e2f..bac5bdd14a 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -804,7 +804,11 @@ func (container *Container) Start() (err error) { } // Otherwise create an directory in $ROOT/volumes/ and use that } else { - c, err := container.runtime.volumes.Create(nil, container, "", "", nil) + + // Do not pass a container as the parameter for the volume creation. + // The graph driver using the container's information ( Image ) to + // create the parent. + c, err := container.runtime.volumes.Create(nil, nil, "", "", nil) if err != nil { return err } From 7c76e102e94973b01ed7a86a4e7c8621e52efd5a Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 9 Nov 2013 00:53:58 +0000 Subject: [PATCH 224/301] Don't use drivers to store temporary image downloads Upstream-commit: 948bb29d27dc7e367cd27003c059d83301b15c4f Component: engine --- components/engine/graph.go | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/components/engine/graph.go b/components/engine/graph.go index 54618ead5a..e42c4ecbf6 100644 --- a/components/engine/graph.go +++ b/components/engine/graph.go @@ -185,18 +185,9 @@ func (graph *Graph) TempLayerArchive(id string, compression archive.Compression, // Mktemp creates a temporary sub-directory inside the graph's filesystem. func (graph *Graph) Mktemp(id string) (string, error) { - if id == "" { - id = GenerateID() - } - // 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("Driver %s couldn't get temporary directory %s: %s", graph.driver, id, err) + dir := path.Join(graph.Root, "_tmp", GenerateID()) + if err := os.MkdirAll(dir, 0700); err != nil { + return "", err } return dir, nil } From 8f13be5bb1a50f2156897b47df43037a234ebb77 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 24 Oct 2013 21:04:49 +0200 Subject: [PATCH 225/301] Remove devicemapper lazy initialization We now always initialize devicemapper on startup, so no need for the code that did lazy initialization, we just delete it. Upstream-commit: b5795749d1688f99df422dd7068b189e5b25e5e9 Component: engine --- components/engine/devmapper/deviceset.go | 61 ++++-------------------- components/engine/devmapper/driver.go | 11 +++-- 2 files changed, 14 insertions(+), 58 deletions(-) diff --git a/components/engine/devmapper/deviceset.go b/components/engine/devmapper/deviceset.go index a80f2a93e7..e7e57ab6a4 100644 --- a/components/engine/devmapper/deviceset.go +++ b/components/engine/devmapper/deviceset.go @@ -38,7 +38,6 @@ type MetaData struct { type DeviceSet struct { MetaData sync.Mutex - initialized bool root string devicePrefix string TransactionId uint64 @@ -450,11 +449,6 @@ func (devices *DeviceSet) AddDevice(hash, baseHash string) error { devices.Lock() defer devices.Unlock() - if err := devices.ensureInit(); err != nil { - utils.Debugf("Error init: %s\n", err) - return err - } - if devices.Devices[hash] != nil { return fmt.Errorf("hash %s already exists", hash) } @@ -522,11 +516,6 @@ func (devices *DeviceSet) RemoveDevice(hash string) error { devices.Lock() defer devices.Unlock() - if err := devices.ensureInit(); err != nil { - utils.Debugf("\n--->Err: %s\n", err) - return err - } - return devices.removeDevice(hash) } @@ -635,10 +624,6 @@ func (devices *DeviceSet) Shutdown() error { utils.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root) defer devices.Unlock() - if !devices.initialized { - return nil - } - for path, count := range devices.activeMounts { for i := count; i > 0; i-- { if err := syscall.Unmount(path, 0); err != nil { @@ -671,10 +656,6 @@ func (devices *DeviceSet) MountDevice(hash, path string, readOnly bool) error { devices.Lock() defer devices.Unlock() - if err := devices.ensureInit(); err != nil { - return fmt.Errorf("Error initializing devmapper: %s", err) - } - if err := devices.activateDeviceIfNeeded(hash); err != nil { return fmt.Errorf("Error activating devmapper device for '%s': %s", hash, err) } @@ -736,9 +717,6 @@ func (devices *DeviceSet) HasDevice(hash string) bool { devices.Lock() defer devices.Unlock() - if err := devices.ensureInit(); err != nil { - return false - } return devices.Devices[hash] != nil } @@ -746,10 +724,6 @@ func (devices *DeviceSet) HasInitializedDevice(hash string) bool { devices.Lock() defer devices.Unlock() - if err := devices.ensureInit(); err != nil { - return false - } - info := devices.Devices[hash] return info != nil && info.Initialized } @@ -758,10 +732,6 @@ func (devices *DeviceSet) HasActivatedDevice(hash string) bool { devices.Lock() defer devices.Unlock() - if err := devices.ensureInit(); err != nil { - return false - } - info := devices.Devices[hash] if info == nil { return false @@ -774,11 +744,6 @@ func (devices *DeviceSet) SetInitialized(hash string) error { devices.Lock() defer devices.Unlock() - if err := devices.ensureInit(); err != nil { - utils.Debugf("\n--->Err: %s\n", err) - return err - } - info := devices.Devices[hash] if info == nil { return fmt.Errorf("Unknown device %s", hash) @@ -800,10 +765,6 @@ func (devices *DeviceSet) Status() *Status { status := &Status{} - if err := devices.ensureInit(); err != nil { - return status - } - status.PoolName = devices.getPoolName() status.DataLoopback = path.Join(devices.loopbackDir(), "data") status.MetadataLoopback = path.Join(devices.loopbackDir(), "metadata") @@ -827,24 +788,18 @@ func (devices *DeviceSet) Status() *Status { return status } -func (devices *DeviceSet) ensureInit() error { - if !devices.initialized { - devices.initialized = true - if err := devices.initDevmapper(); err != nil { - utils.Debugf("\n--->Err: %s\n", err) - return err - } - } - return nil -} - -func NewDeviceSet(root string) *DeviceSet { +func NewDeviceSet(root string) (*DeviceSet, error) { SetDevDir("/dev") - return &DeviceSet{ - initialized: false, + devices := &DeviceSet{ root: root, MetaData: MetaData{Devices: make(map[string]*DevInfo)}, activeMounts: make(map[string]int), } + + if err := devices.initDevmapper(); err != nil { + return nil, err + } + + return devices, nil } diff --git a/components/engine/devmapper/driver.go b/components/engine/devmapper/driver.go index e095b33be5..f62c61787d 100644 --- a/components/engine/devmapper/driver.go +++ b/components/engine/devmapper/driver.go @@ -23,13 +23,14 @@ type Driver struct { } func Init(home string) (graphdriver.Driver, error) { - d := &Driver{ - DeviceSet: NewDeviceSet(home), - home: home, - } - if err := d.DeviceSet.ensureInit(); err != nil { + deviceSet, err := NewDeviceSet(home); + if err != nil { return nil, err } + d := &Driver{ + DeviceSet: deviceSet, + home: home, + } return d, nil } From d30197ef543ec30e709a48b8b3ef62f2ccd37769 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 11 Nov 2013 14:35:29 +0100 Subject: [PATCH 226/301] archive.ChagesDir() - faster and better implementation This replaces the current version with the latest version from the dm branch. Advantages in this version: We only scan each directory tree once, which means less i/o and less chance of container changes inbetween the two scans causing inconsistencies. We avoid comparing some fields for change detection: * Inode * size-in-blocks These can change during a copy operation (e.g. in the dummy backend) without needing to actually reflect a change in content or metadata. * Ctime Any copy operation will create a new Ctime value, and there is no API to change it to the "source" value. * size for directories The size of a directory doesn't have to be the same just because you recreated the same content as another director. Internal details in the filesystem may make these different with no "real" change. Upstream-commit: b6ef4bc9521346dc5066d71821c6cadfbeced9d3 Component: engine --- components/engine/archive/changes.go | 200 ++++++++++++++++++--------- 1 file changed, 136 insertions(+), 64 deletions(-) diff --git a/components/engine/archive/changes.go b/components/engine/archive/changes.go index a03172115f..95e8589d7b 100644 --- a/components/engine/archive/changes.go +++ b/components/engine/archive/changes.go @@ -106,107 +106,179 @@ func Changes(layers []string, rw string) ([]Change, error) { return changes, nil } -func ChangesDirs(newDir, oldDir string) ([]Change, error) { - var changes []Change - err := filepath.Walk(newDir, func(newPath string, f os.FileInfo, err error) error { - if err != nil { - return err - } +type FileInfo struct { + parent *FileInfo + name string + stat syscall.Stat_t + children map[string]*FileInfo +} - var newStat syscall.Stat_t - err = syscall.Lstat(newPath, &newStat) - if err != nil { - return err - } +func (root *FileInfo) LookUp(path string) *FileInfo { + parent := root + if path == "/" { + return root + } - // Rebase path - relPath, err := filepath.Rel(newDir, newPath) - if err != nil { - return err - } - relPath = filepath.Join("/", relPath) - - // Skip root - if relPath == "/" || relPath == "/.docker-id" { - return nil - } - - change := Change{ - Path: relPath, - } - - oldPath := filepath.Join(oldDir, relPath) - - var oldStat = &syscall.Stat_t{} - err = syscall.Lstat(oldPath, oldStat) - if err != nil { - if !os.IsNotExist(err) { - return err + pathElements := strings.Split(path, "/") + for _, elem := range pathElements { + if elem != "" { + child := parent.children[elem] + if child == nil { + return nil } - oldStat = nil + parent = child } + } + return parent +} - if oldStat == nil { - change.Kind = ChangeAdd - changes = append(changes, change) - } else { - if oldStat.Ino != newStat.Ino || - oldStat.Mode != newStat.Mode || +func (info *FileInfo) path() string { + if info.parent == nil { + return "/" + } + return filepath.Join(info.parent.path(), info.name) +} + +func (info *FileInfo) isDir() bool { + return info.parent == nil || info.stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR +} + +func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) { + if oldInfo == nil { + // add + change := Change{ + Path: info.path(), + Kind: ChangeAdd, + } + *changes = append(*changes, change) + } + + // We make a copy so we can modify it to detect additions + // also, we only recurse on the old dir if the new info is a directory + // otherwise any previous delete/change is considered recursive + oldChildren := make(map[string]*FileInfo) + if oldInfo != nil && info.isDir() { + for k, v := range oldInfo.children { + oldChildren[k] = v + } + } + + for name, newChild := range info.children { + oldChild, _ := oldChildren[name] + if oldChild != nil { + // change? + oldStat := &oldChild.stat + newStat := &newChild.stat + // Note: We can't compare inode or ctime or blocksize here, because these change + // when copying a file into a container. However, that is not generally a problem + // because any content change will change mtime, and any status change should + // be visible when actually comparing the stat fields. The only time this + // breaks down is if some code intentionally hides a change by setting + // back mtime + if oldStat.Mode != newStat.Mode || oldStat.Uid != newStat.Uid || oldStat.Gid != newStat.Gid || oldStat.Rdev != newStat.Rdev || - oldStat.Size != newStat.Size || - oldStat.Blocks != newStat.Blocks || - oldStat.Mtim != newStat.Mtim || - oldStat.Ctim != newStat.Ctim { - change.Kind = ChangeModify - changes = append(changes, change) + // Don't look at size for dirs, its not a good measure of change + (oldStat.Size != newStat.Size && oldStat.Mode&syscall.S_IFDIR != syscall.S_IFDIR) || + oldStat.Mtim != newStat.Mtim { + change := Change{ + Path: newChild.path(), + Kind: ChangeModify, + } + *changes = append(*changes, change) } + + // Remove from copy so we can detect deletions + delete(oldChildren, name) } - return nil - }) - if err != nil { - return nil, err + newChild.addChanges(oldChild, changes) } - err = filepath.Walk(oldDir, func(oldPath string, f os.FileInfo, err error) error { + for _, oldChild := range oldChildren { + // delete + change := Change{ + Path: oldChild.path(), + Kind: ChangeDelete, + } + *changes = append(*changes, change) + } + +} + +func (info *FileInfo) Changes(oldInfo *FileInfo) []Change { + var changes []Change + + info.addChanges(oldInfo, &changes) + + return changes +} + +func newRootFileInfo() *FileInfo { + root := &FileInfo{ + name: "/", + children: make(map[string]*FileInfo), + } + return root +} + +func collectFileInfo(sourceDir string) (*FileInfo, error) { + root := newRootFileInfo() + + err := filepath.Walk(sourceDir, func(path string, f os.FileInfo, err error) error { if err != nil { return err } // Rebase path - relPath, err := filepath.Rel(oldDir, oldPath) + relPath, err := filepath.Rel(sourceDir, path) if err != nil { return err } relPath = filepath.Join("/", relPath) - // Skip root if relPath == "/" { return nil } - change := Change{ - Path: relPath, + parent := root.LookUp(filepath.Dir(relPath)) + if parent == nil { + return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath) } - newPath := filepath.Join(newDir, relPath) - - var newStat = &syscall.Stat_t{} - err = syscall.Lstat(newPath, newStat) - if err != nil && os.IsNotExist(err) { - change.Kind = ChangeDelete - changes = append(changes, change) + info := &FileInfo{ + name: filepath.Base(relPath), + children: make(map[string]*FileInfo), + parent: parent, } + if err := syscall.Lstat(path, &info.stat); err != nil { + return err + } + + parent.children[info.name] = info + return nil }) if err != nil { return nil, err } - return changes, nil + return root, nil } +// Compare two directories and generate an array of Change objects describing the changes +func ChangesDirs(newDir, oldDir string) ([]Change, error) { + oldRoot, err := collectFileInfo(oldDir) + if err != nil { + return nil, err + } + newRoot, err := collectFileInfo(newDir) + if err != nil { + return nil, err + } + + return newRoot.Changes(oldRoot), nil +} func ExportChanges(root, rw string) (Archive, error) { changes, err := ChangesDirs(root, rw) From 122fd6b9c21b70e4ba09aadd2fa787fb587d5fb7 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 11 Nov 2013 16:47:36 +0100 Subject: [PATCH 227/301] Implement fallback operation for driver.Diff() This moves the Diff() operation to a separate Differ interface and implements a fallback that uses the Changes() results to encode a diff tar. Upstream-commit: e82f8c1661f3fa18e4dc6ca3aebe4dcc46e8961b Component: engine --- components/engine/container.go | 3 +- components/engine/devmapper/driver.go | 5 --- components/engine/graphdriver/driver.go | 5 ++- components/engine/graphdriver/dummy/driver.go | 8 ----- components/engine/runtime.go | 32 +++++++++++++++++++ 5 files changed, 38 insertions(+), 15 deletions(-) diff --git a/components/engine/container.go b/components/engine/container.go index c272a34621..a9dafa9e83 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -1382,7 +1382,8 @@ func (container *Container) ExportRw() (archive.Archive, error) { if container.runtime == nil { return nil, fmt.Errorf("Can't load storage driver for unregistered container %s", container.ID) } - return container.runtime.driver.Diff(container.ID) + + return container.runtime.Diff(container) } func (container *Container) Export() (archive.Archive, error) { diff --git a/components/engine/devmapper/driver.go b/components/engine/devmapper/driver.go index e095b33be5..c23f5288f2 100644 --- a/components/engine/devmapper/driver.go +++ b/components/engine/devmapper/driver.go @@ -2,7 +2,6 @@ package devmapper import ( "fmt" - "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/graphdriver" "os" "path" @@ -57,10 +56,6 @@ func (d *Driver) Get(id string) (string, error) { return mp, nil } -func (d *Driver) Diff(id string) (archive.Archive, error) { - return nil, fmt.Errorf("Not implemented") -} - func (d *Driver) DiffSize(id string) (int64, error) { return -1, fmt.Errorf("Not implemented") } diff --git a/components/engine/graphdriver/driver.go b/components/engine/graphdriver/driver.go index 4bb129dfb2..e96f6bfa5e 100644 --- a/components/engine/graphdriver/driver.go +++ b/components/engine/graphdriver/driver.go @@ -16,7 +16,6 @@ type Driver interface { Get(id string) (dir string, err error) - Diff(id string) (archive.Archive, error) DiffSize(id string) (bytes int64, err error) Cleanup() error @@ -26,6 +25,10 @@ type Changer interface { Changes(id string) ([]archive.Change, error) } +type Differ interface { + Diff(id string) (archive.Archive, error) +} + var ( // All registred drivers drivers map[string]InitFunc diff --git a/components/engine/graphdriver/dummy/driver.go b/components/engine/graphdriver/dummy/driver.go index 41a3e62a1f..eb05aacb5d 100644 --- a/components/engine/graphdriver/dummy/driver.go +++ b/components/engine/graphdriver/dummy/driver.go @@ -73,14 +73,6 @@ func (d *Driver) Get(id string) (string, error) { return dir, nil } -func (d *Driver) Diff(id string) (archive.Archive, error) { - p, err := d.Get(id) - if err != nil { - return nil, err - } - return archive.Tar(p, archive.Uncompressed) -} - func (d *Driver) DiffSize(id string) (int64, error) { return -1, fmt.Errorf("Not implemented") } diff --git a/components/engine/runtime.go b/components/engine/runtime.go index bda59afce5..d8ec2a90d4 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -18,6 +18,7 @@ import ( "os" "os/exec" "path" + "path/filepath" "sort" "strings" "time" @@ -747,6 +748,37 @@ func (runtime *Runtime) Changes(container *Container) ([]archive.Change, error) return archive.ChangesDirs(cDir, initDir) } +func (runtime *Runtime) Diff(container *Container) (archive.Archive, error) { + if differ, ok := runtime.driver.(graphdriver.Differ); ok { + return differ.Diff(container.ID) + } + + changes, err := runtime.Changes(container) + if err != nil { + return nil, err + } + + cDir, err := runtime.driver.Get(container.ID) + if err != nil { + return nil, fmt.Errorf("Error getting container rootfs %s from driver %s: %s", container.ID, container.runtime.driver, err) + } + + files := make([]string, 0) + deletions := make([]string, 0) + for _, change := range changes { + if change.Kind == archive.ChangeModify || change.Kind == archive.ChangeAdd { + files = append(files, change.Path) + } + if change.Kind == archive.ChangeDelete { + base := filepath.Base(change.Path) + dir := filepath.Dir(change.Path) + deletions = append(deletions, filepath.Join(dir, ".wh."+base)) + } + } + + return archive.TarFilter(cDir, archive.Uncompressed, files, false, deletions) +} + func linkLxcStart(root string) error { sourcePath, err := exec.LookPath("lxc-start") if err != nil { From 675c173a124baeab150ac8d34fbbab89f7de5ad0 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Mon, 11 Nov 2013 18:56:21 +0000 Subject: [PATCH 228/301] Hack: set NONUKE environment variable to run the tests without cleanup, to investigate temp directories Upstream-commit: ec6fe9f2007a0400a441b79c89bced5727589ba9 Component: engine --- components/engine/runtime_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index e61f63d495..a3b3d35bea 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -36,6 +36,9 @@ var ( ) func nuke(runtime *Runtime) error { + if nonuke := os.Getenv("NONUKE"); nonuke != "" { + return nil + } var wg sync.WaitGroup for _, container := range runtime.List() { wg.Add(1) From 86d455035fa5232d1603f92437cc4df3becbcedf Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 11 Nov 2013 12:09:26 -0800 Subject: [PATCH 229/301] Remove Differ and Changer interfaces Add the methods to the Driver interface to force the drivers to implement the methods Upstream-commit: 4d1a537433ede2bbc75b0a4817e8121f9e03fd86 Component: engine --- components/engine/aufs/aufs.go | 6 +-- components/engine/container.go | 9 ++-- components/engine/devmapper/driver.go | 9 ++++ components/engine/graphdriver/driver.go | 10 +---- components/engine/graphdriver/dummy/driver.go | 8 ++++ components/engine/runtime.go | 45 ++----------------- 6 files changed, 30 insertions(+), 57 deletions(-) diff --git a/components/engine/aufs/aufs.go b/components/engine/aufs/aufs.go index f47c65e661..6865020a94 100644 --- a/components/engine/aufs/aufs.go +++ b/components/engine/aufs/aufs.go @@ -210,11 +210,7 @@ func (a *AufsDriver) Diff(id string) (archive.Archive, error) { // Returns the size of the contents for the id func (a *AufsDriver) DiffSize(id string) (int64, error) { - p, err := a.Get(id) - if err != nil { - return -1, err - } - return utils.TreeSize(p) + return utils.TreeSize(path.Join(a.rootPath(), "diff", id)) } func (a *AufsDriver) Changes(id string) ([]archive.Change, error) { diff --git a/components/engine/container.go b/components/engine/container.go index a9dafa9e83..3f471f4d09 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -1479,10 +1479,13 @@ func validateID(id string) error { // GetSize, return real size, virtual size func (container *Container) GetSize() (int64, int64) { - var sizeRw, sizeRootfs int64 + var ( + sizeRw, sizeRootfs int64 + err error + driver = container.runtime.driver + ) - driver := container.runtime.driver - sizeRw, err := driver.DiffSize(container.ID) + 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 diff --git a/components/engine/devmapper/driver.go b/components/engine/devmapper/driver.go index c23f5288f2..287c0baec4 100644 --- a/components/engine/devmapper/driver.go +++ b/components/engine/devmapper/driver.go @@ -2,6 +2,7 @@ package devmapper import ( "fmt" + "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/graphdriver" "os" "path" @@ -60,6 +61,14 @@ func (d *Driver) DiffSize(id string) (int64, error) { return -1, fmt.Errorf("Not implemented") } +func (d *Driver) Diff(id string) (archive.Archive, error) { + return nil, fmt.Errorf("Not implemented)") +} + +func (d *Driver) Changes(id string) ([]archive.Change, error) { + return nil, fmt.Errorf("asdlfj)") +} + func (d *Driver) mount(id, mp string) error { // Create the target directories if they don't exist if err := os.MkdirAll(mp, 0755); err != nil && !os.IsExist(err) { diff --git a/components/engine/graphdriver/driver.go b/components/engine/graphdriver/driver.go index e96f6bfa5e..8014eddc71 100644 --- a/components/engine/graphdriver/driver.go +++ b/components/engine/graphdriver/driver.go @@ -17,18 +17,12 @@ type Driver interface { Get(id string) (dir string, err error) DiffSize(id string) (bytes int64, err error) + Diff(id string) (archive.Archive, error) + Changes(id string) ([]archive.Change, error) Cleanup() error } -type Changer interface { - Changes(id string) ([]archive.Change, error) -} - -type Differ interface { - Diff(id string) (archive.Archive, error) -} - var ( // All registred drivers drivers map[string]InitFunc diff --git a/components/engine/graphdriver/dummy/driver.go b/components/engine/graphdriver/dummy/driver.go index eb05aacb5d..e25f98416e 100644 --- a/components/engine/graphdriver/dummy/driver.go +++ b/components/engine/graphdriver/dummy/driver.go @@ -76,3 +76,11 @@ func (d *Driver) Get(id string) (string, error) { func (d *Driver) DiffSize(id string) (int64, error) { return -1, fmt.Errorf("Not implemented") } + +func (d *Driver) Diff(id string) (archive.Archive, error) { + return nil, fmt.Errorf("Not implemented)") +} + +func (d *Driver) Changes(id string) ([]archive.Change, error) { + return nil, fmt.Errorf("asdlfj)") +} diff --git a/components/engine/runtime.go b/components/engine/runtime.go index d8ec2a90d4..bb21d192ec 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -18,7 +18,6 @@ import ( "os" "os/exec" "path" - "path/filepath" "sort" "strings" "time" @@ -734,49 +733,13 @@ func (runtime *Runtime) Unmount(container *Container) error { } func (runtime *Runtime) Changes(container *Container) ([]archive.Change, error) { - if changer, ok := runtime.driver.(graphdriver.Changer); ok { - return changer.Changes(container.ID) - } - cDir, err := runtime.driver.Get(container.ID) - if err != nil { - return nil, fmt.Errorf("Error getting container rootfs %s from driver %s: %s", container.ID, container.runtime.driver, err) - } - initDir, err := runtime.driver.Get(container.ID + "-init") - if err != nil { - return nil, fmt.Errorf("Error getting container init rootfs %s from driver %s: %s", container.ID, container.runtime.driver, err) - } - return archive.ChangesDirs(cDir, initDir) + // FIXME: Remove Changes method from runtime + return runtime.driver.Changes(container.ID) } func (runtime *Runtime) Diff(container *Container) (archive.Archive, error) { - if differ, ok := runtime.driver.(graphdriver.Differ); ok { - return differ.Diff(container.ID) - } - - changes, err := runtime.Changes(container) - if err != nil { - return nil, err - } - - cDir, err := runtime.driver.Get(container.ID) - if err != nil { - return nil, fmt.Errorf("Error getting container rootfs %s from driver %s: %s", container.ID, container.runtime.driver, err) - } - - files := make([]string, 0) - deletions := make([]string, 0) - for _, change := range changes { - if change.Kind == archive.ChangeModify || change.Kind == archive.ChangeAdd { - files = append(files, change.Path) - } - if change.Kind == archive.ChangeDelete { - base := filepath.Base(change.Path) - dir := filepath.Dir(change.Path) - deletions = append(deletions, filepath.Join(dir, ".wh."+base)) - } - } - - return archive.TarFilter(cDir, archive.Uncompressed, files, false, deletions) + // FIXME: Remove Diff method from runtime + return runtime.driver.Diff(container.ID) } func linkLxcStart(root string) error { From b93991acb18126273286e53bac49e7e0f0fe92d6 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 11 Nov 2013 14:30:38 -0800 Subject: [PATCH 230/301] Add ability to exclude files from tar Upstream-commit: 2c7f50a77dc281289387008b4a08656e7e5328be Component: engine --- components/engine/archive/archive.go | 34 +++++++++++++++++--------- components/engine/archive/changes.go | 36 +++++++++++++--------------- components/engine/aufs/aufs.go | 16 +++++++------ components/engine/container.go | 6 ++++- 4 files changed, 54 insertions(+), 38 deletions(-) diff --git a/components/engine/archive/archive.go b/components/engine/archive/archive.go index dd96abf626..4d28587e5d 100644 --- a/components/engine/archive/archive.go +++ b/components/engine/archive/archive.go @@ -15,7 +15,15 @@ import ( type Archive io.Reader -type Compression uint32 +type Compression int + +type TarOptions struct { + Includes []string + Excludes []string + Recursive bool + Compression Compression + CreateFiles []string +} const ( Uncompressed Compression = iota @@ -80,7 +88,7 @@ func (compression *Compression) Extension() string { // Tar creates an archive from the directory at `path`, and returns it as a // stream of bytes. func Tar(path string, compression Compression) (io.Reader, error) { - return TarFilter(path, compression, nil, true, nil) + return TarFilter(path, &TarOptions{Recursive: true, Compression: compression}) } func escapeName(name string) string { @@ -101,25 +109,29 @@ func escapeName(name string) string { // Tar creates an archive from the directory at `path`, only including files whose relative // paths are included in `filter`. If `filter` is nil, then all files are included. -func TarFilter(path string, compression Compression, filter []string, recursive bool, createFiles []string) (io.Reader, error) { +func TarFilter(path string, options *TarOptions) (io.Reader, error) { args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path, "-T", "-"} - if filter == nil { - filter = []string{"."} + if options.Includes == nil { + options.Includes = []string{"."} } - args = append(args, "-c"+compression.Flag()) + args = append(args, "-c"+options.Compression.Flag()) - if !recursive { + for _, exclude := range options.Excludes { + args = append(args, fmt.Sprintf("--exclude=%s", exclude)) + } + + if !options.Recursive { args = append(args, "--no-recursion") } files := "" - for _, f := range filter { + for _, f := range options.Includes { files = files + escapeName(f) + "\n" } tmpDir := "" - if createFiles != nil { + if options.CreateFiles != nil { var err error // Can't use := here or we override the outer tmpDir tmpDir, err = ioutil.TempDir("", "docker-tar") if err != nil { @@ -127,7 +139,7 @@ func TarFilter(path string, compression Compression, filter []string, recursive } files = files + "-C" + tmpDir + "\n" - for _, f := range createFiles { + for _, f := range options.CreateFiles { path := filepath.Join(tmpDir, f) err := os.MkdirAll(filepath.Dir(path), 0600) if err != nil { @@ -194,7 +206,7 @@ func Untar(archive io.Reader, path string) error { // TarUntar aborts and returns the error. func TarUntar(src string, filter []string, dst string) error { utils.Debugf("TarUntar(%s %s %s)", src, filter, dst) - archive, err := TarFilter(src, Uncompressed, filter, true, nil) + archive, err := TarFilter(src, &TarOptions{Compression: Uncompressed, Includes: filter, Recursive: true}) if err != nil { return err } diff --git a/components/engine/archive/changes.go b/components/engine/archive/changes.go index a03172115f..94c30e325a 100644 --- a/components/engine/archive/changes.go +++ b/components/engine/archive/changes.go @@ -207,24 +207,22 @@ func ChangesDirs(newDir, oldDir string) ([]Change, error) { return changes, nil } - func ExportChanges(root, rw string) (Archive, error) { - changes, err := ChangesDirs(root, rw) - if err != nil { - return nil, err - } - files := make([]string, 0) - deletions := make([]string, 0) - for _, change := range changes { - if change.Kind == ChangeModify || change.Kind == ChangeAdd { - files = append(files, change.Path) - } - if change.Kind == ChangeDelete { - base := filepath.Base(change.Path) - dir := filepath.Dir(change.Path) - deletions = append(deletions, filepath.Join(dir, ".wh."+base)) - } - } - return TarFilter(root, Uncompressed, files, false, deletions) + changes, err := ChangesDirs(root, rw) + if err != nil { + return nil, err + } + files := make([]string, 0) + deletions := make([]string, 0) + for _, change := range changes { + if change.Kind == ChangeModify || change.Kind == ChangeAdd { + files = append(files, change.Path) + } + if change.Kind == ChangeDelete { + base := filepath.Base(change.Path) + dir := filepath.Dir(change.Path) + deletions = append(deletions, filepath.Join(dir, ".wh."+base)) + } + } + return TarFilter(root, &TarOptions{Compression: Uncompressed, Recursive: false, Includes: files, CreateFiles: deletions}) } - diff --git a/components/engine/aufs/aufs.go b/components/engine/aufs/aufs.go index 6865020a94..a1ec39d3d2 100644 --- a/components/engine/aufs/aufs.go +++ b/components/engine/aufs/aufs.go @@ -137,8 +137,7 @@ func (a *AufsDriver) createDirsFor(id string) error { } for _, p := range paths { - dir := path.Join(a.rootPath(), p, id) - if err := os.MkdirAll(dir, 0755); err != nil { + if err := os.MkdirAll(path.Join(a.rootPath(), p, id), 0755); err != nil { return err } } @@ -201,11 +200,14 @@ func (a *AufsDriver) Get(id string) (string, error) { // Returns an archive of the contents for the id func (a *AufsDriver) Diff(id string) (archive.Archive, error) { - p, err := a.Get(id) - if err != nil { - return nil, err - } - return archive.Tar(p, archive.Uncompressed) + // Exclude top level aufs metadata from the diff + return archive.TarFilter( + path.Join(a.rootPath(), "diff", id), + &archive.TarOptions{ + Excludes: []string{".wh*"}, + Recursive: true, + Compression: archive.Uncompressed, + }) } // Returns the size of the contents for the id diff --git a/components/engine/container.go b/components/engine/container.go index 3f471f4d09..c39bc335a1 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -1527,7 +1527,11 @@ func (container *Container) Copy(resource string) (archive.Archive, error) { filter = []string{path.Base(basePath)} basePath = path.Dir(basePath) } - return archive.TarFilter(basePath, archive.Uncompressed, filter, true, nil) + return archive.TarFilter(basePath, &archive.TarOptions{ + Compression: archive.Uncompressed, + Includes: filter, + Recursive: true, + }) } // Returns true if the container exposes a certain port From 084cb15f1ab91fb509515761097a1a0268a8e998 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 11 Nov 2013 17:17:38 -0800 Subject: [PATCH 231/301] Allow drivers to implement ApplyDiff in Differ interface Upstream-commit: 5d9723002bc764e2c768e5184994d7949f55fc49 Component: engine --- components/engine/archive/archive.go | 17 +++-- components/engine/archive/archive_test.go | 2 +- components/engine/archive/diff.go | 2 +- components/engine/aufs/aufs.go | 18 +++--- components/engine/buildfile.go | 2 +- components/engine/commands.go | 2 +- components/engine/container.go | 2 +- components/engine/container_test.go | 64 +++++++++++++++++++ components/engine/devmapper/driver.go | 11 +--- components/engine/graph.go | 2 +- components/engine/graphdriver/driver.go | 11 ++-- components/engine/graphdriver/dummy/driver.go | 10 +-- components/engine/image.go | 17 +++-- components/engine/runtime.go | 50 +++++++++++++-- components/engine/runtime_test.go | 2 +- 15 files changed, 159 insertions(+), 53 deletions(-) diff --git a/components/engine/archive/archive.go b/components/engine/archive/archive.go index 4d28587e5d..110f9cc1ad 100644 --- a/components/engine/archive/archive.go +++ b/components/engine/archive/archive.go @@ -167,7 +167,7 @@ func TarFilter(path string, options *TarOptions) (io.Reader, error) { // The archive may be compressed with one of the following algorithms: // identity (uncompressed), gzip, bzip2, xz. // FIXME: specify behavior when target path exists vs. doesn't exist. -func Untar(archive io.Reader, path string) error { +func Untar(archive io.Reader, path string, options *TarOptions) error { if archive == nil { return fmt.Errorf("Empty archive") } @@ -188,8 +188,15 @@ func Untar(archive io.Reader, path string) error { compression := DetectCompression(buf) utils.Debugf("Archive compression detected: %s", compression.Extension()) + args := []string{"--numeric-owner", "-f", "-", "-C", path, "-x" + compression.Flag()} - cmd := exec.Command("tar", "--numeric-owner", "-f", "-", "-C", path, "-x"+compression.Flag()) + if options != nil { + for _, exclude := range options.Excludes { + args = append(args, fmt.Sprintf("--exclude=%s", exclude)) + } + } + + cmd := exec.Command("tar", args...) cmd.Stdin = io.MultiReader(bytes.NewReader(buf), archive) // Hardcode locale environment for predictable outcome regardless of host configuration. // (see https://github.com/dotcloud/docker/issues/355) @@ -210,7 +217,7 @@ func TarUntar(src string, filter []string, dst string) error { if err != nil { return err } - return Untar(archive, dst) + return Untar(archive, dst, nil) } // UntarPath is a convenience function which looks for an archive @@ -218,7 +225,7 @@ func TarUntar(src string, filter []string, dst string) error { func UntarPath(src, dst string) error { if archive, err := os.Open(src); err != nil { return err - } else if err := Untar(archive, dst); err != nil { + } else if err := Untar(archive, dst, nil); err != nil { return err } return nil @@ -287,7 +294,7 @@ func CopyFileWithTar(src, dst string) error { return err } tw.Close() - return Untar(buf, filepath.Dir(dst)) + return Untar(buf, filepath.Dir(dst), nil) } // CmdStream executes a command, and returns its stdout as a stream. diff --git a/components/engine/archive/archive_test.go b/components/engine/archive/archive_test.go index 8d551464c5..684d99dc14 100644 --- a/components/engine/archive/archive_test.go +++ b/components/engine/archive/archive_test.go @@ -83,7 +83,7 @@ func tarUntar(t *testing.T, origin string, compression Compression) error { return err } defer os.RemoveAll(tmp) - if err := Untar(archive, tmp); err != nil { + if err := Untar(archive, tmp, nil); err != nil { return err } if _, err := os.Stat(tmp); err != nil { diff --git a/components/engine/archive/diff.go b/components/engine/archive/diff.go index c06eddd2c8..d2c3a05161 100644 --- a/components/engine/archive/diff.go +++ b/components/engine/archive/diff.go @@ -13,7 +13,7 @@ func ApplyLayer(dest string, layer Archive) error { // Poor man's diff applyer in 2 steps: // Step 1: untar everything in place - if err := Untar(layer, dest); err != nil { + if err := Untar(layer, dest, nil); err != nil { return err } diff --git a/components/engine/aufs/aufs.go b/components/engine/aufs/aufs.go index a1ec39d3d2..97c671207b 100644 --- a/components/engine/aufs/aufs.go +++ b/components/engine/aufs/aufs.go @@ -200,18 +200,18 @@ func (a *AufsDriver) Get(id string) (string, error) { // Returns an archive of the contents for the id func (a *AufsDriver) Diff(id string) (archive.Archive, error) { - // Exclude top level aufs metadata from the diff - return archive.TarFilter( - path.Join(a.rootPath(), "diff", id), - &archive.TarOptions{ - Excludes: []string{".wh*"}, - Recursive: true, - Compression: archive.Uncompressed, - }) + return archive.TarFilter(path.Join(a.rootPath(), "diff", id), &archive.TarOptions{ + Recursive: true, + Compression: archive.Uncompressed, + }) +} + +func (a *AufsDriver) ApplyDiff(id string, diff archive.Archive) error { + return archive.Untar(diff, path.Join(a.rootPath(), "diff", id), nil) } // Returns the size of the contents for the id -func (a *AufsDriver) DiffSize(id string) (int64, error) { +func (a *AufsDriver) Size(id string) (int64, error) { return utils.TreeSize(path.Join(a.rootPath(), "diff", id)) } diff --git a/components/engine/buildfile.go b/components/engine/buildfile.go index dbcec51889..e86bb05fef 100644 --- a/components/engine/buildfile.go +++ b/components/engine/buildfile.go @@ -476,7 +476,7 @@ func (b *buildFile) Build(context io.Reader) (string, error) { if err != nil { return "", err } - if err := archive.Untar(context, name); err != nil { + if err := archive.Untar(context, name, nil); err != nil { return "", err } defer os.RemoveAll(name) diff --git a/components/engine/commands.go b/components/engine/commands.go index d41f0f86b8..fac70ca5d4 100644 --- a/components/engine/commands.go +++ b/components/engine/commands.go @@ -1911,7 +1911,7 @@ func (cli *DockerCli) CmdCp(args ...string) error { if statusCode == 200 { r := bytes.NewReader(data) - if err := archive.Untar(r, copyData.HostPath); err != nil { + if err := archive.Untar(r, copyData.HostPath, nil); err != nil { return err } } diff --git a/components/engine/container.go b/components/engine/container.go index c39bc335a1..2f76bf6582 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -1485,7 +1485,7 @@ func (container *Container) GetSize() (int64, int64) { driver = container.runtime.driver ) - sizeRw, err = driver.DiffSize(container.ID) + sizeRw, err = driver.Size(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 diff --git a/components/engine/container_test.go b/components/engine/container_test.go index 6f39f9be8e..d182f42900 100644 --- a/components/engine/container_test.go +++ b/components/engine/container_test.go @@ -1680,3 +1680,67 @@ func TestRestartGhost(t *testing.T) { t.Fatal(err) } } + +func TestRemoveFile(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + + container1, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "touch test.txt"}, t) + defer runtime.Destroy(container1) + + if container1.State.Running { + t.Errorf("Container shouldn't be running") + } + if err := container1.Run(); err != nil { + t.Fatal(err) + } + if container1.State.Running { + t.Errorf("Container shouldn't be running") + } + + commit := func(container *Container) (*Image, error) { + rwTar, err := container.ExportRw() + if err != nil { + return nil, err + } + img, err := runtime.graph.Create(rwTar, container, "unit test commited image", "", nil) + if err != nil { + return nil, err + } + return img, nil + } + + img, err := commit(container1) + if err != nil { + t.Fatal(err) + } + + container2, _ := mkContainer(runtime, []string{img.ID, "/bin/sh", "-c", "rm /test.txt"}, t) + defer runtime.Destroy(container2) + + if err := container2.Run(); err != nil { + t.Fatal(err) + } + + containerMount, err := runtime.driver.Get(container2.ID) + if err != nil { + t.Fatal(err) + } + if _, err := os.Stat(path.Join(containerMount, "test.txt")); err == nil { + t.Fatalf("test.txt should not exist") + } + + img, err = commit(container2) + if err != nil { + t.Fatal(err) + } + + mountPoint, err := runtime.driver.Get(img.ID) + if err != nil { + t.Fatal(err) + } + file := path.Join(mountPoint, "test.txt") + if _, err := os.Stat(file); err == nil { + t.Fatalf("The file %s should not exist\n", file) + } +} diff --git a/components/engine/devmapper/driver.go b/components/engine/devmapper/driver.go index 287c0baec4..a2808473ba 100644 --- a/components/engine/devmapper/driver.go +++ b/components/engine/devmapper/driver.go @@ -2,7 +2,6 @@ package devmapper import ( "fmt" - "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/graphdriver" "os" "path" @@ -57,18 +56,10 @@ func (d *Driver) Get(id string) (string, error) { return mp, nil } -func (d *Driver) DiffSize(id string) (int64, error) { +func (d *Driver) Size(id string) (int64, error) { return -1, fmt.Errorf("Not implemented") } -func (d *Driver) Diff(id string) (archive.Archive, error) { - return nil, fmt.Errorf("Not implemented)") -} - -func (d *Driver) Changes(id string) ([]archive.Change, error) { - return nil, fmt.Errorf("asdlfj)") -} - func (d *Driver) mount(id, mp string) error { // Create the target directories if they don't exist if err := os.MkdirAll(mp, 0755); err != nil && !os.IsExist(err) { diff --git a/components/engine/graph.go b/components/engine/graph.go index e42c4ecbf6..a6c42b0ff8 100644 --- a/components/engine/graph.go +++ b/components/engine/graph.go @@ -151,6 +151,7 @@ func (graph *Graph) Register(jsonData []byte, layerData archive.Archive, img *Im if err != nil { return fmt.Errorf("Driver %s failed to get image rootfs %s: %s", graph.driver, img.ID, err) } + img.graph = graph if err := StoreImage(img, jsonData, layerData, tmp, rootfs); err != nil { return err } @@ -158,7 +159,6 @@ func (graph *Graph) Register(jsonData []byte, layerData archive.Archive, img *Im if err := os.Rename(tmp, graph.imageRoot(img.ID)); err != nil { return err } - img.graph = graph graph.idIndex.Add(img.ID) return nil } diff --git a/components/engine/graphdriver/driver.go b/components/engine/graphdriver/driver.go index 8014eddc71..f521e0bbaf 100644 --- a/components/engine/graphdriver/driver.go +++ b/components/engine/graphdriver/driver.go @@ -15,14 +15,17 @@ type Driver interface { Remove(id string) error Get(id string) (dir string, err error) - - DiffSize(id string) (bytes int64, err error) - Diff(id string) (archive.Archive, error) - Changes(id string) ([]archive.Change, error) + Size(id string) (bytes int64, err error) Cleanup() error } +type Differ interface { + Diff(id string) (archive.Archive, error) + Changes(id string) ([]archive.Change, error) + ApplyDiff(id string, diff archive.Archive) error +} + var ( // All registred drivers drivers map[string]InitFunc diff --git a/components/engine/graphdriver/dummy/driver.go b/components/engine/graphdriver/dummy/driver.go index e25f98416e..ad0e21ec4a 100644 --- a/components/engine/graphdriver/dummy/driver.go +++ b/components/engine/graphdriver/dummy/driver.go @@ -73,14 +73,6 @@ func (d *Driver) Get(id string) (string, error) { return dir, nil } -func (d *Driver) DiffSize(id string) (int64, error) { +func (d *Driver) Size(id string) (int64, error) { return -1, fmt.Errorf("Not implemented") } - -func (d *Driver) Diff(id string) (archive.Archive, error) { - return nil, fmt.Errorf("Not implemented)") -} - -func (d *Driver) Changes(id string) ([]archive.Change, error) { - return nil, fmt.Errorf("asdlfj)") -} diff --git a/components/engine/image.go b/components/engine/image.go index 5f27a89719..d2d11eca10 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "github.com/dotcloud/docker/archive" + "github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -70,12 +71,18 @@ func StoreImage(img *Image, jsonData []byte, layerData archive.Archive, root, ro // If layerData is not nil, unpack it into the new layer if layerData != nil { - start := time.Now() - utils.Debugf("Start untar layer") - if err := archive.Untar(layerData, layer); err != nil { - return err + if differ, ok := img.graph.driver.(graphdriver.Differ); ok { + if err := differ.ApplyDiff(img.ID, layerData); err != nil { + return err + } + } else { + start := time.Now() + utils.Debugf("Start untar layer") + if err := archive.ApplyLayer(layer, layerData); err != nil { + return err + } + utils.Debugf("Untar time: %vs", time.Now().Sub(start).Seconds()) } - utils.Debugf("Untar time: %vs", time.Now().Sub(start).Seconds()) } // If raw json is provided, then use it diff --git a/components/engine/runtime.go b/components/engine/runtime.go index bb21d192ec..1429d54a37 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -18,6 +18,7 @@ import ( "os" "os/exec" "path" + "path/filepath" "sort" "strings" "time" @@ -733,13 +734,54 @@ func (runtime *Runtime) Unmount(container *Container) error { } func (runtime *Runtime) Changes(container *Container) ([]archive.Change, error) { - // FIXME: Remove Changes method from runtime - return runtime.driver.Changes(container.ID) + if differ, ok := runtime.driver.(graphdriver.Differ); ok { + return differ.Changes(container.ID) + } + cDir, err := runtime.driver.Get(container.ID) + if err != nil { + return nil, fmt.Errorf("Error getting container rootfs %s from driver %s: %s", container.ID, container.runtime.driver, err) + } + initDir, err := runtime.driver.Get(container.ID + "-init") + if err != nil { + return nil, fmt.Errorf("Error getting container init rootfs %s from driver %s: %s", container.ID, container.runtime.driver, err) + } + return archive.ChangesDirs(cDir, initDir) } func (runtime *Runtime) Diff(container *Container) (archive.Archive, error) { - // FIXME: Remove Diff method from runtime - return runtime.driver.Diff(container.ID) + if differ, ok := runtime.driver.(graphdriver.Differ); ok { + return differ.Diff(container.ID) + } + + changes, err := runtime.Changes(container) + if err != nil { + return nil, err + } + + cDir, err := runtime.driver.Get(container.ID) + if err != nil { + return nil, fmt.Errorf("Error getting container rootfs %s from driver %s: %s", container.ID, container.runtime.driver, err) + } + + files := make([]string, 0) + deletions := make([]string, 0) + for _, change := range changes { + if change.Kind == archive.ChangeModify || change.Kind == archive.ChangeAdd { + files = append(files, change.Path) + } + if change.Kind == archive.ChangeDelete { + base := filepath.Base(change.Path) + dir := filepath.Dir(change.Path) + deletions = append(deletions, filepath.Join(dir, ".wh."+base)) + } + } + // FIXME: Why do we create whiteout files inside Tar code ? + return archive.TarFilter(cDir, &archive.TarOptions{ + Compression: archive.Uncompressed, + Includes: files, + Recursive: false, + CreateFiles: deletions, + }) } func linkLxcStart(root string) error { diff --git a/components/engine/runtime_test.go b/components/engine/runtime_test.go index a3b3d35bea..af7cc6e85d 100644 --- a/components/engine/runtime_test.go +++ b/components/engine/runtime_test.go @@ -143,7 +143,7 @@ func setupBaseImage() { if img, err := runtime.repositories.LookupImage(unitTestImageName); err != nil || img.ID != unitTestImageID { // Retrieve the Image if err := srv.ImagePull(unitTestImageName, "", os.Stdout, utils.NewStreamFormatter(false), nil, nil, true); err != nil { - log.Fatalf("Unable to pull the test image:", err) + log.Fatalf("Unable to pull the test image: %s", err) } } } From 67a0c658d340f510ba81f1ea5e99aefba382222c Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 12 Nov 2013 11:48:35 -0800 Subject: [PATCH 232/301] Pull parent layers first before children Upstream-commit: 7301fbe035ca450e2aea86b8f2467cffd3fac18b Component: engine --- components/engine/server.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/engine/server.go b/components/engine/server.go index 6a94c8ae2c..3d27d50dcc 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -537,7 +537,8 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin // FIXME: Try to stream the images? // FIXME: Launch the getRemoteImage() in goroutines - for _, id := range history { + for i := len(history) - 1; i >= 0; i-- { + id := history[i] // ensure no two downloads of the same layer happen at the same time if err := srv.poolAdd("pull", "layer:"+id); err != nil { From 82914826a6eefb5bf89c11dc83779790c6ca28db Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 11 Nov 2013 17:39:19 +0100 Subject: [PATCH 233/301] archive.ApplyLayer() - handle directory whiteouts When directories are white-outed we can get called with the previously removed directories. Handle this with os.IsNotExist(error). Upstream-commit: 6f3c32eb18f0a604ba2638b6a9f19f8824412fd1 Component: engine --- components/engine/archive/diff.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/engine/archive/diff.go b/components/engine/archive/diff.go index c06eddd2c8..dffb6c49be 100644 --- a/components/engine/archive/diff.go +++ b/components/engine/archive/diff.go @@ -20,6 +20,11 @@ func ApplyLayer(dest string, layer Archive) error { // Step 2: walk for whiteouts and apply them, removing them in the process err := filepath.Walk(dest, func(fullPath string, f os.FileInfo, err error) error { if err != nil { + if os.IsNotExist(err) { + // This happens in the case of whiteouts in parent dir removing a directory + // We just ignore it + return filepath.SkipDir + } return err } From ce008ff5b0c669a1f296308e222a1d89f5a475b8 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 12 Nov 2013 12:25:35 -0800 Subject: [PATCH 234/301] Fix ImageTree test Upstream-commit: 08623dc216227d6377cb4558caedd75cacf68755 Component: engine --- components/engine/commands.go | 14 ++++++++------ components/engine/commands_test.go | 9 ++++----- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/components/engine/commands.go b/components/engine/commands.go index fac70ca5d4..179a303c64 100644 --- a/components/engine/commands.go +++ b/components/engine/commands.go @@ -1100,16 +1100,18 @@ func (cli *DockerCli) CmdImages(args ...string) error { } var outs []APIImages - err = json.Unmarshal(body, &outs) - if err != nil { + if err := json.Unmarshal(body, &outs); err != nil { return err } - var startImageArg = cmd.Arg(0) - var startImage APIImages + var ( + startImageArg = cmd.Arg(0) + startImage APIImages + + roots []APIImages + byParent = make(map[string][]APIImages) + ) - var roots []APIImages - var byParent = make(map[string][]APIImages) for _, image := range outs { if image.ParentId == "" { roots = append(roots, image) diff --git a/components/engine/commands_test.go b/components/engine/commands_test.go index 6c6a8e975b..186bce2c0a 100644 --- a/components/engine/commands_test.go +++ b/components/engine/commands_test.go @@ -776,13 +776,12 @@ func TestImagesTree(t *testing.T) { t.Fatal(err) } cmdOutput := string(cmdOutputBytes) - regexpStrings := []string{ fmt.Sprintf("└─%s Size: (\\d+.\\d+ MB) \\(virtual \\d+.\\d+ MB\\) Tags: %s:latest", unitTestImageIDShort, unitTestImageName), - "(?m)^ └─[0-9a-f]+", - "(?m)^ └─[0-9a-f]+", - "(?m)^ └─[0-9a-f]+", - fmt.Sprintf(" └─%s Size: \\d+ B \\(virtual \\d+.\\d+ MB\\) Tags: test:latest", utils.TruncateID(image.ID)), + "(?m) └─[0-9a-f]+.*", + "(?m) └─[0-9a-f]+.*", + "(?m) └─[0-9a-f]+.*", + fmt.Sprintf("(?m)^ └─%s Size: \\d+.\\d+ MB \\(virtual \\d+.\\d+ MB\\) Tags: test:latest", utils.TruncateID(image.ID)), } compiledRegexps := []*regexp.Regexp{} From 2e972c6b88c96919e7b9bce8e012dfb0a21ee574 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 13 Nov 2013 10:33:24 -0800 Subject: [PATCH 235/301] Ignore dir sizes in TreeSize func Upstream-commit: a4f14528c25c4a092c30334d05008fe44275a79e Component: engine --- components/engine/aufs/aufs_test.go | 4 +--- components/engine/utils/fs.go | 4 ++++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/components/engine/aufs/aufs_test.go b/components/engine/aufs/aufs_test.go index e4250d9e7b..872f14441b 100644 --- a/components/engine/aufs/aufs_test.go +++ b/components/engine/aufs/aufs_test.go @@ -426,7 +426,6 @@ func TestChanges(t *testing.T) { } } -/* FIXME: How to properly test this? func TestDiffSize(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) @@ -457,7 +456,7 @@ func TestDiffSize(t *testing.T) { t.Fatal(err) } - diffSize, err := d.DiffSize("1") + diffSize, err := d.Size("1") if err != nil { t.Fatal(err) } @@ -465,4 +464,3 @@ func TestDiffSize(t *testing.T) { t.Fatalf("Expected size to be %d got %d", size, diffSize) } } -*/ diff --git a/components/engine/utils/fs.go b/components/engine/utils/fs.go index 891a200507..f07784184c 100644 --- a/components/engine/utils/fs.go +++ b/components/engine/utils/fs.go @@ -8,6 +8,10 @@ import ( // TreeSize walks a directory tree and returns its total size in bytes. func TreeSize(dir string) (size int64, err error) { err = filepath.Walk(dir, func(d string, fileInfo os.FileInfo, e error) error { + // Ignore directory sizes + if fileInfo.IsDir() { + return nil + } size += fileInfo.Size() return nil }) From 19f53e14f10bf7e8c3457fb562c97e38707187aa Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 13 Nov 2013 11:03:56 -0800 Subject: [PATCH 236/301] Add test for ApplyDiff Upstream-commit: a69d86e0b19d804819d37a2a9edc03803267f579 Component: engine --- components/engine/aufs/aufs_test.go | 51 +++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/components/engine/aufs/aufs_test.go b/components/engine/aufs/aufs_test.go index 872f14441b..a33441bbd0 100644 --- a/components/engine/aufs/aufs_test.go +++ b/components/engine/aufs/aufs_test.go @@ -464,3 +464,54 @@ func TestDiffSize(t *testing.T) { t.Fatalf("Expected size to be %d got %d", size, diffSize) } } + +func TestApplyDiff(t *testing.T) { + d := newDriver(t) + defer os.RemoveAll(tmp) + defer d.Cleanup() + + if err := d.Create("1", ""); err != nil { + t.Fatal(err) + } + + diffPath, err := d.Get("1") + if err != nil { + t.Fatal(err) + } + + // Add a file to the diff path with a fixed size + size := int64(1024) + + f, err := os.Create(path.Join(diffPath, "test_file")) + if err != nil { + t.Fatal(err) + } + f.Truncate(size) + f.Close() + + diff, err := d.Diff("1") + if err != nil { + t.Fatal(err) + } + + if err := d.Create("2", ""); err != nil { + t.Fatal(err) + } + if err := d.Create("3", "2"); err != nil { + t.Fatal(err) + } + + if err := d.ApplyDiff("3", diff); err != nil { + t.Fatal(err) + } + + // Ensure that the file is in the mount point for id 3 + + mountPoint, err := d.Get("3") + if err != nil { + t.Fatal(err) + } + if _, err := os.Stat(path.Join(mountPoint, "test_file")); err != nil { + t.Fatal(err) + } +} From 3a53e728294817fbfa1d64941f18504924f4e249 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 13 Nov 2013 14:36:31 -0800 Subject: [PATCH 237/301] Create devmapper_wrapper.go Upstream-commit: 1d188c8737f97a4882851ccd0150cd09c7f6c1e9 Component: engine --- components/engine/devmapper/devmapper.go | 296 +++------------ .../engine/devmapper/devmapper_wrapper.go | 351 ++++++++++++++++++ 2 files changed, 401 insertions(+), 246 deletions(-) create mode 100644 components/engine/devmapper/devmapper_wrapper.go diff --git a/components/engine/devmapper/devmapper.go b/components/engine/devmapper/devmapper.go index eae42247cf..b4e8118431 100644 --- a/components/engine/devmapper/devmapper.go +++ b/components/engine/devmapper/devmapper.go @@ -1,163 +1,11 @@ package devmapper -/* -#cgo LDFLAGS: -L. -ldevmapper -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef LOOP_CTL_GET_FREE -#define LOOP_CTL_GET_FREE 0x4C82 -#endif - -// FIXME: this could easily be rewritten in go -char* attach_loop_device(const char *filename, int *loop_fd_out) -{ - struct loop_info64 loopinfo = {0}; - struct stat st; - char buf[64]; - int i, loop_fd, fd, start_index; - char* loopname; - - - *loop_fd_out = -1; - - start_index = 0; - fd = open("/dev/loop-control", O_RDONLY); - if (fd >= 0) { - start_index = ioctl(fd, LOOP_CTL_GET_FREE); - close(fd); - - if (start_index < 0) - start_index = 0; - } - - fd = open(filename, O_RDWR); - if (fd < 0) { - perror("open"); - return NULL; - } - - loop_fd = -1; - for (i = start_index ; loop_fd < 0 ; i++ ) { - if (sprintf(buf, "/dev/loop%d", i) < 0) { - close(fd); - return NULL; - } - - if (stat(buf, &st)) { - if (!S_ISBLK(st.st_mode)) { - fprintf(stderr, "[error] Loopback device %s is not a block device.\n", buf); - } else if (errno == ENOENT) { - fprintf(stderr, "[error] There are no more loopback device available.\n"); - } else { - fprintf(stderr, "[error] Unkown error trying to stat the loopback device %s (errno: %d).\n", buf, errno); - } - close(fd); - return NULL; - } - - loop_fd = open(buf, O_RDWR); - if (loop_fd < 0 && errno == ENOENT) { - fprintf(stderr, "[error] The loopback device %s does not exists.\n", buf); - close(fd); - return NULL; - } else if (loop_fd < 0) { - fprintf(stderr, "[error] Unkown error openning the loopback device %s. (errno: %d)\n", buf, errno); - continue; - } - - if (ioctl(loop_fd, LOOP_SET_FD, (void *)(size_t)fd) < 0) { - int errsv = errno; - close(loop_fd); - loop_fd = -1; - if (errsv != EBUSY) { - close(fd); - fprintf(stderr, "cannot set up loopback device %s: %s", buf, strerror(errsv)); - return NULL; - } - continue; - } - - close(fd); - - strncpy((char*)loopinfo.lo_file_name, buf, LO_NAME_SIZE); - loopinfo.lo_offset = 0; - loopinfo.lo_flags = LO_FLAGS_AUTOCLEAR; - - if (ioctl(loop_fd, LOOP_SET_STATUS64, &loopinfo) < 0) { - perror("ioctl LOOP_SET_STATUS64"); - if (ioctl(loop_fd, LOOP_CLR_FD, 0) < 0) { - perror("ioctl LOOP_CLR_FD"); - } - close(loop_fd); - fprintf (stderr, "cannot set up loopback device info"); - return (NULL); - } - - loopname = strdup(buf); - if (loopname == NULL) { - close(loop_fd); - return (NULL); - } - - *loop_fd_out = loop_fd; - return (loopname); - } - - return (NULL); -} - -static int64_t get_block_size(int fd) -{ - uint64_t size; - - if (ioctl(fd, BLKGETSIZE64, &size) == -1) - return -1; - return ((int64_t)size); -} - -extern void DevmapperLogCallback(int level, char *file, int line, int dm_errno_or_class, char *str); - -static void -log_cb(int level, const char *file, int line, - int dm_errno_or_class, const char *f, ...) -{ - char buffer[256]; - va_list ap; - - va_start(ap, f); - vsnprintf(buffer, 256, f, ap); - va_end(ap); - - DevmapperLogCallback(level, (char *)file, line, dm_errno_or_class, buffer); -} - -static void -log_with_errno_init () -{ - dm_log_with_errno_init(log_cb); -} - -*/ -import "C" - import ( "errors" "fmt" "github.com/dotcloud/docker/utils" "os" "runtime" - "syscall" - "unsafe" ) type DevmapperLogger interface { @@ -198,6 +46,7 @@ var ( ErrTaskSetAddNode = errors.New("dm_task_set_add_node failed") ErrTaskSetRO = errors.New("dm_task_set_ro failed") ErrTaskAddTarget = errors.New("dm_task_add_target failed") + ErrGetInfo = errors.New("dm_task_get_info failed") ErrGetDriverVersion = errors.New("dm_task_get_driver_version failed") ErrAttachLoopbackDevice = errors.New("loopback mounting failed") ErrGetBlockSize = errors.New("Can't get block size") @@ -210,7 +59,7 @@ var ( type ( Task struct { - unmanaged *C.struct_dm_task + unmanaged *CDmTask } Info struct { Exists int @@ -230,167 +79,131 @@ type ( func (t *Task) destroy() { if t != nil { - C.dm_task_destroy(t.unmanaged) + DmTaskDestory(t.unmanaged) runtime.SetFinalizer(t, nil) } } func TaskCreate(tasktype TaskType) *Task { - c_task := C.dm_task_create(C.int(tasktype)) - if c_task == nil { + Ctask := DmTaskCreate(int(tasktype)) + if Ctask == nil { return nil } - task := &Task{unmanaged: c_task} + task := &Task{unmanaged: Ctask} runtime.SetFinalizer(task, (*Task).destroy) return task } func (t *Task) Run() error { - if res := C.dm_task_run(t.unmanaged); res != 1 { + if res := DmTaskRun(t.unmanaged); res != 1 { return ErrTaskRun } return nil } func (t *Task) SetName(name string) error { - c_name := C.CString(name) - defer free(c_name) - - if res := C.dm_task_set_name(t.unmanaged, c_name); res != 1 { + if res := DmTaskSetName(t.unmanaged, name); res != 1 { return ErrTaskSetName } return nil } func (t *Task) SetMessage(message string) error { - c_message := C.CString(message) - defer free(c_message) - - if res := C.dm_task_set_message(t.unmanaged, c_message); res != 1 { + if res := DmTaskSetMessage(t.unmanaged, message); res != 1 { return ErrTaskSetMessage } return nil } func (t *Task) SetSector(sector uint64) error { - if res := C.dm_task_set_sector(t.unmanaged, C.uint64_t(sector)); res != 1 { + if res := DmTaskSetSector(t.unmanaged, sector); res != 1 { return ErrTaskSetAddNode } return nil } -func (t *Task) SetCookie(cookie *uint32, flags uint16) error { - c_cookie := C.uint32_t(*cookie) - if res := C.dm_task_set_cookie(t.unmanaged, &c_cookie, C.uint16_t(flags)); res != 1 { +func (t *Task) SetCookie(cookie *uint, flags uint16) error { + if res := DmTaskSetCookie(t.unmanaged, cookie, flags); res != 1 { return ErrTaskSetAddNode } - *cookie = uint32(c_cookie) return nil } -func (t *Task) SetAddNode(add_node AddNodeType) error { - if res := C.dm_task_set_add_node(t.unmanaged, C.dm_add_node_t(add_node)); res != 1 { +func (t *Task) SetAddNode(addNode AddNodeType) error { + if res := DmTaskSetAddNode(t.unmanaged, addNode); res != 1 { return ErrTaskSetAddNode } return nil } func (t *Task) SetRo() error { - if res := C.dm_task_set_ro(t.unmanaged); res != 1 { + if res := DmTaskSetRo(t.unmanaged); res != 1 { return ErrTaskSetRO } return nil } -func (t *Task) AddTarget(start uint64, size uint64, ttype string, params string) error { - c_ttype := C.CString(ttype) - defer free(c_ttype) - - c_params := C.CString(params) - defer free(c_params) - - if res := C.dm_task_add_target(t.unmanaged, C.uint64_t(start), C.uint64_t(size), c_ttype, c_params); res != 1 { +func (t *Task) AddTarget(start, size uint64, ttype, params string) error { + if res := DmTaskAddTarget(t.unmanaged, start, size, + ttype, params); res != 1 { return ErrTaskAddTarget } return nil } func (t *Task) GetDriverVersion() (string, error) { - buffer := C.CString(string(make([]byte, 128))) - defer free(buffer) - - if res := C.dm_task_get_driver_version(t.unmanaged, buffer, 128); res != 1 { + var version string + if res := DmTaskGetDriverVersion(t.unmanaged, &version); res != 1 { return "", ErrGetDriverVersion } - return C.GoString(buffer), nil + return version, nil } func (t *Task) GetInfo() (*Info, error) { - c_info := C.struct_dm_info{} - if res := C.dm_task_get_info(t.unmanaged, &c_info); res != 1 { - return nil, ErrGetDriverVersion + info := &Info{} + if res := DmTaskGetInfo(t.unmanaged, info); res != 1 { + return nil, ErrGetInfo } - return &Info{ - Exists: int(c_info.exists), - Suspended: int(c_info.suspended), - LiveTable: int(c_info.live_table), - InactiveTable: int(c_info.inactive_table), - OpenCount: int32(c_info.open_count), - EventNr: uint32(c_info.event_nr), - Major: uint32(c_info.major), - Minor: uint32(c_info.minor), - ReadOnly: int(c_info.read_only), - TargetCount: int32(c_info.target_count), - }, nil + return info, nil } -func (t *Task) GetNextTarget(next uintptr) (uintptr, uint64, uint64, string, string) { - var ( - c_start, c_length C.uint64_t - c_target_type, c_params *C.char - ) +func (t *Task) GetNextTarget(next uintptr) (nextPtr uintptr, start uint64, + length uint64, targetType string, params string) { - nextp := C.dm_get_next_target(t.unmanaged, unsafe.Pointer(next), &c_start, &c_length, &c_target_type, &c_params) - return uintptr(nextp), uint64(c_start), uint64(c_length), C.GoString(c_target_type), C.GoString(c_params) + return DmGetNextTarget(t.unmanaged, next, &start, &length, + &targetType, ¶ms), + start, length, targetType, params } func AttachLoopDevice(filename string) (*os.File, error) { - c_filename := C.CString(filename) - defer free(c_filename) - - var fd C.int - res := C.attach_loop_device(c_filename, &fd) - if res == nil { - if os.Getenv("DEBUG") != "" { - C.perror(C.CString(fmt.Sprintf("[debug] Error attach_loop_device(%s, %d)", filename, int(fd)))) - } + var fd int + res := DmAttachLoopDevice(filename, &fd) + if res == "" { return nil, ErrAttachLoopbackDevice } - defer free(res) - - return os.NewFile(uintptr(fd), C.GoString(res)), nil + return os.NewFile(uintptr(fd), res), nil } func getBlockSize(fd uintptr) int { var size uint64 - if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.BLKGETSIZE64, uintptr(unsafe.Pointer(&size))); err != 0 { - utils.Debugf("Error ioctl: %s", err) + if err := SysGetBlockSize(fd, &size); err != 0 { + utils.Debugf("Error ioctl (getBlockSize: %s)", err) return -1 } return int(size) } func GetBlockDeviceSize(file *os.File) (uint64, error) { - if size := C.get_block_size(C.int(file.Fd())); size == -1 { + size := DmGetBlockSize(file.Fd()) + if size == -1 { return 0, ErrGetBlockSize - } else { - return uint64(size), nil } + return uint64(size), nil } -func UdevWait(cookie uint32) error { - if res := C.dm_udev_wait(C.uint32_t(cookie)); res != 1 { +func UdevWait(cookie uint) error { + if res := DmUdevWait(cookie); res != 1 { utils.Debugf("Failed to wait on udev cookie %d", cookie) return ErrUdevWait } @@ -398,21 +211,18 @@ func UdevWait(cookie uint32) error { } func LogInitVerbose(level int) { - C.dm_log_init_verbose(C.int(level)) + DmLogInitVerbose(level) } var dmLogger DevmapperLogger = nil func logInit(logger DevmapperLogger) { dmLogger = logger - C.log_with_errno_init() + LogWithErrnoInit() } func SetDevDir(dir string) error { - c_dir := C.CString(dir) - defer free(c_dir) - - if res := C.dm_set_dev_dir(c_dir); res != 1 { + if res := DmSetDevDir(dir); res != 1 { utils.Debugf("Error dm_set_dev_dir") return ErrSetDevDir } @@ -420,13 +230,11 @@ func SetDevDir(dir string) error { } func GetLibraryVersion() (string, error) { - buffer := C.CString(string(make([]byte, 128))) - defer free(buffer) - - if res := C.dm_get_library_version(buffer, 128); res != 1 { + var version string + if res := DmGetLibraryVersion(&version); res != 1 { return "", ErrGetLibraryVersion } - return C.GoString(buffer), nil + return version, nil } // Useful helper for cleanup @@ -445,10 +253,6 @@ func RemoveDevice(name string) error { return nil } -func free(p *C.char) { - C.free(unsafe.Pointer(p)) -} - // This is the programmatic example of "dmsetup create" func createPool(poolName string, dataFile *os.File, metadataFile *os.File) error { task, err := createTask(DeviceCreate, poolName) @@ -466,7 +270,7 @@ func createPool(poolName string, dataFile *os.File, metadataFile *os.File) error return fmt.Errorf("Can't add target") } - var cookie uint32 = 0 + var cookie uint = 0 if err := task.SetCookie(&cookie, 0); err != nil { return fmt.Errorf("Can't set cookie") } @@ -564,7 +368,7 @@ func resumeDevice(name string) error { return err } - var cookie uint32 = 0 + var cookie uint = 0 if err := task.SetCookie(&cookie, 0); err != nil { return fmt.Errorf("Can't set cookie") } @@ -646,7 +450,7 @@ func activateDevice(poolName string, name string, deviceId int, size uint64) err return fmt.Errorf("Can't add node") } - var cookie uint32 = 0 + var cookie uint = 0 if err := task.SetCookie(&cookie, 0); err != nil { return fmt.Errorf("Can't set cookie") } diff --git a/components/engine/devmapper/devmapper_wrapper.go b/components/engine/devmapper/devmapper_wrapper.go new file mode 100644 index 0000000000..d6ec7ae7fb --- /dev/null +++ b/components/engine/devmapper/devmapper_wrapper.go @@ -0,0 +1,351 @@ +package devmapper + +/* +#cgo LDFLAGS: -L. -ldevmapper +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef LOOP_CTL_GET_FREE +#define LOOP_CTL_GET_FREE 0x4C82 +#endif + +// FIXME: this could easily be rewritten in go +char* attach_loop_device(const char *filename, int *loop_fd_out) +{ + struct loop_info64 loopinfo = {0}; + struct stat st; + char buf[64]; + int i, loop_fd, fd, start_index; + char* loopname; + + + *loop_fd_out = -1; + + start_index = 0; + fd = open("/dev/loop-control", O_RDONLY); + if (fd >= 0) { + start_index = ioctl(fd, LOOP_CTL_GET_FREE); + close(fd); + + if (start_index < 0) + start_index = 0; + } + + fd = open(filename, O_RDWR); + if (fd < 0) { + perror("open"); + return NULL; + } + + loop_fd = -1; + for (i = start_index ; loop_fd < 0 ; i++ ) { + if (sprintf(buf, "/dev/loop%d", i) < 0) { + close(fd); + return NULL; + } + + if (stat(buf, &st)) { + if (!S_ISBLK(st.st_mode)) { + fprintf(stderr, "[error] Loopback device %s is not a block device.\n", buf); + } else if (errno == ENOENT) { + fprintf(stderr, "[error] There are no more loopback device available.\n"); + } else { + fprintf(stderr, "[error] Unkown error trying to stat the loopback device %s (errno: %d).\n", buf, errno); + } + close(fd); + return NULL; + } + + loop_fd = open(buf, O_RDWR); + if (loop_fd < 0 && errno == ENOENT) { + fprintf(stderr, "[error] The loopback device %s does not exists.\n", buf); + close(fd); + return NULL; + } else if (loop_fd < 0) { + fprintf(stderr, "[error] Unkown error openning the loopback device %s. (errno: %d)\n", buf, errno); + continue; + } + + if (ioctl(loop_fd, LOOP_SET_FD, (void *)(size_t)fd) < 0) { + int errsv = errno; + close(loop_fd); + loop_fd = -1; + if (errsv != EBUSY) { + close(fd); + fprintf(stderr, "cannot set up loopback device %s: %s", buf, strerror(errsv)); + return NULL; + } + continue; + } + + close(fd); + + strncpy((char*)loopinfo.lo_file_name, buf, LO_NAME_SIZE); + loopinfo.lo_offset = 0; + loopinfo.lo_flags = LO_FLAGS_AUTOCLEAR; + + if (ioctl(loop_fd, LOOP_SET_STATUS64, &loopinfo) < 0) { + perror("ioctl LOOP_SET_STATUS64"); + if (ioctl(loop_fd, LOOP_CLR_FD, 0) < 0) { + perror("ioctl LOOP_CLR_FD"); + } + close(loop_fd); + fprintf (stderr, "cannot set up loopback device info"); + return (NULL); + } + + loopname = strdup(buf); + if (loopname == NULL) { + close(loop_fd); + return (NULL); + } + + *loop_fd_out = loop_fd; + return (loopname); + } + + return (NULL); +} + +static int64_t get_block_size(int fd) +{ + uint64_t size; + + if (ioctl(fd, BLKGETSIZE64, &size) == -1) + return -1; + return ((int64_t)size); +} + +extern void DevmapperLogCallback(int level, char *file, int line, int dm_errno_or_class, char *str); + +static void +log_cb(int level, const char *file, int line, + int dm_errno_or_class, const char *f, ...) +{ + char buffer[256]; + va_list ap; + + va_start(ap, f); + vsnprintf(buffer, 256, f, ap); + va_end(ap); + + DevmapperLogCallback(level, (char *)file, line, dm_errno_or_class, buffer); +} + +static void +log_with_errno_init () +{ + dm_log_with_errno_init(log_cb); +} + +*/ +import "C" + +import ( + "syscall" + "unsafe" +) + +type ( + CDmTask C.struct_dm_task +) + +var ( + DmTaskDestory = dmTaskDestroyFct + DmTaskCreate = dmTaskCreateFct + DmTaskRun = dmTaskRunFct + DmTaskSetName = dmTaskSetNameFct + DmTaskSetMessage = dmTaskSetMessageFct + DmTaskSetSector = dmTaskSetSectorFct + DmTaskSetCookie = dmTaskSetCookieFct + DmTaskSetAddNode = dmTaskSetAddNodeFct + DmTaskSetRo = dmTaskSetRoFct + DmTaskAddTarget = dmTaskAddTargetFct + DmTaskGetDriverVersion = dmTaskGetDriverVersionFct + DmTaskGetInfo = dmTaskGetInfoFct + DmGetNextTarget = dmGetNextTargetFct + DmAttachLoopDevice = dmAttachLoopDeviceFct + SysGetBlockSize = sysGetBlockSizeFct + DmGetBlockSize = dmGetBlockSizeFct + DmUdevWait = dmUdevWaitFct + DmLogInitVerbose = dmLogInitVerboseFct + LogWithErrnoInit = logWithErrnoInitFct + DmSetDevDir = dmSetDevDirFct + DmGetLibraryVersion = dmGetLibraryVersionFct +) + +func free(p *C.char) { + C.free(unsafe.Pointer(p)) +} + +func dmTaskDestroyFct(task *CDmTask) { + C.dm_task_destroy((*C.struct_dm_task)(task)) +} + +func dmTaskCreateFct(taskType int) *CDmTask { + return (*CDmTask)(C.dm_task_create(C.int(taskType))) +} + +func dmTaskRunFct(task *CDmTask) int { + return int(C.dm_task_run((*C.struct_dm_task)(task))) +} + +func dmTaskSetNameFct(task *CDmTask, name string) int { + Cname := C.CString(name) + defer free(Cname) + + return int(C.dm_task_set_name((*C.struct_dm_task)(task), + Cname)) +} + +func dmTaskSetMessageFct(task *CDmTask, message string) int { + Cmessage := C.CString(message) + defer free(Cmessage) + + return int(C.dm_task_set_message((*C.struct_dm_task)(task), + Cmessage)) +} + +func dmTaskSetSectorFct(task *CDmTask, sector uint64) int { + return int(C.dm_task_set_sector((*C.struct_dm_task)(task), + C.uint64_t(sector))) +} + +func dmTaskSetCookieFct(task *CDmTask, cookie *uint, flags uint16) int { + cCookie := C.uint32_t(*cookie) + defer func() { + *cookie = uint(cCookie) + }() + return int(C.dm_task_set_cookie((*C.struct_dm_task)(task), &cCookie, + C.uint16_t(flags))) +} + +func dmTaskSetAddNodeFct(task *CDmTask, addNode AddNodeType) int { + return int(C.dm_task_set_add_node((*C.struct_dm_task)(task), + C.dm_add_node_t(addNode))) +} + +func dmTaskSetRoFct(task *CDmTask) int { + return int(C.dm_task_set_ro((*C.struct_dm_task)(task))) +} + +func dmTaskAddTargetFct(task *CDmTask, + start, size uint64, ttype, params string) int { + + Cttype := C.CString(ttype) + defer free(Cttype) + + Cparams := C.CString(params) + defer free(Cparams) + + return int(C.dm_task_add_target((*C.struct_dm_task)(task), + C.uint64_t(start), C.uint64_t(size), Cttype, Cparams)) +} + +func dmTaskGetDriverVersionFct(task *CDmTask, version *string) int { + buffer := C.CString(string(make([]byte, 128))) + defer free(buffer) + defer func() { + *version = C.GoString(buffer) + }() + return int(C.dm_task_get_driver_version((*C.struct_dm_task)(task), + buffer, 128)) +} + +func dmTaskGetInfoFct(task *CDmTask, info *Info) int { + Cinfo := C.struct_dm_info{} + defer func() { + info.Exists = int(Cinfo.exists) + info.Suspended = int(Cinfo.suspended) + info.LiveTable = int(Cinfo.live_table) + info.InactiveTable = int(Cinfo.inactive_table) + info.OpenCount = int32(Cinfo.open_count) + info.EventNr = uint32(Cinfo.event_nr) + info.Major = uint32(Cinfo.major) + info.Minor = uint32(Cinfo.minor) + info.ReadOnly = int(Cinfo.read_only) + info.TargetCount = int32(Cinfo.target_count) + }() + return int(C.dm_task_get_info((*C.struct_dm_task)(task), &Cinfo)) +} + +func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64, + target, params *string) uintptr { + + var ( + Cstart, Clength C.uint64_t + CtargetType, Cparams *C.char + ) + defer func() { + *start = uint64(Cstart) + *length = uint64(Clength) + *target = C.GoString(CtargetType) + *params = C.GoString(Cparams) + }() + nextp := C.dm_get_next_target((*C.struct_dm_task)(task), + unsafe.Pointer(next), &Cstart, &Clength, &CtargetType, &Cparams) + return uintptr(nextp) +} + +func dmAttachLoopDeviceFct(filename string, fd *int) string { + cFilename := C.CString(filename) + defer free(cFilename) + + var cFd C.int + defer func() { + *fd = int(cFd) + }() + + ret := C.attach_loop_device(cFilename, &cFd) + defer free(ret) + return C.GoString(ret) +} + +// sysGetBlockSizeFct retrieves the block size from IOCTL +func sysGetBlockSizeFct(fd uintptr, size *uint64) syscall.Errno { + _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.BLKGETSIZE64, + uintptr(unsafe.Pointer(&size))) + return err +} + +// dmGetBlockSizeFct retrieves the block size from library call +func dmGetBlockSizeFct(fd uintptr) int64 { + return int64(C.get_block_size(C.int(fd))) +} + +func dmUdevWaitFct(cookie uint) int { + return int(C.dm_udev_wait(C.uint32_t(cookie))) +} + +func dmLogInitVerboseFct(level int) { + C.dm_log_init_verbose(C.int(level)) +} + +func logWithErrnoInitFct() { + C.log_with_errno_init() +} + +func dmSetDevDirFct(dir string) int { + Cdir := C.CString(dir) + defer free(Cdir) + + return int(C.dm_set_dev_dir(Cdir)) +} + +func dmGetLibraryVersionFct(version *string) int { + buffer := C.CString(string(make([]byte, 128))) + defer free(buffer) + defer func() { + *version = C.GoString(buffer) + }() + return int(C.dm_get_library_version(buffer, 128)) +} From 1b8dd1a73f409329484eda683aaa9cbad8c9f691 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 13 Nov 2013 14:54:47 -0800 Subject: [PATCH 238/301] Make sure setInitialized is called when device is mounted Upstream-commit: 80e73195583c5a9bf02c58ba92832a0bb2819361 Component: engine --- components/engine/devmapper/deviceset.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/components/engine/devmapper/deviceset.go b/components/engine/devmapper/deviceset.go index e7e57ab6a4..47ae2241a5 100644 --- a/components/engine/devmapper/deviceset.go +++ b/components/engine/devmapper/deviceset.go @@ -679,7 +679,7 @@ func (devices *DeviceSet) MountDevice(hash, path string, readOnly bool) error { count := devices.activeMounts[path] devices.activeMounts[path] = count + 1 - return nil + return devices.setInitialized(hash) } func (devices *DeviceSet) UnmountDevice(hash, path string, deactivate bool) error { @@ -740,10 +740,7 @@ func (devices *DeviceSet) HasActivatedDevice(hash string) bool { return devinfo != nil && devinfo.Exists != 0 } -func (devices *DeviceSet) SetInitialized(hash string) error { - devices.Lock() - defer devices.Unlock() - +func (devices *DeviceSet) setInitialized(hash string) error { info := devices.Devices[hash] if info == nil { return fmt.Errorf("Unknown device %s", hash) From dd83a25360479938c7b983b824e3440a2a767e04 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 13 Nov 2013 14:56:26 -0800 Subject: [PATCH 239/301] Add test coverage for devicemapper driver.go Upstream-commit: 6b3dd02bb8068fd9f1d35e75db95d0650a1d3123 Component: engine --- components/engine/devmapper/driver.go | 10 +- components/engine/devmapper/driver_test.go | 260 ++++++++++++++++++++- 2 files changed, 264 insertions(+), 6 deletions(-) diff --git a/components/engine/devmapper/driver.go b/components/engine/devmapper/driver.go index b4a454e6a9..6123b4c3e4 100644 --- a/components/engine/devmapper/driver.go +++ b/components/engine/devmapper/driver.go @@ -22,7 +22,7 @@ type Driver struct { } func Init(home string) (graphdriver.Driver, error) { - deviceSet, err := NewDeviceSet(home); + deviceSet, err := NewDeviceSet(home) if err != nil { return nil, err } @@ -61,17 +61,17 @@ func (d *Driver) Size(id string) (int64, error) { return -1, fmt.Errorf("Not implemented") } -func (d *Driver) mount(id, mp string) error { +func (d *Driver) mount(id, mountPoint string) error { // Create the target directories if they don't exist - if err := os.MkdirAll(mp, 0755); err != nil && !os.IsExist(err) { + if err := os.MkdirAll(mountPoint, 0755); err != nil && !os.IsExist(err) { return err } // If mountpoint is already mounted, do nothing - if mounted, err := Mounted(mp); err != nil { + if mounted, err := Mounted(mountPoint); err != nil { return fmt.Errorf("Error checking mountpoint: %s", err) } else if mounted { return nil } // Mount the device - return d.DeviceSet.MountDevice(id, mp, false) + return d.DeviceSet.MountDevice(id, mountPoint, false) } diff --git a/components/engine/devmapper/driver_test.go b/components/engine/devmapper/driver_test.go index dadf2715ec..c3c710fb31 100644 --- a/components/engine/devmapper/driver_test.go +++ b/components/engine/devmapper/driver_test.go @@ -3,9 +3,18 @@ package devmapper import ( "io/ioutil" "os" + "path" "testing" ) +func init() { + // Reduce the size the the base fs and loopback for the tests + DefaultDataLoopbackSize = 300 * 1024 * 1024 + DefaultMetaDataLoopbackSize = 200 * 1024 * 1024 + DefaultBaseFsSize = 300 * 1024 * 1024 + +} + func mkTestDirectory(t *testing.T) string { dir, err := ioutil.TempDir("", "docker-test-devmapper-") if err != nil { @@ -14,6 +23,20 @@ func mkTestDirectory(t *testing.T) string { return dir } +func newDriver(t *testing.T) *Driver { + home := mkTestDirectory(t) + d, err := Init(home) + if err != nil { + t.Fatal(err) + } + return d.(*Driver) +} + +func cleanup(d *Driver) { + d.Cleanup() + os.RemoveAll(d.home) +} + func TestInit(t *testing.T) { home := mkTestDirectory(t) defer os.RemoveAll(home) @@ -22,11 +45,11 @@ func TestInit(t *testing.T) { t.Fatal(err) } defer func() { - return if err := driver.Cleanup(); err != nil { t.Fatal(err) } }() + id := "foo" if err := driver.Create(id, ""); err != nil { t.Fatal(err) @@ -41,3 +64,238 @@ func TestInit(t *testing.T) { t.Fatalf("Get(%V) did not return a directory", id) } } + +func TestDriverName(t *testing.T) { + d := newDriver(t) + defer cleanup(d) + + if d.String() != "devicemapper" { + t.Fatalf("Expected driver name to be devicemapper got %s", d.String()) + } +} + +func TestDriverCreate(t *testing.T) { + d := newDriver(t) + defer cleanup(d) + + if err := d.Create("1", ""); err != nil { + t.Fatal(err) + } +} + +func TestDriverRemove(t *testing.T) { + d := newDriver(t) + defer cleanup(d) + + if err := d.Create("1", ""); err != nil { + t.Fatal(err) + } + + if err := d.Remove("1"); err != nil { + t.Fatal(err) + } +} + +func TestCleanup(t *testing.T) { + d := newDriver(t) + defer os.RemoveAll(d.home) + + mountPoints := make([]string, 2) + + if err := d.Create("1", ""); err != nil { + t.Fatal(err) + } + // Mount the id + p, err := d.Get("1") + if err != nil { + t.Fatal(err) + } + mountPoints[0] = p + + if err := d.Create("2", "1"); err != nil { + t.Fatal(err) + } + + p, err = d.Get("2") + if err != nil { + t.Fatal(err) + } + mountPoints[1] = p + + // Ensure that all the mount points are currently mounted + for _, p := range mountPoints { + if mounted, err := Mounted(p); err != nil { + t.Fatal(err) + } else if !mounted { + t.Fatalf("Expected %s to be mounted", p) + } + } + + // Ensure that devices are active + for _, p := range []string{"1", "2"} { + if !d.HasActivatedDevice(p) { + t.Fatalf("Expected %s to have an active device", p) + } + } + + if err := d.Cleanup(); err != nil { + t.Fatal(err) + } + + // Ensure that all the mount points are no longer mounted + for _, p := range mountPoints { + if mounted, err := Mounted(p); err != nil { + t.Fatal(err) + } else if mounted { + t.Fatalf("Expected %s to not be mounted", p) + } + } + + // Ensure that devices are no longer activated + for _, p := range []string{"1", "2"} { + if d.HasActivatedDevice(p) { + t.Fatalf("Expected %s not be an active device", p) + } + } +} + +func TestNotMounted(t *testing.T) { + d := newDriver(t) + defer cleanup(d) + + if err := d.Create("1", ""); err != nil { + t.Fatal(err) + } + + mounted, err := Mounted(path.Join(d.home, "mnt", "1")) + if err != nil { + t.Fatal(err) + } + if mounted { + t.Fatal("Id 1 should not be mounted") + } +} + +func TestMounted(t *testing.T) { + d := newDriver(t) + defer cleanup(d) + + if err := d.Create("1", ""); err != nil { + t.Fatal(err) + } + if _, err := d.Get("1"); err != nil { + t.Fatal(err) + } + + mounted, err := Mounted(path.Join(d.home, "mnt", "1")) + if err != nil { + t.Fatal(err) + } + if !mounted { + t.Fatal("Id 1 should be mounted") + } +} + +func TestInitCleanedDriver(t *testing.T) { + d := newDriver(t) + + if err := d.Create("1", ""); err != nil { + t.Fatal(err) + } + if _, err := d.Get("1"); err != nil { + t.Fatal(err) + } + + if err := d.Cleanup(); err != nil { + t.Fatal(err) + } + + driver, err := Init(d.home) + if err != nil { + t.Fatal(err) + } + d = driver.(*Driver) + defer cleanup(d) + + if _, err := d.Get("1"); err != nil { + t.Fatal(err) + } +} + +func TestMountMountedDriver(t *testing.T) { + d := newDriver(t) + defer cleanup(d) + + if err := d.Create("1", ""); err != nil { + t.Fatal(err) + } + + // Perform get on same id to ensure that it will + // not be mounted twice + if _, err := d.Get("1"); err != nil { + t.Fatal(err) + } + if _, err := d.Get("1"); err != nil { + t.Fatal(err) + } +} + +func TestGetReturnsValidDevice(t *testing.T) { + d := newDriver(t) + defer cleanup(d) + + if err := d.Create("1", ""); err != nil { + t.Fatal(err) + } + + if !d.HasDevice("1") { + t.Fatalf("Expected id 1 to be in device set") + } + + if _, err := d.Get("1"); err != nil { + t.Fatal(err) + } + + if !d.HasActivatedDevice("1") { + t.Fatalf("Expected id 1 to be activated") + } + + if !d.HasInitializedDevice("1") { + t.Fatalf("Expected id 1 to be initialized") + } +} + +func TestDriverGetSize(t *testing.T) { + t.Skipf("Size is currently not implemented") + + d := newDriver(t) + defer cleanup(d) + + if err := d.Create("1", ""); err != nil { + t.Fatal(err) + } + + mountPoint, err := d.Get("1") + if err != nil { + t.Fatal(err) + } + + size := int64(1024) + + f, err := os.Create(path.Join(mountPoint, "test_file")) + if err != nil { + t.Fatal(err) + } + if err := f.Truncate(size); err != nil { + t.Fatal(err) + } + f.Close() + + diffSize, err := d.Size("1") + if err != nil { + t.Fatal(err) + } + if diffSize != size { + t.Fatalf("Expected size %d got %d", size, diffSize) + } +} From e69802932f453333aab8a16319ba3db4792cb186 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 13 Nov 2013 15:35:52 -0800 Subject: [PATCH 240/301] Add devmapper_test.go Upstream-commit: 4bebca848ea3546cd5813ab4c13b0248050454d8 Component: engine --- components/engine/devmapper/devmapper.go | 10 +- components/engine/devmapper/devmapper_test.go | 221 ++++++++++++++++++ .../engine/devmapper/devmapper_wrapper.go | 8 +- 3 files changed, 232 insertions(+), 7 deletions(-) create mode 100644 components/engine/devmapper/devmapper_test.go diff --git a/components/engine/devmapper/devmapper.go b/components/engine/devmapper/devmapper.go index b4e8118431..d6e3720129 100644 --- a/components/engine/devmapper/devmapper.go +++ b/components/engine/devmapper/devmapper.go @@ -46,8 +46,11 @@ var ( ErrTaskSetAddNode = errors.New("dm_task_set_add_node failed") ErrTaskSetRO = errors.New("dm_task_set_ro failed") ErrTaskAddTarget = errors.New("dm_task_add_target failed") + ErrTaskSetSector = errors.New("dm_task_set_sector failed") ErrGetInfo = errors.New("dm_task_get_info failed") ErrGetDriverVersion = errors.New("dm_task_get_driver_version failed") + ErrTaskSetCookie = errors.New("dm_task_set_cookie failed") + ErrNilCookie = errors.New("cookie ptr can't be nil") ErrAttachLoopbackDevice = errors.New("loopback mounting failed") ErrGetBlockSize = errors.New("Can't get block size") ErrUdevWait = errors.New("wait on udev cookie failed") @@ -117,14 +120,17 @@ func (t *Task) SetMessage(message string) error { func (t *Task) SetSector(sector uint64) error { if res := DmTaskSetSector(t.unmanaged, sector); res != 1 { - return ErrTaskSetAddNode + return ErrTaskSetSector } return nil } func (t *Task) SetCookie(cookie *uint, flags uint16) error { + if cookie == nil { + return ErrNilCookie + } if res := DmTaskSetCookie(t.unmanaged, cookie, flags); res != 1 { - return ErrTaskSetAddNode + return ErrTaskSetCookie } return nil } diff --git a/components/engine/devmapper/devmapper_test.go b/components/engine/devmapper/devmapper_test.go new file mode 100644 index 0000000000..81a793c48c --- /dev/null +++ b/components/engine/devmapper/devmapper_test.go @@ -0,0 +1,221 @@ +package devmapper + +import ( + "syscall" + "testing" +) + +func TestTaskCreate(t *testing.T) { + // Test success + taskCreate(t, DeviceInfo) + + // Test Failure + DmTaskCreate = dmTaskCreateFail + defer func() { DmTaskCreate = dmTaskCreateFct }() + if task := TaskCreate(-1); task != nil { + t.Fatalf("An error should have occured while creating an invalid task.") + } +} + +func TestTaskRun(t *testing.T) { + task := taskCreate(t, DeviceInfo) + + // Test success + // Perform the RUN + if err := task.Run(); err != nil { + t.Fatal(err) + } + // Make sure we don't have error with GetInfo + if _, err := task.GetInfo(); err != nil { + t.Fatal(err) + } + + // Test failure + DmTaskRun = dmTaskRunFail + defer func() { DmTaskRun = dmTaskRunFct }() + + task = taskCreate(t, DeviceInfo) + // Perform the RUN + if err := task.Run(); err == nil { + t.Fatalf("An error should have occured while running task.") + } + // Make sure GetInfo also fails + if _, err := task.GetInfo(); err == nil { + t.Fatalf("GetInfo should fail if task.Run() failed.") + } +} + +func TestTaskSetName(t *testing.T) { + task := taskCreate(t, DeviceInfo) + + // Test success + if err := task.SetName("test"); err != nil { + t.Fatal(err) + } + + // Test failure + DmTaskSetName = dmTaskSetNameFail + defer func() { DmTaskSetName = dmTaskSetNameFct }() + if err := task.SetName("test"); err != ErrTaskSetName { + t.Fatalf("An error should have occured while runnign SetName.") + } +} + +func TestTaskSetMessage(t *testing.T) { + task := taskCreate(t, DeviceInfo) + + // Test success + if err := task.SetMessage("test"); err != nil { + t.Fatal(err) + } + + // Test failure + DmTaskSetMessage = dmTaskSetMessageFail + defer func() { DmTaskSetMessage = dmTaskSetMessageFct }() + if err := task.SetMessage("test"); err != ErrTaskSetMessage { + t.Fatalf("An error should have occured while runnign SetMessage.") + } +} + +func TestTaskSetSector(t *testing.T) { + task := taskCreate(t, DeviceInfo) + + // Test success + if err := task.SetSector(128); err != nil { + t.Fatal(err) + } + + DmTaskSetSector = dmTaskSetSectorFail + defer func() { DmTaskSetSector = dmTaskSetSectorFct }() + + // Test failure + if err := task.SetSector(0); err != ErrTaskSetSector { + t.Fatalf("An error should have occured while running SetSector.") + } +} + +func TestTaskSetCookie(t *testing.T) { + var ( + cookie uint = 0 + task = taskCreate(t, DeviceInfo) + ) + + // Test success + if err := task.SetCookie(&cookie, 0); err != nil { + t.Fatal(err) + } + + // Test failure + if err := task.SetCookie(nil, 0); err != ErrNilCookie { + t.Fatalf("An error should have occured while running SetCookie with nil cookie.") + } + + DmTaskSetCookie = dmTaskSetCookieFail + defer func() { DmTaskSetCookie = dmTaskSetCookieFct }() + + if err := task.SetCookie(&cookie, 0); err != ErrTaskSetCookie { + t.Fatalf("An error should have occured while running SetCookie.") + } +} + +func TestTaskSetAddNode(t *testing.T) { + task := taskCreate(t, DeviceInfo) + if err := task.SetAddNode(0); err != nil { + t.Fatal(err) + } +} + +func TestTaskSetRo(t *testing.T) { + task := taskCreate(t, DeviceInfo) + if err := task.SetRo(); err != nil { + t.Fatal(err) + } +} + +func TestTaskAddTarget(t *testing.T) { + // task := taskCreate(t, DeviceInfo) +} + +/// Utils +func taskCreate(t *testing.T, taskType TaskType) *Task { + task := TaskCreate(taskType) + if task == nil { + t.Fatalf("Error creating task") + } + return task +} + +/// Failure function replacement +func dmTaskCreateFail(t int) *CDmTask { + return nil +} + +func dmTaskRunFail(task *CDmTask) int { + return -1 +} + +func dmTaskSetNameFail(task *CDmTask, name string) int { + return -1 +} + +func dmTaskSetMessageFail(task *CDmTask, message string) int { + return -1 +} + +func dmTaskSetSectorFail(task *CDmTask, sector uint64) int { + return -1 +} + +func dmTaskSetCookieFail(task *CDmTask, cookie *uint, flags uint16) int { + return -1 +} + +func dmTaskSetAddNodeFail(task *CDmTask, addNode AddNodeType) int { + return -1 +} + +func dmTaskSetRoFail(task *CDmTask) int { + return -1 +} + +func dmTaskAddTargetFail(task *CDmTask, + start, size uint64, ttype, params string) int { + return -1 +} + +func dmTaskGetDriverVersionFail(task *CDmTask, version *string) int { + return -1 +} + +func dmTaskGetInfoFail(task *CDmTask, info *Info) int { + return -1 +} + +func dmGetNextTargetFail(task *CDmTask, next uintptr, start, length *uint64, + target, params *string) uintptr { + return 0 +} + +func dmAttachLoopDeviceFail(filename string, fd *int) string { + return "" +} + +func sysGetBlockSizeFail(fd uintptr, size *uint64) syscall.Errno { + return 1 +} + +func dmGetBlockSizeFail(fd uintptr) int64 { + return -1 +} + +func dmUdevWaitFail(cookie uint) int { + return -1 +} + +func dmSetDevDirFail(dir string) int { + return -1 +} + +func dmGetLibraryVersionFail(version *string) int { + return -1 +} diff --git a/components/engine/devmapper/devmapper_wrapper.go b/components/engine/devmapper/devmapper_wrapper.go index d6ec7ae7fb..a52d2992b4 100644 --- a/components/engine/devmapper/devmapper_wrapper.go +++ b/components/engine/devmapper/devmapper_wrapper.go @@ -127,9 +127,8 @@ static int64_t get_block_size(int fd) extern void DevmapperLogCallback(int level, char *file, int line, int dm_errno_or_class, char *str); -static void -log_cb(int level, const char *file, int line, - int dm_errno_or_class, const char *f, ...) +static void log_cb(int level, const char *file, int line, + int dm_errno_or_class, const char *f, ...) { char buffer[256]; va_list ap; @@ -141,8 +140,7 @@ log_cb(int level, const char *file, int line, DevmapperLogCallback(level, (char *)file, line, dm_errno_or_class, buffer); } -static void -log_with_errno_init () +static void log_with_errno_init() { dm_log_with_errno_init(log_cb); } From ef863f235c97411a38a45d311b06e1eb7fef9fb9 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 13 Nov 2013 16:46:10 -0800 Subject: [PATCH 241/301] More dm unit tests Upstream-commit: 05d70cbcf4ebf30b11d54a177e47649e4e229403 Component: engine --- components/engine/devmapper/devmapper.go | 54 ++++++-------- components/engine/devmapper/devmapper_test.go | 70 +++++++++++++++++- .../engine/devmapper/devmapper_wrapper.go | 71 +++++++------------ 3 files changed, 113 insertions(+), 82 deletions(-) diff --git a/components/engine/devmapper/devmapper.go b/components/engine/devmapper/devmapper.go index d6e3720129..54e21adddb 100644 --- a/components/engine/devmapper/devmapper.go +++ b/components/engine/devmapper/devmapper.go @@ -44,11 +44,11 @@ var ( ErrTaskSetName = errors.New("dm_task_set_name failed") ErrTaskSetMessage = errors.New("dm_task_set_message failed") ErrTaskSetAddNode = errors.New("dm_task_set_add_node failed") - ErrTaskSetRO = errors.New("dm_task_set_ro failed") + ErrTaskSetRo = errors.New("dm_task_set_ro failed") ErrTaskAddTarget = errors.New("dm_task_add_target failed") ErrTaskSetSector = errors.New("dm_task_set_sector failed") - ErrGetInfo = errors.New("dm_task_get_info failed") - ErrGetDriverVersion = errors.New("dm_task_get_driver_version failed") + ErrTaskGetInfo = errors.New("dm_task_get_info failed") + ErrTaskGetDriverVersion = errors.New("dm_task_get_driver_version failed") ErrTaskSetCookie = errors.New("dm_task_set_cookie failed") ErrNilCookie = errors.New("cookie ptr can't be nil") ErrAttachLoopbackDevice = errors.New("loopback mounting failed") @@ -58,6 +58,7 @@ var ( ErrGetLibraryVersion = errors.New("dm_get_library_version failed") ErrCreateRemoveTask = errors.New("Can't create task of type DeviceRemove") ErrRunRemoveDevice = errors.New("running removeDevice failed") + ErrInvalidAddNode = errors.New("Invalide AddNoce type") ) type ( @@ -136,6 +137,9 @@ func (t *Task) SetCookie(cookie *uint, flags uint16) error { } func (t *Task) SetAddNode(addNode AddNodeType) error { + if addNode != AddNodeOnResume && addNode != AddNodeOnCreate { + return ErrInvalidAddNode + } if res := DmTaskSetAddNode(t.unmanaged, addNode); res != 1 { return ErrTaskSetAddNode } @@ -144,7 +148,7 @@ func (t *Task) SetAddNode(addNode AddNodeType) error { func (t *Task) SetRo() error { if res := DmTaskSetRo(t.unmanaged); res != 1 { - return ErrTaskSetRO + return ErrTaskSetRo } return nil } @@ -157,18 +161,10 @@ func (t *Task) AddTarget(start, size uint64, ttype, params string) error { return nil } -func (t *Task) GetDriverVersion() (string, error) { - var version string - if res := DmTaskGetDriverVersion(t.unmanaged, &version); res != 1 { - return "", ErrGetDriverVersion - } - return version, nil -} - func (t *Task) GetInfo() (*Info, error) { info := &Info{} if res := DmTaskGetInfo(t.unmanaged, info); res != 1 { - return nil, ErrGetInfo + return nil, ErrTaskGetInfo } return info, nil } @@ -190,24 +186,6 @@ func AttachLoopDevice(filename string) (*os.File, error) { return os.NewFile(uintptr(fd), res), nil } -func getBlockSize(fd uintptr) int { - var size uint64 - - if err := SysGetBlockSize(fd, &size); err != 0 { - utils.Debugf("Error ioctl (getBlockSize: %s)", err) - return -1 - } - return int(size) -} - -func GetBlockDeviceSize(file *os.File) (uint64, error) { - size := DmGetBlockSize(file.Fd()) - if size == -1 { - return 0, ErrGetBlockSize - } - return uint64(size), nil -} - func UdevWait(cookie uint) error { if res := DmUdevWait(cookie); res != 1 { utils.Debugf("Failed to wait on udev cookie %d", cookie) @@ -259,6 +237,14 @@ func RemoveDevice(name string) error { return nil } +func GetBlockDeviceSize(file *os.File) (uint64, error) { + size, errno := DmGetBlockSize(file.Fd()) + if size == -1 || errno != 0 { + return 0, ErrGetBlockSize + } + return uint64(size), nil +} + // This is the programmatic example of "dmsetup create" func createPool(poolName string, dataFile *os.File, metadataFile *os.File) error { task, err := createTask(DeviceCreate, poolName) @@ -282,7 +268,7 @@ func createPool(poolName string, dataFile *os.File, metadataFile *os.File) error } if err := task.Run(); err != nil { - return fmt.Errorf("Error running DeviceCreate") + return fmt.Errorf("Error running DeviceCreate (createPool)") } UdevWait(cookie) @@ -462,7 +448,7 @@ func activateDevice(poolName string, name string, deviceId int, size uint64) err } if err := task.Run(); err != nil { - return fmt.Errorf("Error running DeviceCreate") + return fmt.Errorf("Error running DeviceCreate (activateDevice)") } UdevWait(cookie) @@ -506,7 +492,7 @@ func (devices *DeviceSet) createSnapDevice(poolName string, deviceId int, baseNa if doSuspend { resumeDevice(baseName) } - return fmt.Errorf("Error running DeviceCreate") + return fmt.Errorf("Error running DeviceCreate (createSnapDevice)") } if doSuspend { diff --git a/components/engine/devmapper/devmapper_test.go b/components/engine/devmapper/devmapper_test.go index 81a793c48c..8d93ab30b1 100644 --- a/components/engine/devmapper/devmapper_test.go +++ b/components/engine/devmapper/devmapper_test.go @@ -36,11 +36,11 @@ func TestTaskRun(t *testing.T) { task = taskCreate(t, DeviceInfo) // Perform the RUN - if err := task.Run(); err == nil { + if err := task.Run(); err != ErrTaskRun { t.Fatalf("An error should have occured while running task.") } // Make sure GetInfo also fails - if _, err := task.GetInfo(); err == nil { + if _, err := task.GetInfo(); err != ErrTaskGetInfo { t.Fatalf("GetInfo should fail if task.Run() failed.") } } @@ -56,6 +56,7 @@ func TestTaskSetName(t *testing.T) { // Test failure DmTaskSetName = dmTaskSetNameFail defer func() { DmTaskSetName = dmTaskSetNameFct }() + if err := task.SetName("test"); err != ErrTaskSetName { t.Fatalf("An error should have occured while runnign SetName.") } @@ -72,6 +73,7 @@ func TestTaskSetMessage(t *testing.T) { // Test failure DmTaskSetMessage = dmTaskSetMessageFail defer func() { DmTaskSetMessage = dmTaskSetMessageFct }() + if err := task.SetMessage("test"); err != ErrTaskSetMessage { t.Fatalf("An error should have occured while runnign SetMessage.") } @@ -120,22 +122,84 @@ func TestTaskSetCookie(t *testing.T) { func TestTaskSetAddNode(t *testing.T) { task := taskCreate(t, DeviceInfo) + + // Test success if err := task.SetAddNode(0); err != nil { t.Fatal(err) } + + // Test failure + if err := task.SetAddNode(-1); err != ErrInvalidAddNode { + t.Fatalf("An error should have occured running SetAddNode with wrong node.") + } + + DmTaskSetAddNode = dmTaskSetAddNodeFail + defer func() { DmTaskSetAddNode = dmTaskSetAddNodeFct }() + + if err := task.SetAddNode(0); err != ErrTaskSetAddNode { + t.Fatalf("An error should have occured running SetAddNode.") + } } func TestTaskSetRo(t *testing.T) { task := taskCreate(t, DeviceInfo) + + // Test success if err := task.SetRo(); err != nil { t.Fatal(err) } + + // Test failure + DmTaskSetRo = dmTaskSetRoFail + defer func() { DmTaskSetRo = dmTaskSetRoFct }() + + if err := task.SetRo(); err != ErrTaskSetRo { + t.Fatalf("An error should have occured running SetRo.") + } } func TestTaskAddTarget(t *testing.T) { - // task := taskCreate(t, DeviceInfo) + task := taskCreate(t, DeviceInfo) + + // Test success + if err := task.AddTarget(0, 128, "thinp", ""); err != nil { + t.Fatal(err) + } + + // Test failure + DmTaskAddTarget = dmTaskAddTargetFail + defer func() { DmTaskAddTarget = dmTaskAddTargetFct }() + + if err := task.AddTarget(0, 128, "thinp", ""); err != ErrTaskAddTarget { + t.Fatalf("An error should have occured running AddTarget.") + } } +// func TestTaskGetInfo(t *testing.T) { +// task := taskCreate(t, DeviceInfo) + +// // Test success +// if _, err := task.GetInfo(); err != nil { +// t.Fatal(err) +// } + +// // Test failure +// DmTaskGetInfo = dmTaskGetInfoFail +// defer func() { DmTaskGetInfo = dmTaskGetInfoFct }() + +// if _, err := task.GetInfo(); err != ErrTaskGetInfo { +// t.Fatalf("An error should have occured running GetInfo.") +// } +// } + +// func TestTaskGetNextTarget(t *testing.T) { +// task := taskCreate(t, DeviceInfo) + +// if next, _, _, _, _ := task.GetNextTarget(0); next == 0 { +// t.Fatalf("The next target should not be 0.") +// } +// } + /// Utils func taskCreate(t *testing.T, taskType TaskType) *Task { task := TaskCreate(taskType) diff --git a/components/engine/devmapper/devmapper_wrapper.go b/components/engine/devmapper/devmapper_wrapper.go index a52d2992b4..fcd125e6c5 100644 --- a/components/engine/devmapper/devmapper_wrapper.go +++ b/components/engine/devmapper/devmapper_wrapper.go @@ -116,15 +116,6 @@ char* attach_loop_device(const char *filename, int *loop_fd_out) return (NULL); } -static int64_t get_block_size(int fd) -{ - uint64_t size; - - if (ioctl(fd, BLKGETSIZE64, &size) == -1) - return -1; - return ((int64_t)size); -} - extern void DevmapperLogCallback(int level, char *file, int line, int dm_errno_or_class, char *str); static void log_cb(int level, const char *file, int line, @@ -158,27 +149,26 @@ type ( ) var ( - DmTaskDestory = dmTaskDestroyFct - DmTaskCreate = dmTaskCreateFct - DmTaskRun = dmTaskRunFct - DmTaskSetName = dmTaskSetNameFct - DmTaskSetMessage = dmTaskSetMessageFct - DmTaskSetSector = dmTaskSetSectorFct - DmTaskSetCookie = dmTaskSetCookieFct - DmTaskSetAddNode = dmTaskSetAddNodeFct - DmTaskSetRo = dmTaskSetRoFct - DmTaskAddTarget = dmTaskAddTargetFct - DmTaskGetDriverVersion = dmTaskGetDriverVersionFct - DmTaskGetInfo = dmTaskGetInfoFct - DmGetNextTarget = dmGetNextTargetFct - DmAttachLoopDevice = dmAttachLoopDeviceFct - SysGetBlockSize = sysGetBlockSizeFct - DmGetBlockSize = dmGetBlockSizeFct - DmUdevWait = dmUdevWaitFct - DmLogInitVerbose = dmLogInitVerboseFct - LogWithErrnoInit = logWithErrnoInitFct - DmSetDevDir = dmSetDevDirFct - DmGetLibraryVersion = dmGetLibraryVersionFct + DmTaskDestory = dmTaskDestroyFct + DmTaskCreate = dmTaskCreateFct + DmTaskRun = dmTaskRunFct + DmTaskSetName = dmTaskSetNameFct + DmTaskSetMessage = dmTaskSetMessageFct + DmTaskSetSector = dmTaskSetSectorFct + DmTaskSetCookie = dmTaskSetCookieFct + DmTaskSetAddNode = dmTaskSetAddNodeFct + DmTaskSetRo = dmTaskSetRoFct + DmTaskAddTarget = dmTaskAddTargetFct + DmTaskGetInfo = dmTaskGetInfoFct + DmGetNextTarget = dmGetNextTargetFct + DmGetBlockSize = dmGetBlockSizeFct + DmAttachLoopDevice = dmAttachLoopDeviceFct + DmUdevWait = dmUdevWaitFct + DmLogInitVerbose = dmLogInitVerboseFct + DmSetDevDir = dmSetDevDirFct + DmGetLibraryVersion = dmGetLibraryVersionFct + LogWithErrnoInit = logWithErrnoInitFct + GetBlockSize = getBlockSizeFct ) func free(p *C.char) { @@ -249,14 +239,11 @@ func dmTaskAddTargetFct(task *CDmTask, C.uint64_t(start), C.uint64_t(size), Cttype, Cparams)) } -func dmTaskGetDriverVersionFct(task *CDmTask, version *string) int { - buffer := C.CString(string(make([]byte, 128))) - defer free(buffer) - defer func() { - *version = C.GoString(buffer) - }() - return int(C.dm_task_get_driver_version((*C.struct_dm_task)(task), - buffer, 128)) +func dmGetBlockSizeFct(fd uintptr) (int64, syscall.Errno) { + var size int64 + _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.BLKGETSIZE64, + uintptr(unsafe.Pointer(&size))) + return size, err } func dmTaskGetInfoFct(task *CDmTask, info *Info) int { @@ -308,18 +295,12 @@ func dmAttachLoopDeviceFct(filename string, fd *int) string { return C.GoString(ret) } -// sysGetBlockSizeFct retrieves the block size from IOCTL -func sysGetBlockSizeFct(fd uintptr, size *uint64) syscall.Errno { +func getBlockSizeFct(fd uintptr, size *uint64) syscall.Errno { _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.BLKGETSIZE64, uintptr(unsafe.Pointer(&size))) return err } -// dmGetBlockSizeFct retrieves the block size from library call -func dmGetBlockSizeFct(fd uintptr) int64 { - return int64(C.get_block_size(C.int(fd))) -} - func dmUdevWaitFct(cookie uint) int { return int(C.dm_udev_wait(C.uint32_t(cookie))) } From cfec1cc83c331bc017fc2ce54dc6ecb353c1cbf1 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 13 Nov 2013 17:05:32 -0800 Subject: [PATCH 242/301] Close sparse files when done Upstream-commit: bbc9fc79072bc259371aa96958336baaf8b53638 Component: engine --- components/engine/devmapper/deviceset.go | 1 + 1 file changed, 1 insertion(+) diff --git a/components/engine/devmapper/deviceset.go b/components/engine/devmapper/deviceset.go index 47ae2241a5..227bc8ad8c 100644 --- a/components/engine/devmapper/deviceset.go +++ b/components/engine/devmapper/deviceset.go @@ -120,6 +120,7 @@ func (devices *DeviceSet) ensureImage(name string, size int64) (string, error) { if err != nil { return "", err } + defer file.Close() if err = file.Truncate(size); err != nil { return "", err From 93ce091ee186f6c09918c6b35e3e976e96a76ca0 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 14 Nov 2013 09:42:12 -0800 Subject: [PATCH 243/301] Try to load aufs first then check in filesystems for aufs support Upstream-commit: 7b2d59b91e64e72e346747cf2e561be8afc87fa7 Component: engine --- components/engine/aufs/aufs.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/engine/aufs/aufs.go b/components/engine/aufs/aufs.go index 97c671207b..e0727440e2 100644 --- a/components/engine/aufs/aufs.go +++ b/components/engine/aufs/aufs.go @@ -76,6 +76,10 @@ func Init(root string) (graphdriver.Driver, error) { // We cannot modprobe because inside dind modprobe fails // to run func supportsAufs() error { + // We can try to modprobe aufs first before looking at + // proc/filesystems for when aufs is supported + exec.Command("modprobe", "aufs").Run() + f, err := os.Open("/proc/filesystems") if err != nil { return err From 39e4a0bc9e3a25b39083e21646a67e67939e3613 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 14 Nov 2013 22:56:18 +0100 Subject: [PATCH 244/301] ApplyLayer() use RemoveAll to handle removing directories rmTargetPath can be a directory, so we have to use RemoveAll() or we will fail to whiteout non-empty directories. Upstream-commit: 006e2a600ce689770ba2c49805bc4f634976f365 Component: engine --- components/engine/archive/diff.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/archive/diff.go b/components/engine/archive/diff.go index 0e4fe3fcc2..877d076c8b 100644 --- a/components/engine/archive/diff.go +++ b/components/engine/archive/diff.go @@ -49,7 +49,7 @@ func ApplyLayer(dest string, layer Archive) error { rmTargetPath := filepath.Join(filepath.Dir(fullPath), rmTargetName) // Remove the file targeted by the whiteout log.Printf("Removing whiteout target %s", rmTargetPath) - _ = os.Remove(rmTargetPath) + _ = os.RemoveAll(rmTargetPath) // Remove the whiteout itself log.Printf("Removing whiteout %s", fullPath) _ = os.RemoveAll(fullPath) From ea7ee7989a0eec43afbe24db278752379eeb8a6f Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 12 Nov 2013 16:59:37 -0800 Subject: [PATCH 245/301] Unlink docker init files Upstream-commit: 29fa1b6666f28e7f42e92a10d195823fa84b7622 Component: engine --- components/engine/graph.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/components/engine/graph.go b/components/engine/graph.go index a6c42b0ff8..31f21d8fbc 100644 --- a/components/engine/graph.go +++ b/components/engine/graph.go @@ -11,6 +11,7 @@ import ( "path" "path/filepath" "strings" + "syscall" "time" ) @@ -212,6 +213,13 @@ func setupInitLayer(initLayer string) error { // "var/run": "dir", // "var/lock": "dir", } { + parts := strings.Split(pth, "/") + prev := "/" + for _, p := range parts[1:] { + prev = path.Join(prev, p) + syscall.Unlink(path.Join(initLayer, prev)) + } + if _, err := os.Stat(path.Join(initLayer, pth)); err != nil { if os.IsNotExist(err) { switch typ { From b264af0c151e7e196164317c76d3dd0099da3c12 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 14 Nov 2013 22:52:08 -0800 Subject: [PATCH 246/301] Save driver to container and skip incompat containers For people who toggle drivers we want to save the driver used to create a container so that if the driver changes we can skip loading the container and it should not show up in docker ps Upstream-commit: 4908d7f81db91f4a28be152ec0cacb0cf711b403 Component: engine --- components/engine/container.go | 1 + components/engine/runtime.go | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/components/engine/container.go b/components/engine/container.go index 0e54abe52c..ed4e5f4777 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -47,6 +47,7 @@ type Container struct { HostnamePath string HostsPath string Name string + Driver string cmd *exec.Cmd stdout *utils.WriteBroadcaster diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 48d632b3e8..57e5498ff4 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -262,6 +262,7 @@ func (runtime *Runtime) restore() error { return err } containers := make(map[string]*Container) + currentDriver := runtime.driver.String() for i, v := range dir { id := v.Name() @@ -273,8 +274,14 @@ func (runtime *Runtime) restore() error { utils.Errorf("Failed to load container %v: %v", id, err) continue } - utils.Debugf("Loaded container %v", container.ID) - containers[container.ID] = container + + // Ignore the container if it does not support the current driver being used by the graph + if container.Driver == "" && currentDriver == "aufs" || container.Driver == currentDriver { + utils.Debugf("Loaded container %v", container.ID) + containers[container.ID] = container + } else { + utils.Debugf("Cannot load container %s because it was created with another graph driver.", container.ID) + } } register := func(container *Container) { @@ -445,6 +452,7 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin // FIXME: do we need to store this in the container? SysInitPath: sysInitPath, Name: name, + Driver: runtime.driver.String(), } container.root = runtime.containerRoot(container.ID) // Step 1: create the container directory. From b091059403d83c7f8f26a5e1d45f5744a217b2fd Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 14 Nov 2013 22:57:33 +0100 Subject: [PATCH 247/301] Use ExportChanges() in runtime.Diff() This code was duplicated in two places, one which was unused. This syncs the code and removes the unused version. Upstream-commit: d69a6a20f0b6657821638ee591920d071a784b0e Component: engine --- components/engine/archive/changes.go | 14 ++++++++------ components/engine/runtime.go | 21 +-------------------- 2 files changed, 9 insertions(+), 26 deletions(-) diff --git a/components/engine/archive/changes.go b/components/engine/archive/changes.go index d94d8bc33c..9305fe2311 100644 --- a/components/engine/archive/changes.go +++ b/components/engine/archive/changes.go @@ -280,11 +280,7 @@ func ChangesDirs(newDir, oldDir string) ([]Change, error) { return newRoot.Changes(oldRoot), nil } -func ExportChanges(root, rw string) (Archive, error) { - changes, err := ChangesDirs(root, rw) - if err != nil { - return nil, err - } +func ExportChanges(dir string, changes []Change) (Archive, error) { files := make([]string, 0) deletions := make([]string, 0) for _, change := range changes { @@ -297,5 +293,11 @@ func ExportChanges(root, rw string) (Archive, error) { deletions = append(deletions, filepath.Join(dir, ".wh."+base)) } } - return TarFilter(root, &TarOptions{Compression: Uncompressed, Recursive: false, Includes: files, CreateFiles: deletions}) + // FIXME: Why do we create whiteout files inside Tar code ? + return TarFilter(dir, &TarOptions{ + Compression: Uncompressed, + Includes: files, + Recursive: false, + CreateFiles: deletions, + }) } diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 1429d54a37..6835be5ea2 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -18,7 +18,6 @@ import ( "os" "os/exec" "path" - "path/filepath" "sort" "strings" "time" @@ -763,25 +762,7 @@ func (runtime *Runtime) Diff(container *Container) (archive.Archive, error) { return nil, fmt.Errorf("Error getting container rootfs %s from driver %s: %s", container.ID, container.runtime.driver, err) } - files := make([]string, 0) - deletions := make([]string, 0) - for _, change := range changes { - if change.Kind == archive.ChangeModify || change.Kind == archive.ChangeAdd { - files = append(files, change.Path) - } - if change.Kind == archive.ChangeDelete { - base := filepath.Base(change.Path) - dir := filepath.Dir(change.Path) - deletions = append(deletions, filepath.Join(dir, ".wh."+base)) - } - } - // FIXME: Why do we create whiteout files inside Tar code ? - return archive.TarFilter(cDir, &archive.TarOptions{ - Compression: archive.Uncompressed, - Includes: files, - Recursive: false, - CreateFiles: deletions, - }) + return archive.ExportChanges(cDir, changes) } func linkLxcStart(root string) error { From d9b9f3ddbd99bb1197c698b368ba0c26ced44bf1 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 14 Nov 2013 22:59:04 +0100 Subject: [PATCH 248/301] Add tests for the changes detection code This adds 3 tests: Verify that ChangesDirs() returns nothing when run on a copy of the same directory. Verify that various mutations of a directory get reported with the right changes. Verify that ExportChanges() + ApplyLayer() of the above mutation gets an identical directory. Unfortunately the last test is disabled because it fails in multiple ways atm. But I want to get it in so that we can fix it. Upstream-commit: f7238f94e8b5b31e78360c342676ca84b5984806 Component: engine --- components/engine/archive/changes_test.go | 297 ++++++++++++++++++++++ 1 file changed, 297 insertions(+) create mode 100644 components/engine/archive/changes_test.go diff --git a/components/engine/archive/changes_test.go b/components/engine/archive/changes_test.go new file mode 100644 index 0000000000..37a3869e96 --- /dev/null +++ b/components/engine/archive/changes_test.go @@ -0,0 +1,297 @@ +package archive + +import ( + "io/ioutil" + "os" + "os/exec" + "path" + "sort" + "testing" + "time" +) + +func max(x, y int) int { + if x >= y { + return x + } + return y +} + +func copyDir(src, dst string) error { + cmd := exec.Command("cp", "-a", src, dst) + if err := cmd.Run(); err != nil { + return err + } + return nil +} + +// Helper to sort []Change by path +type byPath struct{ changes []Change } + +func (b byPath) Less(i, j int) bool { return b.changes[i].Path < b.changes[j].Path } +func (b byPath) Len() int { return len(b.changes) } +func (b byPath) Swap(i, j int) { b.changes[i], b.changes[j] = b.changes[j], b.changes[i] } + +type FileType uint32 + +const ( + Regular FileType = iota + Dir + Symlink +) + +type FileData struct { + filetype FileType + path string + contents string + permissions os.FileMode +} + +func createSampleDir(t *testing.T, root string) { + files := []FileData{ + {Regular, "file1", "file1\n", 0600}, + {Regular, "file2", "file2\n", 0666}, + {Regular, "file3", "file3\n", 0404}, + {Regular, "file4", "file4\n", 0600}, + {Regular, "file5", "file5\n", 0600}, + {Regular, "file6", "file6\n", 0600}, + {Regular, "file7", "file7\n", 0600}, + {Dir, "dir1", "", 0740}, + {Regular, "dir1/file1-1", "file1-1\n", 01444}, + {Regular, "dir1/file1-2", "file1-2\n", 0666}, + {Dir, "dir2", "", 0700}, + {Regular, "dir2/file2-1", "file2-1\n", 0666}, + {Regular, "dir2/file2-2", "file2-2\n", 0666}, + {Dir, "dir3", "", 0700}, + {Regular, "dir3/file3-1", "file3-1\n", 0666}, + {Regular, "dir3/file3-2", "file3-2\n", 0666}, + {Dir, "dir4", "", 0700}, + {Regular, "dir4/file3-1", "file4-1\n", 0666}, + {Regular, "dir4/file3-2", "file4-2\n", 0666}, + {Symlink, "symlink1", "target1", 0666}, + {Symlink, "symlink2", "target2", 0666}, + } + for _, info := range files { + if info.filetype == Dir { + if err := os.MkdirAll(path.Join(root, info.path), info.permissions); err != nil { + t.Fatal(err) + } + } else if info.filetype == Regular { + if err := ioutil.WriteFile(path.Join(root, info.path), []byte(info.contents), info.permissions); err != nil { + t.Fatal(err) + } + } else if info.filetype == Symlink { + if err := os.Symlink(info.contents, path.Join(root, info.path)); err != nil { + t.Fatal(err) + } + } + } +} + +// Create an directory, copy it, make sure we report no changes between the two +func TestChangesDirsEmpty(t *testing.T) { + src, err := ioutil.TempDir("", "docker-changes-test") + if err != nil { + t.Fatal(err) + } + createSampleDir(t, src) + dst := src + "-copy" + if err := copyDir(src, dst); err != nil { + t.Fatal(err) + } + changes, err := ChangesDirs(dst, src) + if err != nil { + t.Fatal(err) + } + + if len(changes) != 0 { + t.Fatalf("Reported changes for identical dirs: %v", changes) + } + os.RemoveAll(src) + os.RemoveAll(dst) +} + +func mutateSampleDir(t *testing.T, root string) { + // Remove a regular file + if err := os.RemoveAll(path.Join(root, "file1")); err != nil { + t.Fatal(err) + } + + // Remove a directory + if err := os.RemoveAll(path.Join(root, "dir1")); err != nil { + t.Fatal(err) + } + + // Remove a symlink + if err := os.RemoveAll(path.Join(root, "symlink1")); err != nil { + t.Fatal(err) + } + + // Rewrite a file + if err := ioutil.WriteFile(path.Join(root, "file2"), []byte("fileN\n"), 0777); err != nil { + t.Fatal(err) + } + + // Replace a file + if err := os.RemoveAll(path.Join(root, "file3")); err != nil { + t.Fatal(err) + } + if err := ioutil.WriteFile(path.Join(root, "file3"), []byte("fileM\n"), 0404); err != nil { + t.Fatal(err) + } + + // Touch file + if err := os.Chtimes(path.Join(root, "file4"), time.Now(), time.Now()); err != nil { + t.Fatal(err) + } + + // Replace file with dir + if err := os.RemoveAll(path.Join(root, "file5")); err != nil { + t.Fatal(err) + } + if err := os.MkdirAll(path.Join(root, "file5"), 0666); err != nil { + t.Fatal(err) + } + + // Create new file + if err := ioutil.WriteFile(path.Join(root, "filenew"), []byte("filenew\n"), 0777); err != nil { + t.Fatal(err) + } + + // Create new dir + if err := os.MkdirAll(path.Join(root, "dirnew"), 0766); err != nil { + t.Fatal(err) + } + + // Create a new symlink + if err := os.Symlink("targetnew", path.Join(root, "symlinknew")); err != nil { + t.Fatal(err) + } + + // Change a symlink + if err := os.RemoveAll(path.Join(root, "symlink2")); err != nil { + t.Fatal(err) + } + if err := os.Symlink("target2change", path.Join(root, "symlink2")); err != nil { + t.Fatal(err) + } + + // Replace dir with file + if err := os.RemoveAll(path.Join(root, "dir2")); err != nil { + t.Fatal(err) + } + if err := ioutil.WriteFile(path.Join(root, "dir2"), []byte("dir2\n"), 0777); err != nil { + t.Fatal(err) + } + + // Touch dir + if err := os.Chtimes(path.Join(root, "dir3"), time.Now(), time.Now()); err != nil { + t.Fatal(err) + } +} + +func TestChangesDirsMutated(t *testing.T) { + src, err := ioutil.TempDir("", "docker-changes-test") + if err != nil { + t.Fatal(err) + } + createSampleDir(t, src) + dst := src + "-copy" + if err := copyDir(src, dst); err != nil { + t.Fatal(err) + } + mutateSampleDir(t, dst) + + changes, err := ChangesDirs(dst, src) + if err != nil { + t.Fatal(err) + } + + sort.Sort(byPath{changes}) + + expectedChanges := []Change{ + {"/dir1", ChangeDelete}, + {"/dir2", ChangeModify}, + {"/dir3", ChangeModify}, + {"/dirnew", ChangeAdd}, + {"/file1", ChangeDelete}, + {"/file2", ChangeModify}, + {"/file3", ChangeModify}, + {"/file4", ChangeModify}, + {"/file5", ChangeModify}, + {"/filenew", ChangeAdd}, + {"/symlink1", ChangeDelete}, + {"/symlink2", ChangeModify}, + {"/symlinknew", ChangeAdd}, + } + + i := 0 + for ; i < max(len(changes), len(expectedChanges)); i++ { + if i >= len(expectedChanges) { + t.Fatalf("unexpected change %s\n", changes[i].String()) + } + if i >= len(changes) { + t.Fatalf("no change for expected change %s\n", expectedChanges[i].String()) + } + if changes[i].Path == expectedChanges[i].Path { + if changes[i] != expectedChanges[i] { + t.Fatalf("Wrong change for %s, expected %s, got %d\n", changes[i].Path, changes[i].String(), expectedChanges[i].String()) + } + } else if changes[i].Path < expectedChanges[i].Path { + t.Fatalf("unexpected change %s\n", changes[i].String()) + } else { + t.Fatalf("no change for expected change %s\n", expectedChanges[i].String()) + } + } + for ; i < len(expectedChanges); i++ { + } + + os.RemoveAll(src) + os.RemoveAll(dst) +} + +func TestApplyLayer(t *testing.T) { + return // Disable this for now as it is broken + + src, err := ioutil.TempDir("", "docker-changes-test") + if err != nil { + t.Fatal(err) + } + createSampleDir(t, src) + dst := src + "-copy" + if err := copyDir(src, dst); err != nil { + t.Fatal(err) + } + mutateSampleDir(t, dst) + + changes, err := ChangesDirs(dst, src) + if err != nil { + t.Fatal(err) + } + + layer, err := ExportChanges(dst, changes) + if err != nil { + t.Fatal(err) + } + + layerCopy, err := NewTempArchive(layer, "") + if err != nil { + t.Fatal(err) + } + + if err := ApplyLayer(src, layerCopy); err != nil { + t.Fatal(err) + } + + changes2, err := ChangesDirs(src, dst) + if err != nil { + t.Fatal(err) + } + + if len(changes2) != 0 { + t.Fatalf("Unexpected differences after re applying mutation: %v", changes) + } + + os.RemoveAll(src) + os.RemoveAll(dst) +} From fb91dc2f0c9e68b01e3e139b88d4d0ba4307d966 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 15 Nov 2013 10:24:48 +0100 Subject: [PATCH 249/301] Show active driver in docker info output Upstream-commit: 062810caedba973e76a3dd7eb9e45b56511eefc6 Component: engine --- components/engine/api_params.go | 1 + components/engine/commands.go | 1 + components/engine/graphdriver/driver.go | 2 ++ components/engine/server.go | 1 + 4 files changed, 5 insertions(+) diff --git a/components/engine/api_params.go b/components/engine/api_params.go index e4508542ef..f6c5b2f4b8 100644 --- a/components/engine/api_params.go +++ b/components/engine/api_params.go @@ -52,6 +52,7 @@ type APIInfo struct { Debug bool Containers int Images int + Driver string `json:",omitempty"` NFd int `json:",omitempty"` NGoroutines int `json:",omitempty"` MemoryLimit bool `json:",omitempty"` diff --git a/components/engine/commands.go b/components/engine/commands.go index 4b51bc4433..6fdc0cfbcd 100644 --- a/components/engine/commands.go +++ b/components/engine/commands.go @@ -460,6 +460,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error { fmt.Fprintf(cli.out, "Containers: %d\n", out.Containers) fmt.Fprintf(cli.out, "Images: %d\n", out.Images) + fmt.Fprintf(cli.out, "Driver: %s\n", out.Driver) if out.Debug || os.Getenv("DEBUG") != "" { fmt.Fprintf(cli.out, "Debug mode (server): %v\n", out.Debug) fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "") diff --git a/components/engine/graphdriver/driver.go b/components/engine/graphdriver/driver.go index f521e0bbaf..aba3666b58 100644 --- a/components/engine/graphdriver/driver.go +++ b/components/engine/graphdriver/driver.go @@ -11,6 +11,8 @@ import ( type InitFunc func(root string) (Driver, error) type Driver interface { + String() string + Create(id, parent string) error Remove(id string) error diff --git a/components/engine/server.go b/components/engine/server.go index 1d24f73433..1f0c31d842 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -375,6 +375,7 @@ func (srv *Server) DockerInfo() *APIInfo { return &APIInfo{ Containers: len(srv.runtime.List()), Images: imgcount, + Driver: srv.runtime.driver.String(), MemoryLimit: srv.runtime.capabilities.MemoryLimit, SwapLimit: srv.runtime.capabilities.SwapLimit, IPv4Forwarding: !srv.runtime.capabilities.IPv4ForwardingDisabled, From fa730d30737b0bc3837469562be58561b99b33a2 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 15 Nov 2013 11:04:02 +0100 Subject: [PATCH 250/301] Print devicemapper status details in docker info This adds a generic Status call in the Driver api and implements if for the devicemapper backend. The status is an array of key/value strings rather than a map so that we can guarantee some static order of the docker info output. Upstream-commit: 243843c0787ce2b56c8bbf72a2d4bd7604e84b2e Component: engine --- components/engine/api_params.go | 21 ++++++++++--------- components/engine/aufs/aufs.go | 4 ++++ components/engine/commands.go | 3 +++ components/engine/devmapper/driver.go | 15 +++++++++++++ components/engine/graphdriver/driver.go | 2 ++ components/engine/graphdriver/dummy/driver.go | 4 ++++ components/engine/server.go | 1 + 7 files changed, 40 insertions(+), 10 deletions(-) diff --git a/components/engine/api_params.go b/components/engine/api_params.go index f6c5b2f4b8..5a23cfc1be 100644 --- a/components/engine/api_params.go +++ b/components/engine/api_params.go @@ -52,16 +52,17 @@ type APIInfo struct { Debug bool Containers int Images int - Driver string `json:",omitempty"` - NFd int `json:",omitempty"` - NGoroutines int `json:",omitempty"` - MemoryLimit bool `json:",omitempty"` - SwapLimit bool `json:",omitempty"` - IPv4Forwarding bool `json:",omitempty"` - LXCVersion string `json:",omitempty"` - NEventsListener int `json:",omitempty"` - KernelVersion string `json:",omitempty"` - IndexServerAddress string `json:",omitempty"` + Driver string `json:",omitempty"` + DriverStatus [][2]string `json:",omitempty"` + NFd int `json:",omitempty"` + NGoroutines int `json:",omitempty"` + MemoryLimit bool `json:",omitempty"` + SwapLimit bool `json:",omitempty"` + IPv4Forwarding bool `json:",omitempty"` + LXCVersion string `json:",omitempty"` + NEventsListener int `json:",omitempty"` + KernelVersion string `json:",omitempty"` + IndexServerAddress string `json:",omitempty"` } type APITop struct { diff --git a/components/engine/aufs/aufs.go b/components/engine/aufs/aufs.go index e0727440e2..c0bc069ce8 100644 --- a/components/engine/aufs/aufs.go +++ b/components/engine/aufs/aufs.go @@ -103,6 +103,10 @@ func (a *AufsDriver) String() string { return "aufs" } +func (d *AufsDriver) Status() [][2]string { + return nil +} + // Three folders are created for each id // mnt, layers, and diff func (a *AufsDriver) Create(id, parent string) error { diff --git a/components/engine/commands.go b/components/engine/commands.go index 6fdc0cfbcd..e991492d5e 100644 --- a/components/engine/commands.go +++ b/components/engine/commands.go @@ -461,6 +461,9 @@ func (cli *DockerCli) CmdInfo(args ...string) error { fmt.Fprintf(cli.out, "Containers: %d\n", out.Containers) fmt.Fprintf(cli.out, "Images: %d\n", out.Images) fmt.Fprintf(cli.out, "Driver: %s\n", out.Driver) + for _, pair := range out.DriverStatus { + fmt.Fprintf(cli.out, " %s: %s\n", pair[0], pair[1]) + } if out.Debug || os.Getenv("DEBUG") != "" { fmt.Fprintf(cli.out, "Debug mode (server): %v\n", out.Debug) fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "") diff --git a/components/engine/devmapper/driver.go b/components/engine/devmapper/driver.go index 6123b4c3e4..315b7b091e 100644 --- a/components/engine/devmapper/driver.go +++ b/components/engine/devmapper/driver.go @@ -37,6 +37,21 @@ func (d *Driver) String() string { return "devicemapper" } +func (d *Driver) Status() [][2]string { + s := d.DeviceSet.Status() + + status := [][2]string{ + {"Pool Name", s.PoolName}, + {"Data file", s.DataLoopback}, + {"Metadata file", s.MetadataLoopback}, + {"Data Space Used", fmt.Sprintf("%.1f Mb", float64(s.Data.Used)/(1024*1024))}, + {"Data Space Total", fmt.Sprintf("%.1f Mb", float64(s.Data.Total)/(1024*1024))}, + {"Metadata Space Used", fmt.Sprintf("%.1f Mb", float64(s.Metadata.Used)/(1024*1024))}, + {"Metadata Space Total", fmt.Sprintf("%.1f Mb", float64(s.Metadata.Total)/(1024*1024))}, + } + return status +} + func (d *Driver) Cleanup() error { return d.DeviceSet.Shutdown() } diff --git a/components/engine/graphdriver/driver.go b/components/engine/graphdriver/driver.go index aba3666b58..e29904c3dd 100644 --- a/components/engine/graphdriver/driver.go +++ b/components/engine/graphdriver/driver.go @@ -19,6 +19,8 @@ type Driver interface { Get(id string) (dir string, err error) Size(id string) (bytes int64, err error) + Status() [][2]string + Cleanup() error } diff --git a/components/engine/graphdriver/dummy/driver.go b/components/engine/graphdriver/dummy/driver.go index ad0e21ec4a..4ca8fac58a 100644 --- a/components/engine/graphdriver/dummy/driver.go +++ b/components/engine/graphdriver/dummy/driver.go @@ -27,6 +27,10 @@ func (d *Driver) String() string { return "dummy" } +func (d *Driver) Status() [][2]string { + return nil +} + func (d *Driver) Cleanup() error { return nil } diff --git a/components/engine/server.go b/components/engine/server.go index 1f0c31d842..a973aa5d00 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -376,6 +376,7 @@ func (srv *Server) DockerInfo() *APIInfo { Containers: len(srv.runtime.List()), Images: imgcount, Driver: srv.runtime.driver.String(), + DriverStatus: srv.runtime.driver.Status(), MemoryLimit: srv.runtime.capabilities.MemoryLimit, SwapLimit: srv.runtime.capabilities.SwapLimit, IPv4Forwarding: !srv.runtime.capabilities.IPv4ForwardingDisabled, From faff941ade3ba56d1e5bb50d814e3d4dc234c832 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 15 Nov 2013 11:30:28 +0100 Subject: [PATCH 251/301] Use dummy driver for volumes It makes no sense to use the aufs or devicemapper drivers for volumes. The aufs one is perhaps not a big problem, but the devicemapper one certainly is. It will be unnecessarily using a dm blockdevice-over-loopback with a limited size base FS. This just hardcodes the driver to be the dummy, perhaps in the future we can have other drivers that make sense for the volumes. Upstream-commit: 10f23a94f6daaf03c684937daea67d10205b4b89 Component: engine --- components/engine/graphdriver/driver.go | 6 +++--- components/engine/runtime.go | 9 ++++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/components/engine/graphdriver/driver.go b/components/engine/graphdriver/driver.go index f521e0bbaf..3987a79580 100644 --- a/components/engine/graphdriver/driver.go +++ b/components/engine/graphdriver/driver.go @@ -50,7 +50,7 @@ func Register(name string, initFunc InitFunc) error { return nil } -func getDriver(name, home string) (Driver, error) { +func GetDriver(name, home string) (Driver, error) { if initFunc, exists := drivers[name]; exists { return initFunc(path.Join(home, name)) } @@ -62,11 +62,11 @@ func New(root string) (Driver, error) { var lastError error // Use environment variable DOCKER_DRIVER to force a choice of driver if name := os.Getenv("DOCKER_DRIVER"); name != "" { - return getDriver(name, root) + return GetDriver(name, root) } // Check for priority drivers first for _, name := range priority { - driver, lastError = getDriver(name, root) + driver, lastError = GetDriver(name, root) if lastError != nil { utils.Debugf("Error loading driver %s: %s", name, lastError) continue diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 5072a00fdd..422cebf7b2 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -629,7 +629,14 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { if err != nil { return nil, err } - volumes, err := NewGraph(path.Join(config.Root, "volumes"), driver) + + // We don't want to use a complex driver like aufs or devmapper + // for volumes, just a plain filesystem + volumesDriver, err := graphdriver.GetDriver("dummy", config.Root) + if err != nil { + return nil, err + } + volumes, err := NewGraph(path.Join(config.Root, "volumes"), volumesDriver) if err != nil { return nil, err } From 052ebaaf527b193514102eed5667ca8622679f49 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 15 Nov 2013 15:48:24 -0800 Subject: [PATCH 252/301] Move all drivers to the same subdir graphdriver Upstream-commit: 035c1442424ee502118cdbf2ee2dddafcc0f01cd Component: engine --- components/engine/{ => graphdriver}/aufs/aufs.go | 0 components/engine/{ => graphdriver}/aufs/aufs_test.go | 0 components/engine/{ => graphdriver}/aufs/dirs.go | 0 components/engine/{ => graphdriver}/aufs/mount.go | 0 components/engine/{ => graphdriver}/aufs/mount_darwin.go | 0 components/engine/{ => graphdriver}/aufs/mount_linux.go | 0 components/engine/{ => graphdriver}/devmapper/deviceset.go | 0 components/engine/{ => graphdriver}/devmapper/devmapper.go | 0 .../engine/{ => graphdriver}/devmapper/devmapper_log.go | 0 .../engine/{ => graphdriver}/devmapper/devmapper_test.go | 0 .../engine/{ => graphdriver}/devmapper/devmapper_wrapper.go | 0 .../devmapper/docker-device-tool/device_tool.go | 0 components/engine/{ => graphdriver}/devmapper/driver.go | 0 components/engine/{ => graphdriver}/devmapper/driver_test.go | 0 components/engine/{ => graphdriver}/devmapper/mount.go | 0 components/engine/runtime.go | 4 ++-- 16 files changed, 2 insertions(+), 2 deletions(-) rename components/engine/{ => graphdriver}/aufs/aufs.go (100%) rename components/engine/{ => graphdriver}/aufs/aufs_test.go (100%) rename components/engine/{ => graphdriver}/aufs/dirs.go (100%) rename components/engine/{ => graphdriver}/aufs/mount.go (100%) rename components/engine/{ => graphdriver}/aufs/mount_darwin.go (100%) rename components/engine/{ => graphdriver}/aufs/mount_linux.go (100%) rename components/engine/{ => graphdriver}/devmapper/deviceset.go (100%) rename components/engine/{ => graphdriver}/devmapper/devmapper.go (100%) rename components/engine/{ => graphdriver}/devmapper/devmapper_log.go (100%) rename components/engine/{ => graphdriver}/devmapper/devmapper_test.go (100%) rename components/engine/{ => graphdriver}/devmapper/devmapper_wrapper.go (100%) rename components/engine/{ => graphdriver}/devmapper/docker-device-tool/device_tool.go (100%) rename components/engine/{ => graphdriver}/devmapper/driver.go (100%) rename components/engine/{ => graphdriver}/devmapper/driver_test.go (100%) rename components/engine/{ => graphdriver}/devmapper/mount.go (100%) diff --git a/components/engine/aufs/aufs.go b/components/engine/graphdriver/aufs/aufs.go similarity index 100% rename from components/engine/aufs/aufs.go rename to components/engine/graphdriver/aufs/aufs.go diff --git a/components/engine/aufs/aufs_test.go b/components/engine/graphdriver/aufs/aufs_test.go similarity index 100% rename from components/engine/aufs/aufs_test.go rename to components/engine/graphdriver/aufs/aufs_test.go diff --git a/components/engine/aufs/dirs.go b/components/engine/graphdriver/aufs/dirs.go similarity index 100% rename from components/engine/aufs/dirs.go rename to components/engine/graphdriver/aufs/dirs.go diff --git a/components/engine/aufs/mount.go b/components/engine/graphdriver/aufs/mount.go similarity index 100% rename from components/engine/aufs/mount.go rename to components/engine/graphdriver/aufs/mount.go diff --git a/components/engine/aufs/mount_darwin.go b/components/engine/graphdriver/aufs/mount_darwin.go similarity index 100% rename from components/engine/aufs/mount_darwin.go rename to components/engine/graphdriver/aufs/mount_darwin.go diff --git a/components/engine/aufs/mount_linux.go b/components/engine/graphdriver/aufs/mount_linux.go similarity index 100% rename from components/engine/aufs/mount_linux.go rename to components/engine/graphdriver/aufs/mount_linux.go diff --git a/components/engine/devmapper/deviceset.go b/components/engine/graphdriver/devmapper/deviceset.go similarity index 100% rename from components/engine/devmapper/deviceset.go rename to components/engine/graphdriver/devmapper/deviceset.go diff --git a/components/engine/devmapper/devmapper.go b/components/engine/graphdriver/devmapper/devmapper.go similarity index 100% rename from components/engine/devmapper/devmapper.go rename to components/engine/graphdriver/devmapper/devmapper.go diff --git a/components/engine/devmapper/devmapper_log.go b/components/engine/graphdriver/devmapper/devmapper_log.go similarity index 100% rename from components/engine/devmapper/devmapper_log.go rename to components/engine/graphdriver/devmapper/devmapper_log.go diff --git a/components/engine/devmapper/devmapper_test.go b/components/engine/graphdriver/devmapper/devmapper_test.go similarity index 100% rename from components/engine/devmapper/devmapper_test.go rename to components/engine/graphdriver/devmapper/devmapper_test.go diff --git a/components/engine/devmapper/devmapper_wrapper.go b/components/engine/graphdriver/devmapper/devmapper_wrapper.go similarity index 100% rename from components/engine/devmapper/devmapper_wrapper.go rename to components/engine/graphdriver/devmapper/devmapper_wrapper.go diff --git a/components/engine/devmapper/docker-device-tool/device_tool.go b/components/engine/graphdriver/devmapper/docker-device-tool/device_tool.go similarity index 100% rename from components/engine/devmapper/docker-device-tool/device_tool.go rename to components/engine/graphdriver/devmapper/docker-device-tool/device_tool.go diff --git a/components/engine/devmapper/driver.go b/components/engine/graphdriver/devmapper/driver.go similarity index 100% rename from components/engine/devmapper/driver.go rename to components/engine/graphdriver/devmapper/driver.go diff --git a/components/engine/devmapper/driver_test.go b/components/engine/graphdriver/devmapper/driver_test.go similarity index 100% rename from components/engine/devmapper/driver_test.go rename to components/engine/graphdriver/devmapper/driver_test.go diff --git a/components/engine/devmapper/mount.go b/components/engine/graphdriver/devmapper/mount.go similarity index 100% rename from components/engine/devmapper/mount.go rename to components/engine/graphdriver/devmapper/mount.go diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 57e5498ff4..806ae69ed3 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -6,10 +6,10 @@ import ( "database/sql" "fmt" "github.com/dotcloud/docker/archive" - _ "github.com/dotcloud/docker/aufs" - _ "github.com/dotcloud/docker/devmapper" "github.com/dotcloud/docker/gograph" "github.com/dotcloud/docker/graphdriver" + _ "github.com/dotcloud/docker/graphdriver/aufs" + _ "github.com/dotcloud/docker/graphdriver/devmapper" _ "github.com/dotcloud/docker/graphdriver/dummy" "github.com/dotcloud/docker/utils" "io" From 73e6c3f45e35d148743ee9d5db2eba63c665ae0d Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 15 Nov 2013 15:55:45 -0800 Subject: [PATCH 253/301] rename gograph in graphdb Upstream-commit: cbd1281ec998e017b579c13ee6262aa21c96c38a Component: engine --- components/engine/{gograph => graphdb}/MAINTAINERS | 0 .../{gograph/gograph.go => graphdb/graphdb.go} | 2 +- .../gograph_test.go => graphdb/graphdb_test.go} | 2 +- components/engine/{gograph => graphdb}/sort.go | 2 +- .../engine/{gograph => graphdb}/sort_test.go | 2 +- components/engine/{gograph => graphdb}/utils.go | 2 +- components/engine/runtime.go | 14 +++++++------- components/engine/server.go | 4 ++-- 8 files changed, 14 insertions(+), 14 deletions(-) rename components/engine/{gograph => graphdb}/MAINTAINERS (100%) rename components/engine/{gograph/gograph.go => graphdb/graphdb.go} (99%) rename components/engine/{gograph/gograph_test.go => graphdb/graphdb_test.go} (99%) rename components/engine/{gograph => graphdb}/sort.go (96%) rename components/engine/{gograph => graphdb}/sort_test.go (96%) rename components/engine/{gograph => graphdb}/utils.go (96%) diff --git a/components/engine/gograph/MAINTAINERS b/components/engine/graphdb/MAINTAINERS similarity index 100% rename from components/engine/gograph/MAINTAINERS rename to components/engine/graphdb/MAINTAINERS diff --git a/components/engine/gograph/gograph.go b/components/engine/graphdb/graphdb.go similarity index 99% rename from components/engine/gograph/gograph.go rename to components/engine/graphdb/graphdb.go index aa6a4126a0..9e2466b692 100644 --- a/components/engine/gograph/gograph.go +++ b/components/engine/graphdb/graphdb.go @@ -1,4 +1,4 @@ -package gograph +package graphdb import ( "database/sql" diff --git a/components/engine/gograph/gograph_test.go b/components/engine/graphdb/graphdb_test.go similarity index 99% rename from components/engine/gograph/gograph_test.go rename to components/engine/graphdb/graphdb_test.go index 1a40fffce3..0c3e8670e0 100644 --- a/components/engine/gograph/gograph_test.go +++ b/components/engine/graphdb/graphdb_test.go @@ -1,4 +1,4 @@ -package gograph +package graphdb import ( _ "code.google.com/p/gosqlite/sqlite3" diff --git a/components/engine/gograph/sort.go b/components/engine/graphdb/sort.go similarity index 96% rename from components/engine/gograph/sort.go rename to components/engine/graphdb/sort.go index a0af6b4025..c07df077d8 100644 --- a/components/engine/gograph/sort.go +++ b/components/engine/graphdb/sort.go @@ -1,4 +1,4 @@ -package gograph +package graphdb import "sort" diff --git a/components/engine/gograph/sort_test.go b/components/engine/graphdb/sort_test.go similarity index 96% rename from components/engine/gograph/sort_test.go rename to components/engine/graphdb/sort_test.go index 40431039a5..ddf2266f60 100644 --- a/components/engine/gograph/sort_test.go +++ b/components/engine/graphdb/sort_test.go @@ -1,4 +1,4 @@ -package gograph +package graphdb import ( "testing" diff --git a/components/engine/gograph/utils.go b/components/engine/graphdb/utils.go similarity index 96% rename from components/engine/gograph/utils.go rename to components/engine/graphdb/utils.go index 4896242796..bdbcd79813 100644 --- a/components/engine/gograph/utils.go +++ b/components/engine/graphdb/utils.go @@ -1,4 +1,4 @@ -package gograph +package graphdb import ( "path" diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 806ae69ed3..8ae9b97d2e 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -6,7 +6,7 @@ import ( "database/sql" "fmt" "github.com/dotcloud/docker/archive" - "github.com/dotcloud/docker/gograph" + "github.com/dotcloud/docker/graphdb" "github.com/dotcloud/docker/graphdriver" _ "github.com/dotcloud/docker/graphdriver/aufs" _ "github.com/dotcloud/docker/graphdriver/devmapper" @@ -43,7 +43,7 @@ type Runtime struct { volumes *Graph srv *Server config *DaemonConfig - containerGraph *gograph.Database + containerGraph *graphdb.Database driver graphdriver.Driver } @@ -581,7 +581,7 @@ func (runtime *Runtime) Children(name string) (map[string]*Container, error) { } children := make(map[string]*Container) - err = runtime.containerGraph.Walk(name, func(p string, e *gograph.Entity) error { + err = runtime.containerGraph.Walk(name, func(p string, e *graphdb.Entity) error { c := runtime.Get(e.ID()) if c == nil { return fmt.Errorf("Could not get container for name %s and id %s", e.ID(), p) @@ -659,20 +659,20 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { return nil, err } - gographPath := path.Join(config.Root, "linkgraph.db") + graphdbPath := path.Join(config.Root, "linkgraph.db") initDatabase := false - if _, err := os.Stat(gographPath); err != nil { + if _, err := os.Stat(graphdbPath); err != nil { if os.IsNotExist(err) { initDatabase = true } else { return nil, err } } - conn, err := sql.Open("sqlite3", gographPath) + conn, err := sql.Open("sqlite3", graphdbPath) if err != nil { return nil, err } - graph, err := gograph.NewDatabase(conn, initDatabase) + graph, err := graphdb.NewDatabase(conn, initDatabase) if err != nil { return nil, err } diff --git a/components/engine/server.go b/components/engine/server.go index a973aa5d00..4852e2133f 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -8,7 +8,7 @@ import ( "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/engine" - "github.com/dotcloud/docker/gograph" + "github.com/dotcloud/docker/graphdb" "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/utils" "io" @@ -502,7 +502,7 @@ func createAPIContainer(container *Container, size bool, runtime *Runtime) APICo ID: container.ID, } names := []string{} - runtime.containerGraph.Walk("/", func(p string, e *gograph.Entity) error { + runtime.containerGraph.Walk("/", func(p string, e *graphdb.Entity) error { if e.ID() == container.ID { names = append(names, p) } From 9d193b1c0099037c386f5a31c743622caebdfa7f Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 15 Nov 2013 17:16:30 -0800 Subject: [PATCH 254/301] WIP Upstream-commit: a518b847511f034d9bfd4166e17f8f0eac61d021 Component: engine --- components/engine/graphdriver/aufs/aufs.go | 13 ++++++-- components/engine/graphdriver/aufs/migrate.go | 32 +++++++++++++++++++ components/engine/runtime.go | 8 ++++- 3 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 components/engine/graphdriver/aufs/migrate.go diff --git a/components/engine/graphdriver/aufs/aufs.go b/components/engine/graphdriver/aufs/aufs.go index c0bc069ce8..9cfaf90996 100644 --- a/components/engine/graphdriver/aufs/aufs.go +++ b/components/engine/graphdriver/aufs/aufs.go @@ -95,18 +95,25 @@ func supportsAufs() error { return fmt.Errorf("AUFS was not found in /proc/filesystems") } -func (a *AufsDriver) rootPath() string { +func (a AufsDriver) rootPath() string { return a.root } -func (a *AufsDriver) String() string { +func (AufsDriver) String() string { return "aufs" } -func (d *AufsDriver) Status() [][2]string { +func (AufsDriver) Status() [][2]string { return nil } +func (a AufsDriver) Exists(id string) bool { + if _, err := os.Lstat(path.Join(a.rootPath(), "diff", id)); err != nil { + return false + } + return true +} + // Three folders are created for each id // mnt, layers, and diff func (a *AufsDriver) Create(id, parent string) error { diff --git a/components/engine/graphdriver/aufs/migrate.go b/components/engine/graphdriver/aufs/migrate.go new file mode 100644 index 0000000000..58145aa989 --- /dev/null +++ b/components/engine/graphdriver/aufs/migrate.go @@ -0,0 +1,32 @@ +package aufs + +import ( + "io/ioutil" + "os" + "path" +) + +func exists(pth string) bool { + if _, err := os.Stat(pth); err != nil { + return false + } + return true +} + +func (a *AufsDriver) Migrate(pth string) error { + fis, err := ioutil.ReadDir(pth) + if err != nil { + return err + } + for _, fi := range fis { + if fi.IsDir() && exists(path.Join(pth, fi.Name(), "layer")) && !a.Exists(fi.Name()) { + if err := os.Symlink(path.Join(pth, fi.Name(), "layer"), path.Join(a.rootPath(), "diff", fi.Name())); err != nil { + return err + } + if err := a.Create(fi.Name(), ""); err != nil { + return err + } + } + } + return nil +} diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 8ae9b97d2e..6eef38c842 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -8,7 +8,7 @@ import ( "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/graphdb" "github.com/dotcloud/docker/graphdriver" - _ "github.com/dotcloud/docker/graphdriver/aufs" + "github.com/dotcloud/docker/graphdriver/aufs" _ "github.com/dotcloud/docker/graphdriver/devmapper" _ "github.com/dotcloud/docker/graphdriver/dummy" "github.com/dotcloud/docker/utils" @@ -629,6 +629,12 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { return nil, err } + if ad, ok := driver.(*aufs.AufsDriver); ok { + if err := ad.Migrate(path.Join(config.Root, "graph")); err != nil { + return nil, err + } + } + if err := linkLxcStart(config.Root); err != nil { return nil, err } From 63db5cd7f5f7a5dbdaad30850d49a161ef6bfeba Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 18 Nov 2013 14:31:34 +0100 Subject: [PATCH 255/301] Mark archive.TestApplyLayer as skipped Instead of just returning we call Skip to log that the test was skipped. Upstream-commit: 8ed4307f50b93d7c49eccf30357a4b9d3fbb1376 Component: engine --- components/engine/archive/changes_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/engine/archive/changes_test.go b/components/engine/archive/changes_test.go index 37a3869e96..341710a33b 100644 --- a/components/engine/archive/changes_test.go +++ b/components/engine/archive/changes_test.go @@ -251,7 +251,8 @@ func TestChangesDirsMutated(t *testing.T) { } func TestApplyLayer(t *testing.T) { - return // Disable this for now as it is broken + t.Skip("Skipping TestApplyLayer due to known failures") // Disable this for now as it is broken + return src, err := ioutil.TempDir("", "docker-changes-test") if err != nil { From 80e02fbc62e73a19486974f57962f5b13e51eabc Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 18 Nov 2013 13:16:28 -0800 Subject: [PATCH 256/301] Use tryRelocate to fall back to symlink if rename fails Upstream-commit: 94e854823f3549eeddaafa24835093966d9aaeeb Component: engine --- components/engine/graphdriver/aufs/migrate.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/components/engine/graphdriver/aufs/migrate.go b/components/engine/graphdriver/aufs/migrate.go index 58145aa989..3d1fa0c0f2 100644 --- a/components/engine/graphdriver/aufs/migrate.go +++ b/components/engine/graphdriver/aufs/migrate.go @@ -1,6 +1,7 @@ package aufs import ( + "fmt" "io/ioutil" "os" "path" @@ -20,7 +21,7 @@ func (a *AufsDriver) Migrate(pth string) error { } for _, fi := range fis { if fi.IsDir() && exists(path.Join(pth, fi.Name(), "layer")) && !a.Exists(fi.Name()) { - if err := os.Symlink(path.Join(pth, fi.Name(), "layer"), path.Join(a.rootPath(), "diff", fi.Name())); err != nil { + if err := tryRelocate(path.Join(pth, fi.Name(), "layer"), path.Join(a.rootPath(), "diff", fi.Name())); err != nil { return err } if err := a.Create(fi.Name(), ""); err != nil { @@ -30,3 +31,14 @@ func (a *AufsDriver) Migrate(pth string) error { } return nil } + +// tryRelocate will try to rename the old path to the new pack and if +// the operation fails, it will fallback to a symlink +func tryRelocate(oldPath, newPath string) error { + if err := os.Rename(oldPath, newPath); err != nil { + if sErr := os.Symlink(oldPath, newPath); sErr != nil { + return fmt.Errorf("Unable to relocate %s to %s: Rename err %s Symlink err %s", oldPath, newPath, err, sErr) + } + } + return nil +} From 3688213d0409f05270b97bc2c3902ff4628a9251 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 18 Nov 2013 16:17:43 -0800 Subject: [PATCH 257/301] Migrate images with parent relationship Upstream-commit: f88b760809677109a723f25399f738d756f14c13 Component: engine --- components/engine/graphdriver/aufs/migrate.go | 81 +++++++++++++++++-- 1 file changed, 74 insertions(+), 7 deletions(-) diff --git a/components/engine/graphdriver/aufs/migrate.go b/components/engine/graphdriver/aufs/migrate.go index 3d1fa0c0f2..4231ac2111 100644 --- a/components/engine/graphdriver/aufs/migrate.go +++ b/components/engine/graphdriver/aufs/migrate.go @@ -1,34 +1,83 @@ package aufs import ( + "encoding/json" "fmt" "io/ioutil" "os" "path" + "time" ) -func exists(pth string) bool { +type imageMetadata struct { + ID string `json:"id"` + ParentID string `json:"parent,omitempty"` + Created time.Time `json:"created"` + DockerVersion string `json:"docker_version,omitempty"` + Architecture string `json:"architecture,omitempty"` + + parent *imageMetadata +} + +func pathExists(pth string) bool { if _, err := os.Stat(pth); err != nil { return false } return true } +// Migrate existing images and containers from docker < 0.7.x func (a *AufsDriver) Migrate(pth string) error { fis, err := ioutil.ReadDir(pth) if err != nil { return err } + var ( + metadata = make(map[string]*imageMetadata) + current *imageMetadata + exists bool + ) + + // Load metadata for _, fi := range fis { - if fi.IsDir() && exists(path.Join(pth, fi.Name(), "layer")) && !a.Exists(fi.Name()) { - if err := tryRelocate(path.Join(pth, fi.Name(), "layer"), path.Join(a.rootPath(), "diff", fi.Name())); err != nil { - return err - } - if err := a.Create(fi.Name(), ""); err != nil { - return err + if id := fi.Name(); fi.IsDir() && pathExists(path.Join(pth, id, "layer")) && !a.Exists(id) { + if current, exists = metadata[id]; !exists { + current, err = loadMetadata(pth, id) + if err != nil { + return err + } + metadata[id] = current } } } + + // Recreate tree + for _, v := range metadata { + v.parent = metadata[v.ParentID] + } + + // Perform image migration + for _, v := range metadata { + if err := migrateImage(v, a, pth); err != nil { + return err + } + } + return nil +} + +func migrateImage(m *imageMetadata, a *AufsDriver, pth string) error { + if !pathExists(path.Join(a.rootPath(), "diff", m.ID)) { + if m.parent != nil { + migrateImage(m.parent, a, pth) + } + if err := tryRelocate(path.Join(pth, m.ID, "layer"), path.Join(a.rootPath(), "diff", m.ID)); err != nil { + return err + } + + if err := a.Create(m.ID, m.ParentID); err != nil { + return err + } + } return nil } @@ -42,3 +91,21 @@ func tryRelocate(oldPath, newPath string) error { } return nil } + +func loadMetadata(pth, id string) (*imageMetadata, error) { + f, err := os.Open(path.Join(pth, id, "json")) + if err != nil { + return nil, err + } + defer f.Close() + + var ( + out = &imageMetadata{} + dec = json.NewDecoder(f) + ) + + if err := dec.Decode(out); err != nil { + return nil, err + } + return out, nil +} From 2d65b723cd11a185b3d68e3dd708a2e9320bbc93 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 18 Nov 2013 17:20:03 -0800 Subject: [PATCH 258/301] Add container migration to aufs driver Upstream-commit: 29f07f854497571db570be79c8df878624f5b41c Component: engine --- components/engine/graphdriver/aufs/aufs.go | 2 + components/engine/graphdriver/aufs/migrate.go | 109 +++++++++++++----- components/engine/runtime.go | 2 +- 3 files changed, 85 insertions(+), 28 deletions(-) diff --git a/components/engine/graphdriver/aufs/aufs.go b/components/engine/graphdriver/aufs/aufs.go index 9cfaf90996..cf42f363fc 100644 --- a/components/engine/graphdriver/aufs/aufs.go +++ b/components/engine/graphdriver/aufs/aufs.go @@ -107,6 +107,8 @@ func (AufsDriver) Status() [][2]string { return nil } +// Exists returns true if the given id is registered with +// this driver func (a AufsDriver) Exists(id string) bool { if _, err := os.Lstat(path.Join(a.rootPath(), "diff", id)); err != nil { return false diff --git a/components/engine/graphdriver/aufs/migrate.go b/components/engine/graphdriver/aufs/migrate.go index 4231ac2111..9cdce5faaa 100644 --- a/components/engine/graphdriver/aufs/migrate.go +++ b/components/engine/graphdriver/aufs/migrate.go @@ -6,17 +6,14 @@ import ( "io/ioutil" "os" "path" - "time" ) -type imageMetadata struct { - ID string `json:"id"` - ParentID string `json:"parent,omitempty"` - Created time.Time `json:"created"` - DockerVersion string `json:"docker_version,omitempty"` - Architecture string `json:"architecture,omitempty"` +type metadata struct { + ID string `json:"id"` + ParentID string `json:"parent,omitempty"` + Image string `json:"Image,omitempty"` - parent *imageMetadata + parent *metadata } func pathExists(pth string) bool { @@ -27,48 +24,106 @@ func pathExists(pth string) bool { } // Migrate existing images and containers from docker < 0.7.x -func (a *AufsDriver) Migrate(pth string) error { +// +// The format pre 0.7 is for docker to store the metadata and filesystem +// content in the same directory. For the migration to work we need to move Image layer +// data from /var/lib/docker/graph//layers to the diff of the registered id. +// +// Next we need to migrate the container's rw layer to diff of the driver. After the +// contents are migrated we need to register the image and container ids with the +// driver. +// +// For the migration we try to move the folder containing the layer files, if that +// fails because the data is currently mounted we will fallback to creating a +// symlink. +func (a *AufsDriver) Migrate(pth string, setupInit func(p string) error) error { + if pathExists(path.Join(pth, "graph")) { + if err := a.migrateImages(path.Join(pth, "graph")); err != nil { + return err + } + return a.migrateContainers(path.Join(pth, "containers"), setupInit) + } + return nil +} + +func (a *AufsDriver) migrateContainers(pth string, setupInit func(p string) error) error { + fis, err := ioutil.ReadDir(pth) + if err != nil { + return err + } + + for _, fi := range fis { + if id := fi.Name(); fi.IsDir() && pathExists(path.Join(pth, id, "rw")) && !a.Exists(id) { + if err := tryRelocate(path.Join(pth, id, "rw"), path.Join(a.rootPath(), "diff", id)); err != nil { + return err + } + + metadata, err := loadMetadata(path.Join(pth, id, "config.json")) + if err != nil { + return err + } + + initID := fmt.Sprintf("%s-init", id) + if err := a.Create(initID, metadata.Image); err != nil { + return err + } + + initPath, err := a.Get(initID) + if err != nil { + return err + } + // setup init layer + if err := setupInit(initPath); err != nil { + return err + } + + if err := a.Create(id, initID); err != nil { + return err + } + } + } + return nil +} + +func (a *AufsDriver) migrateImages(pth string) error { fis, err := ioutil.ReadDir(pth) if err != nil { return err } var ( - metadata = make(map[string]*imageMetadata) - current *imageMetadata - exists bool + m = make(map[string]*metadata) + current *metadata + exists bool ) - // Load metadata for _, fi := range fis { if id := fi.Name(); fi.IsDir() && pathExists(path.Join(pth, id, "layer")) && !a.Exists(id) { - if current, exists = metadata[id]; !exists { - current, err = loadMetadata(pth, id) + if current, exists = m[id]; !exists { + current, err = loadMetadata(path.Join(pth, id, "json")) if err != nil { return err } - metadata[id] = current + m[id] = current } } } - // Recreate tree - for _, v := range metadata { - v.parent = metadata[v.ParentID] + for _, v := range m { + v.parent = m[v.ParentID] } - // Perform image migration - for _, v := range metadata { - if err := migrateImage(v, a, pth); err != nil { + for _, v := range m { + if err := a.migrateImage(v, pth); err != nil { return err } } return nil } -func migrateImage(m *imageMetadata, a *AufsDriver, pth string) error { +func (a *AufsDriver) migrateImage(m *metadata, pth string) error { if !pathExists(path.Join(a.rootPath(), "diff", m.ID)) { if m.parent != nil { - migrateImage(m.parent, a, pth) + a.migrateImage(m.parent, pth) } if err := tryRelocate(path.Join(pth, m.ID, "layer"), path.Join(a.rootPath(), "diff", m.ID)); err != nil { return err @@ -92,15 +147,15 @@ func tryRelocate(oldPath, newPath string) error { return nil } -func loadMetadata(pth, id string) (*imageMetadata, error) { - f, err := os.Open(path.Join(pth, id, "json")) +func loadMetadata(pth string) (*metadata, error) { + f, err := os.Open(pth) if err != nil { return nil, err } defer f.Close() var ( - out = &imageMetadata{} + out = &metadata{} dec = json.NewDecoder(f) ) diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 6eef38c842..1146079070 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -630,7 +630,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { } if ad, ok := driver.(*aufs.AufsDriver); ok { - if err := ad.Migrate(path.Join(config.Root, "graph")); err != nil { + if err := ad.Migrate(config.Root, setupInitLayer); err != nil { return nil, err } } From 9820e0b026de31bd6c6a7999449d285c6166d9d6 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 18 Nov 2013 23:28:45 -0800 Subject: [PATCH 259/301] Retry moving dirs on every daemon startup This will try to move the directories that were previously symlinked until a full migration is complete. Upstream-commit: 5ee8e41e43892694f66b4d6d01ed95f4ca2e7cb6 Component: engine --- components/engine/graphdriver/aufs/aufs.go | 2 +- components/engine/graphdriver/aufs/migrate.go | 71 ++++++++++++------- 2 files changed, 45 insertions(+), 28 deletions(-) diff --git a/components/engine/graphdriver/aufs/aufs.go b/components/engine/graphdriver/aufs/aufs.go index cf42f363fc..315390ec72 100644 --- a/components/engine/graphdriver/aufs/aufs.go +++ b/components/engine/graphdriver/aufs/aufs.go @@ -110,7 +110,7 @@ func (AufsDriver) Status() [][2]string { // Exists returns true if the given id is registered with // this driver func (a AufsDriver) Exists(id string) bool { - if _, err := os.Lstat(path.Join(a.rootPath(), "diff", id)); err != nil { + if _, err := os.Lstat(path.Join(a.rootPath(), "layers", id)); err != nil { return false } return true diff --git a/components/engine/graphdriver/aufs/migrate.go b/components/engine/graphdriver/aufs/migrate.go index 9cdce5faaa..15008c27da 100644 --- a/components/engine/graphdriver/aufs/migrate.go +++ b/components/engine/graphdriver/aufs/migrate.go @@ -53,32 +53,35 @@ func (a *AufsDriver) migrateContainers(pth string, setupInit func(p string) erro } for _, fi := range fis { - if id := fi.Name(); fi.IsDir() && pathExists(path.Join(pth, id, "rw")) && !a.Exists(id) { + if id := fi.Name(); fi.IsDir() && pathExists(path.Join(pth, id, "rw")) { if err := tryRelocate(path.Join(pth, id, "rw"), path.Join(a.rootPath(), "diff", id)); err != nil { return err } - metadata, err := loadMetadata(path.Join(pth, id, "config.json")) - if err != nil { - return err - } + if !a.Exists(id) { - initID := fmt.Sprintf("%s-init", id) - if err := a.Create(initID, metadata.Image); err != nil { - return err - } + metadata, err := loadMetadata(path.Join(pth, id, "config.json")) + if err != nil { + return err + } - initPath, err := a.Get(initID) - if err != nil { - return err - } - // setup init layer - if err := setupInit(initPath); err != nil { - return err - } + initID := fmt.Sprintf("%s-init", id) + if err := a.Create(initID, metadata.Image); err != nil { + return err + } - if err := a.Create(id, initID); err != nil { - return err + initPath, err := a.Get(initID) + if err != nil { + return err + } + // setup init layer + if err := setupInit(initPath); err != nil { + return err + } + + if err := a.Create(id, initID); err != nil { + return err + } } } } @@ -97,7 +100,7 @@ func (a *AufsDriver) migrateImages(pth string) error { ) for _, fi := range fis { - if id := fi.Name(); fi.IsDir() && pathExists(path.Join(pth, id, "layer")) && !a.Exists(id) { + if id := fi.Name(); fi.IsDir() && pathExists(path.Join(pth, id, "layer")) { if current, exists = m[id]; !exists { current, err = loadMetadata(path.Join(pth, id, "json")) if err != nil { @@ -112,26 +115,29 @@ func (a *AufsDriver) migrateImages(pth string) error { v.parent = m[v.ParentID] } + migrated := make(map[string]bool) for _, v := range m { - if err := a.migrateImage(v, pth); err != nil { + if err := a.migrateImage(v, pth, migrated); err != nil { return err } } return nil } -func (a *AufsDriver) migrateImage(m *metadata, pth string) error { - if !pathExists(path.Join(a.rootPath(), "diff", m.ID)) { +func (a *AufsDriver) migrateImage(m *metadata, pth string, migrated map[string]bool) error { + if !migrated[m.ID] { if m.parent != nil { - a.migrateImage(m.parent, pth) + a.migrateImage(m.parent, pth, migrated) } if err := tryRelocate(path.Join(pth, m.ID, "layer"), path.Join(a.rootPath(), "diff", m.ID)); err != nil { return err } - - if err := a.Create(m.ID, m.ParentID); err != nil { - return err + if !a.Exists(m.ID) { + if err := a.Create(m.ID, m.ParentID); err != nil { + return err + } } + migrated[m.ID] = true } return nil } @@ -139,6 +145,17 @@ func (a *AufsDriver) migrateImage(m *metadata, pth string) error { // tryRelocate will try to rename the old path to the new pack and if // the operation fails, it will fallback to a symlink func tryRelocate(oldPath, newPath string) error { + s, err := os.Lstat(newPath) + if err != nil && !os.IsNotExist(err) { + return err + } + // If the destination is a symlink then we already tried to relocate once before + // and it failed so we delete it and try to remove + if s != nil && s.Mode()&os.ModeSymlink == os.ModeSymlink { + if err := os.RemoveAll(newPath); err != nil { + return err + } + } if err := os.Rename(oldPath, newPath); err != nil { if sErr := os.Symlink(oldPath, newPath); sErr != nil { return fmt.Errorf("Unable to relocate %s to %s: Rename err %s Symlink err %s", oldPath, newPath, err, sErr) From 8ad4e865dc7f9468ae1bca1f35a9da9405b65db1 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 18 Nov 2013 17:10:47 +0100 Subject: [PATCH 260/301] devmapper: Use a "rootfs" subdirectory in the devmapper volume We place the actual image/containers in the "rootfs" directory, which allows us to have other data in the toplevel directory in the mount. For starters, this means the "lost+found" directory from mkfs will not always be in your container/image. Secondly, we can create a file "id" in the toplevel dir which is not visible from the container. This is useful because it allows us to map back from the device fs to the container if something goes wrong with the devicemapper metadata. Upstream-commit: 00401a30b73d897255b7b99996d7c6a53fe2d4af Component: engine --- .../engine/graphdriver/devmapper/driver.go | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/components/engine/graphdriver/devmapper/driver.go b/components/engine/graphdriver/devmapper/driver.go index 315b7b091e..9643120f7e 100644 --- a/components/engine/graphdriver/devmapper/driver.go +++ b/components/engine/graphdriver/devmapper/driver.go @@ -3,6 +3,7 @@ package devmapper import ( "fmt" "github.com/dotcloud/docker/graphdriver" + "io/ioutil" "os" "path" ) @@ -57,7 +58,26 @@ func (d *Driver) Cleanup() error { } func (d *Driver) Create(id string, parent string) error { - return d.DeviceSet.AddDevice(id, parent) + if err := d.DeviceSet.AddDevice(id, parent); err != nil { + return err + } + + mp := path.Join(d.home, "mnt", id) + if err := d.mount(id, mp); err != nil { + return err + } + + if err := os.MkdirAll(path.Join(mp, "rootfs"), 0755); err != nil && !os.IsExist(err) { + return err + } + + // Create an "id" file with the container/image id in it to help reconscruct this in case + // of later problems + if err := ioutil.WriteFile(path.Join(mp, "id"), []byte(id), 0600); err != nil { + return err + } + + return nil } func (d *Driver) Remove(id string) error { @@ -69,7 +89,7 @@ func (d *Driver) Get(id string) (string, error) { if err := d.mount(id, mp); err != nil { return "", err } - return mp, nil + return path.Join(mp, "rootfs"), nil } func (d *Driver) Size(id string) (int64, error) { From 04ef2191af5ce07f496f2323cc1484a8022e1575 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 19 Nov 2013 00:35:03 -0800 Subject: [PATCH 261/301] Ensure same atime, mtime after applying whiteouts Upstream-commit: 3edb4af663079ad79672bc98fcd20790f8fb2589 Component: engine --- components/engine/archive/diff.go | 46 ++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/components/engine/archive/diff.go b/components/engine/archive/diff.go index 877d076c8b..58d30466e3 100644 --- a/components/engine/archive/diff.go +++ b/components/engine/archive/diff.go @@ -1,10 +1,11 @@ package archive import ( - "log" "os" "path/filepath" "strings" + "syscall" + "time" ) // ApplyLayer parses a diff in the standard layer format from `layer`, and @@ -17,6 +18,20 @@ func ApplyLayer(dest string, layer Archive) error { return err } + modifiedDirs := make(map[string]*syscall.Stat_t) + addDir := func(file string) { + d := filepath.Dir(file) + if _, exists := modifiedDirs[d]; !exists { + if s, err := os.Lstat(d); err == nil { + if sys := s.Sys(); sys != nil { + if stat, ok := sys.(*syscall.Stat_t); ok { + modifiedDirs[d] = stat + } + } + } + } + } + // Step 2: walk for whiteouts and apply them, removing them in the process err := filepath.Walk(dest, func(fullPath string, f os.FileInfo, err error) error { if err != nil { @@ -39,25 +54,42 @@ func ApplyLayer(dest string, layer Archive) error { if matched, err := filepath.Match("/.wh..wh.*", path); err != nil { return err } else if matched { - log.Printf("Removing aufs metadata %s", fullPath) - _ = os.RemoveAll(fullPath) + addDir(fullPath) + if err := os.RemoveAll(fullPath); err != nil { + return err + } } filename := filepath.Base(path) if strings.HasPrefix(filename, ".wh.") { rmTargetName := filename[len(".wh."):] rmTargetPath := filepath.Join(filepath.Dir(fullPath), rmTargetName) + // Remove the file targeted by the whiteout - log.Printf("Removing whiteout target %s", rmTargetPath) - _ = os.RemoveAll(rmTargetPath) + addDir(rmTargetPath) + if err := os.RemoveAll(rmTargetPath); err != nil { + return err + } // Remove the whiteout itself - log.Printf("Removing whiteout %s", fullPath) - _ = os.RemoveAll(fullPath) + addDir(fullPath) + if err := os.RemoveAll(fullPath); err != nil { + return err + } } return nil }) if err != nil { return err } + + for k, v := range modifiedDirs { + aTime := time.Unix(v.Atim.Unix()) + mTime := time.Unix(v.Mtim.Unix()) + + if err := os.Chtimes(k, aTime, mTime); err != nil { + return err + } + } + return nil } From 08c42330ce5c3dbeb4176356b23a488fd323ca82 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 18 Nov 2013 11:33:07 +0100 Subject: [PATCH 262/301] devmapper: Update device-mapper-tool This makes the device mapper tool work again and adds new features to get pool status, device status and to list all devices. Upstream-commit: 80aecc70141f3e7b3138752bf3f0c33b9a273714 Component: engine --- .../engine/graphdriver/devmapper/deviceset.go | 121 +++++++++++++++--- .../docker-device-tool/device_tool.go | 91 ++++++++++--- .../engine/graphdriver/devmapper/driver.go | 2 +- 3 files changed, 176 insertions(+), 38 deletions(-) diff --git a/components/engine/graphdriver/devmapper/deviceset.go b/components/engine/graphdriver/devmapper/deviceset.go index 227bc8ad8c..4e34a92e62 100644 --- a/components/engine/graphdriver/devmapper/deviceset.go +++ b/components/engine/graphdriver/devmapper/deviceset.go @@ -57,6 +57,16 @@ type Status struct { MetadataLoopback string Data DiskUsage Metadata DiskUsage + SectorSize uint64 +} + +type DevStatus struct { + DeviceId int + Size uint64 + TransactionId uint64 + SizeInSectors uint64 + MappedSectors uint64 + HighestMappedSector uint64 } func getDevName(name string) string { @@ -357,13 +367,24 @@ func minor(device uint64) uint64 { return (device & 0xff) | ((device >> 12) & 0xfff00) } -func (devices *DeviceSet) initDevmapper() error { +func (devices *DeviceSet) initDevmapper(doInit bool) error { logInit(devices) // Make sure the sparse images exist in /devicemapper/data and // /devicemapper/metadata - createdLoopback := !devices.hasImage("data") || !devices.hasImage("metadata") + hasData := devices.hasImage("data") + hasMetadata := devices.hasImage("metadata") + + if !doInit && !hasData { + return fmt.Errorf("Looback data file not found %s") + } + + if !doInit && !hasMetadata { + return fmt.Errorf("Looback metadata file not found %s") + } + + createdLoopback := !hasData || !hasMetadata data, err := devices.ensureImage("data", DefaultDataLoopbackSize) if err != nil { utils.Debugf("Error device ensureImage (data): %s\n", err) @@ -438,9 +459,11 @@ func (devices *DeviceSet) initDevmapper() error { } // Setup the base image - if err := devices.setupBaseImage(); err != nil { - utils.Debugf("Error device setupBaseImage: %s\n", err) - return err + if doInit { + if err := devices.setupBaseImage(); err != nil { + utils.Debugf("Error device setupBaseImage: %s\n", err) + return err + } } return nil @@ -757,6 +780,69 @@ func (devices *DeviceSet) setInitialized(hash string) error { return nil } +func (devices *DeviceSet) List() []string { + devices.Lock() + defer devices.Unlock() + + ids := make([]string, len(devices.Devices)) + i := 0 + for k := range devices.Devices { + ids[i] = k + i++ + } + return ids +} + +func (devices *DeviceSet) deviceStatus(devName string) (sizeInSectors, mappedSectors, highestMappedSector uint64, err error) { + var params string + _, sizeInSectors, _, params, err = getStatus(devName) + if err != nil { + return + } + if _, err = fmt.Sscanf(params, "%d %d", &mappedSectors, &highestMappedSector); err == nil { + return + } + return +} + +func (devices *DeviceSet) GetDeviceStatus(hash string) (*DevStatus, error) { + devices.Lock() + defer devices.Unlock() + + info := devices.Devices[hash] + if info == nil { + return nil, fmt.Errorf("No device %s", hash) + } + + status := &DevStatus{ + DeviceId: info.DeviceId, + Size: info.Size, + TransactionId: info.TransactionId, + } + + if err := devices.activateDeviceIfNeeded(hash); err != nil { + return nil, fmt.Errorf("Error activating devmapper device for '%s': %s", hash, err) + } + + if sizeInSectors, mappedSectors, highestMappedSector, err := devices.deviceStatus(info.DevName()); err != nil { + return nil, err + } else { + status.SizeInSectors = sizeInSectors + status.MappedSectors = mappedSectors + status.HighestMappedSector = highestMappedSector + } + + return status, nil +} + +func (devices *DeviceSet) poolStatus() (totalSizeInSectors, transactionId, dataUsed, dataTotal, metadataUsed, metadataTotal uint64, err error) { + var params string + if _, totalSizeInSectors, _, params, err = getStatus(devices.getPoolName()); err == nil { + _, err = fmt.Sscanf(params, "%d %d/%d %d/%d", &transactionId, &metadataUsed, &metadataTotal, &dataUsed, &dataTotal) + } + return +} + func (devices *DeviceSet) Status() *Status { devices.Lock() defer devices.Unlock() @@ -767,26 +853,25 @@ func (devices *DeviceSet) Status() *Status { status.DataLoopback = path.Join(devices.loopbackDir(), "data") status.MetadataLoopback = path.Join(devices.loopbackDir(), "metadata") - _, totalSizeInSectors, _, params, err := getStatus(devices.getPoolName()) + totalSizeInSectors, _, dataUsed, dataTotal, metadataUsed, metadataTotal, err := devices.poolStatus() if err == nil { - var transactionId, dataUsed, dataTotal, metadataUsed, metadataTotal uint64 - if _, err := fmt.Sscanf(params, "%d %d/%d %d/%d", &transactionId, &metadataUsed, &metadataTotal, &dataUsed, &dataTotal); err == nil { - // Convert from blocks to bytes - blockSizeInSectors := totalSizeInSectors / dataTotal + // Convert from blocks to bytes + blockSizeInSectors := totalSizeInSectors / dataTotal - status.Data.Used = dataUsed * blockSizeInSectors * 512 - status.Data.Total = dataTotal * blockSizeInSectors * 512 + status.Data.Used = dataUsed * blockSizeInSectors * 512 + status.Data.Total = dataTotal * blockSizeInSectors * 512 - // metadata blocks are always 4k - status.Metadata.Used = metadataUsed * 4096 - status.Metadata.Total = metadataTotal * 4096 - } + // metadata blocks are always 4k + status.Metadata.Used = metadataUsed * 4096 + status.Metadata.Total = metadataTotal * 4096 + + status.SectorSize = blockSizeInSectors * 512 } return status } -func NewDeviceSet(root string) (*DeviceSet, error) { +func NewDeviceSet(root string, doInit bool) (*DeviceSet, error) { SetDevDir("/dev") devices := &DeviceSet{ @@ -795,7 +880,7 @@ func NewDeviceSet(root string) (*DeviceSet, error) { activeMounts: make(map[string]int), } - if err := devices.initDevmapper(); err != nil { + if err := devices.initDevmapper(doInit); err != nil { return nil, err } diff --git a/components/engine/graphdriver/devmapper/docker-device-tool/device_tool.go b/components/engine/graphdriver/devmapper/docker-device-tool/device_tool.go index f66762da5e..bd7831c0c5 100644 --- a/components/engine/graphdriver/devmapper/docker-device-tool/device_tool.go +++ b/components/engine/graphdriver/devmapper/docker-device-tool/device_tool.go @@ -1,59 +1,112 @@ package main import ( + "flag" "fmt" - "github.com/dotcloud/docker/devmapper" + "github.com/dotcloud/docker/graphdriver/devmapper" "os" + "path" + "sort" ) func usage() { - fmt.Printf("Usage: %s [snap new-id base-id] | [remove id] | [mount id mountpoint]\n", os.Args[0]) + fmt.Fprintf(os.Stderr, "Usage: %s [status] | [list] | [device id] | [snap new-id base-id] | [remove id] | [mount id mountpoint]\n", os.Args[0]) + flag.PrintDefaults() os.Exit(1) } func main() { - devices := devmapper.NewDeviceSet("/var/lib/docker") + root := flag.String("r", "/var/lib/docker", "Docker root dir") + flDebug := flag.Bool("D", false, "Debug mode") - if len(os.Args) < 2 { + flag.Parse() + + if *flDebug { + os.Setenv("DEBUG", "1") + } + + if flag.NArg() < 1 { usage() } - cmd := os.Args[1] - if cmd == "snap" { - if len(os.Args) < 4 { + args := flag.Args() + + home := path.Join(*root, "devicemapper") + devices, err := devmapper.NewDeviceSet(home, false) + if err != nil { + fmt.Println("Can't initialize device mapper: ", err) + os.Exit(1) + } + + switch args[0] { + case "status": + status := devices.Status() + fmt.Printf("Pool name: %s\n", status.PoolName) + fmt.Printf("Data Loopback file: %s\n", status.DataLoopback) + fmt.Printf("Metadata Loopback file: %s\n", status.MetadataLoopback) + fmt.Printf("Sector size: %d\n", status.SectorSize) + fmt.Printf("Data use: %d of %d (%.1f %%)\n", status.Data.Used, status.Data.Total, 100.0*float64(status.Data.Used)/float64(status.Data.Total)) + fmt.Printf("Metadata use: %d of %d (%.1f %%)\n", status.Metadata.Used, status.Metadata.Total, 100.0*float64(status.Metadata.Used)/float64(status.Metadata.Total)) + break + case "list": + ids := devices.List() + sort.Strings(ids) + for _, id := range ids { + fmt.Println(id) + } + break + case "device": + if flag.NArg() < 2 { + usage() + } + status, err := devices.GetDeviceStatus(args[1]) + if err != nil { + fmt.Println("Can't get device info: ", err) + os.Exit(1) + } + fmt.Printf("Id: %d\n", status.DeviceId) + fmt.Printf("Size: %d\n", status.Size) + fmt.Printf("Transaction Id: %d\n", status.TransactionId) + fmt.Printf("Size in Sectors: %d\n", status.SizeInSectors) + fmt.Printf("Mapped Sectors: %d\n", status.MappedSectors) + fmt.Printf("Highest Mapped Sector: %d\n", status.HighestMappedSector) + break + case "snap": + if flag.NArg() < 3 { usage() } - err := devices.AddDevice(os.Args[2], os.Args[3]) + err := devices.AddDevice(args[1], args[2]) if err != nil { fmt.Println("Can't create snap device: ", err) os.Exit(1) } - } else if cmd == "remove" { - if len(os.Args) < 3 { + break + case "remove": + if flag.NArg() < 2 { usage() } - err := devices.RemoveDevice(os.Args[2]) + err := devices.RemoveDevice(args[1]) if err != nil { fmt.Println("Can't remove device: ", err) os.Exit(1) } - } else if cmd == "mount" { - if len(os.Args) < 4 { + break + case "mount": + if flag.NArg() < 3 { usage() } - err := devices.MountDevice(os.Args[2], os.Args[3]) + err := devices.MountDevice(args[1], args[2], false) if err != nil { fmt.Println("Can't create snap device: ", err) os.Exit(1) } - } else { - fmt.Printf("Unknown command %s\n", cmd) - if len(os.Args) < 4 { - usage() - } + break + default: + fmt.Printf("Unknown command %s\n", args[0]) + usage() os.Exit(1) } diff --git a/components/engine/graphdriver/devmapper/driver.go b/components/engine/graphdriver/devmapper/driver.go index 315b7b091e..8e94b7bbc0 100644 --- a/components/engine/graphdriver/devmapper/driver.go +++ b/components/engine/graphdriver/devmapper/driver.go @@ -22,7 +22,7 @@ type Driver struct { } func Init(home string) (graphdriver.Driver, error) { - deviceSet, err := NewDeviceSet(home) + deviceSet, err := NewDeviceSet(home, true) if err != nil { return nil, err } From d0deac1a2c30e2b285206caefa4d582ca4811660 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 19 Nov 2013 00:51:16 -0800 Subject: [PATCH 263/301] Prohibit more than 42 layers in the core We need to do this because we still support aufs and users on other drivers can push incompat images to the registory that aufs users cannot use. Upstream-commit: af753cbad8957f8c51852fdc26a25a8e43114938 Component: engine --- components/engine/image.go | 19 +++++++++++++++++++ components/engine/runtime.go | 14 ++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/components/engine/image.go b/components/engine/image.go index 727a34a389..70f02e2ffa 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -211,6 +211,25 @@ func (img *Image) getParentsSize(size int64) int64 { return parentImage.getParentsSize(size) } +// Depth returns the number of parents for a +// current image +func (img *Image) Depth() (int, error) { + var ( + count = 0 + parent = img + err error + ) + + for parent != nil { + count++ + parent, err = parent.GetParent() + if err != nil { + return -1, err + } + } + return count, nil +} + // Build an Image object from raw json data func NewImgJSON(src []byte) (*Image, error) { ret := &Image{} diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 1146079070..61605186fc 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -23,6 +23,9 @@ import ( "time" ) +// Set the max depth to the aufs restriction +const MaxImageDepth = 42 + var defaultDns = []string{"8.8.8.8", "8.8.4.4"} type Capabilities struct { @@ -366,6 +369,17 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin return nil, nil, err } + // We add 2 layers to the depth because the container's rw and + // init layer add to the restriction + depth, err := img.Depth() + if err != nil { + return nil, nil, err + } + + if depth+2 >= MaxImageDepth { + return nil, nil, fmt.Errorf("Cannot create container with more than %d parents", MaxImageDepth) + } + checkDeprecatedExpose := func(config *Config) bool { if config != nil { if config.PortSpecs != nil { From 90396dc0fc8a88fbb248ea169b22a8089f109ea1 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 18 Nov 2013 12:12:04 +0100 Subject: [PATCH 264/301] devicemapper tool: Add support for pool resizing Upstream-commit: a0224e61b48e1dd1378cb955596fba1937e99068 Component: engine --- .../engine/graphdriver/devmapper/deviceset.go | 66 ++++++++++ .../engine/graphdriver/devmapper/devmapper.go | 113 +++++++++++++++--- .../devmapper/devmapper_wrapper.go | 12 ++ .../docker-device-tool/device_tool.go | 57 ++++++++- 4 files changed, 228 insertions(+), 20 deletions(-) diff --git a/components/engine/graphdriver/devmapper/deviceset.go b/components/engine/graphdriver/devmapper/deviceset.go index 4e34a92e62..e24840634d 100644 --- a/components/engine/graphdriver/devmapper/deviceset.go +++ b/components/engine/graphdriver/devmapper/deviceset.go @@ -367,6 +367,72 @@ func minor(device uint64) uint64 { return (device & 0xff) | ((device >> 12) & 0xfff00) } +func (devices *DeviceSet) ResizePool(size int64) error { + dirname := devices.loopbackDir() + datafilename := path.Join(dirname, "data") + metadatafilename := path.Join(dirname, "metadata") + + datafile, err := os.OpenFile(datafilename, os.O_RDWR, 0) + if datafile == nil { + return err + } + defer datafile.Close() + + fi, err := datafile.Stat() + if fi == nil { + return err + } + + if fi.Size() > size { + return fmt.Errorf("Can't shrink file") + } + + dataloopback := FindLoopDeviceFor(datafile) + if dataloopback == nil { + return fmt.Errorf("Unable to find loopback mount for: %s", datafilename) + } + defer dataloopback.Close() + + metadatafile, err := os.OpenFile(metadatafilename, os.O_RDWR, 0) + if metadatafile == nil { + return err + } + defer metadatafile.Close() + + metadataloopback := FindLoopDeviceFor(metadatafile) + if metadataloopback == nil { + return fmt.Errorf("Unable to find loopback mount for: %s", metadatafilename) + } + defer metadataloopback.Close() + + // Grow loopback file + if err := datafile.Truncate(size); err != nil { + return fmt.Errorf("Unable to grow loopback file: %s", err) + } + + // Reload size for loopback device + if err := LoopbackSetCapacity(dataloopback); err != nil { + return fmt.Errorf("Unable to update loopback capacity: %s", err) + } + + // Suspend the pool + if err := suspendDevice(devices.getPoolName()); err != nil { + return fmt.Errorf("Unable to suspend pool: %s", err) + } + + // Reload with the new block sizes + if err := reloadPool(devices.getPoolName(), dataloopback, metadataloopback); err != nil { + return fmt.Errorf("Unable to reload pool: %s", err) + } + + // Resume the pool + if err := resumeDevice(devices.getPoolName()); err != nil { + return fmt.Errorf("Unable to resume pool: %s", err) + } + + return nil +} + func (devices *DeviceSet) initDevmapper(doInit bool) error { logInit(devices) diff --git a/components/engine/graphdriver/devmapper/devmapper.go b/components/engine/graphdriver/devmapper/devmapper.go index 54e21adddb..6901ba9a6e 100644 --- a/components/engine/graphdriver/devmapper/devmapper.go +++ b/components/engine/graphdriver/devmapper/devmapper.go @@ -6,6 +6,7 @@ import ( "github.com/dotcloud/docker/utils" "os" "runtime" + "syscall" ) type DevmapperLogger interface { @@ -40,25 +41,27 @@ const ( ) var ( - ErrTaskRun = errors.New("dm_task_run failed") - ErrTaskSetName = errors.New("dm_task_set_name failed") - ErrTaskSetMessage = errors.New("dm_task_set_message failed") - ErrTaskSetAddNode = errors.New("dm_task_set_add_node failed") - ErrTaskSetRo = errors.New("dm_task_set_ro failed") - ErrTaskAddTarget = errors.New("dm_task_add_target failed") - ErrTaskSetSector = errors.New("dm_task_set_sector failed") - ErrTaskGetInfo = errors.New("dm_task_get_info failed") - ErrTaskGetDriverVersion = errors.New("dm_task_get_driver_version failed") - ErrTaskSetCookie = errors.New("dm_task_set_cookie failed") - ErrNilCookie = errors.New("cookie ptr can't be nil") - ErrAttachLoopbackDevice = errors.New("loopback mounting failed") - ErrGetBlockSize = errors.New("Can't get block size") - ErrUdevWait = errors.New("wait on udev cookie failed") - ErrSetDevDir = errors.New("dm_set_dev_dir failed") - ErrGetLibraryVersion = errors.New("dm_get_library_version failed") - ErrCreateRemoveTask = errors.New("Can't create task of type DeviceRemove") - ErrRunRemoveDevice = errors.New("running removeDevice failed") - ErrInvalidAddNode = errors.New("Invalide AddNoce type") + ErrTaskRun = errors.New("dm_task_run failed") + ErrTaskSetName = errors.New("dm_task_set_name failed") + ErrTaskSetMessage = errors.New("dm_task_set_message failed") + ErrTaskSetAddNode = errors.New("dm_task_set_add_node failed") + ErrTaskSetRo = errors.New("dm_task_set_ro failed") + ErrTaskAddTarget = errors.New("dm_task_add_target failed") + ErrTaskSetSector = errors.New("dm_task_set_sector failed") + ErrTaskGetInfo = errors.New("dm_task_get_info failed") + ErrTaskGetDriverVersion = errors.New("dm_task_get_driver_version failed") + ErrTaskSetCookie = errors.New("dm_task_set_cookie failed") + ErrNilCookie = errors.New("cookie ptr can't be nil") + ErrAttachLoopbackDevice = errors.New("loopback mounting failed") + ErrGetBlockSize = errors.New("Can't get block size") + ErrUdevWait = errors.New("wait on udev cookie failed") + ErrSetDevDir = errors.New("dm_set_dev_dir failed") + ErrGetLibraryVersion = errors.New("dm_get_library_version failed") + ErrCreateRemoveTask = errors.New("Can't create task of type DeviceRemove") + ErrRunRemoveDevice = errors.New("running removeDevice failed") + ErrInvalidAddNode = errors.New("Invalide AddNoce type") + ErrGetLoopbackBackingFile = errors.New("Unable to get loopback backing file") + ErrLoopbackSetCapacity = errors.New("Unable set loopback capacity") ) type ( @@ -186,6 +189,55 @@ func AttachLoopDevice(filename string) (*os.File, error) { return os.NewFile(uintptr(fd), res), nil } +func getLoopbackBackingFile(file *os.File) (uint64, uint64, error) { + dev, inode, err := dmGetLoopbackBackingFile(file.Fd()) + if err != 0 { + return 0, 0, ErrGetLoopbackBackingFile + } + return dev, inode, nil +} + +func LoopbackSetCapacity(file *os.File) error { + err := dmLoopbackSetCapacity(file.Fd()) + if err != 0 { + return ErrLoopbackSetCapacity + } + return nil +} + +func FindLoopDeviceFor(file *os.File) *os.File { + stat, err := file.Stat() + if err != nil { + return nil + } + targetInode := stat.Sys().(*syscall.Stat_t).Ino + targetDevice := stat.Sys().(*syscall.Stat_t).Dev + + for i := 0; true; i++ { + path := fmt.Sprintf("/dev/loop%d", i) + + file, err := os.OpenFile(path, os.O_RDWR, 0) + if err != nil { + if os.IsNotExist(err) { + return nil + } + + // Ignore all errors until the first not-exist + // we want to continue looking for the file + continue + } + + dev, inode, err := getLoopbackBackingFile(file) + if err == nil && dev == targetDevice && inode == targetInode { + return file + } + + file.Close() + } + + return nil +} + func UdevWait(cookie uint) error { if res := DmUdevWait(cookie); res != 1 { utils.Debugf("Failed to wait on udev cookie %d", cookie) @@ -276,6 +328,29 @@ func createPool(poolName string, dataFile *os.File, metadataFile *os.File) error return nil } +func reloadPool(poolName string, dataFile *os.File, metadataFile *os.File) error { + task, err := createTask(DeviceReload, poolName) + if task == nil { + return err + } + + size, err := GetBlockDeviceSize(dataFile) + if err != nil { + return fmt.Errorf("Can't get data size") + } + + params := metadataFile.Name() + " " + dataFile.Name() + " 128 32768" + if err := task.AddTarget(0, size/512, "thin-pool", params); err != nil { + return fmt.Errorf("Can't add target") + } + + if err := task.Run(); err != nil { + return fmt.Errorf("Error running DeviceCreate") + } + + return nil +} + func createTask(t TaskType, name string) (*Task, error) { task := TaskCreate(t) if task == nil { diff --git a/components/engine/graphdriver/devmapper/devmapper_wrapper.go b/components/engine/graphdriver/devmapper/devmapper_wrapper.go index fcd125e6c5..f9a7d039cf 100644 --- a/components/engine/graphdriver/devmapper/devmapper_wrapper.go +++ b/components/engine/graphdriver/devmapper/devmapper_wrapper.go @@ -239,6 +239,18 @@ func dmTaskAddTargetFct(task *CDmTask, C.uint64_t(start), C.uint64_t(size), Cttype, Cparams)) } +func dmGetLoopbackBackingFile(fd uintptr) (uint64, uint64, syscall.Errno) { + var lo64 C.struct_loop_info64 + _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.LOOP_GET_STATUS64, + uintptr(unsafe.Pointer(&lo64))) + return uint64(lo64.lo_device), uint64(lo64.lo_inode), err +} + +func dmLoopbackSetCapacity(fd uintptr) syscall.Errno { + _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.LOOP_SET_CAPACITY, 0) + return err +} + func dmGetBlockSizeFct(fd uintptr) (int64, syscall.Errno) { var size int64 _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.BLKGETSIZE64, diff --git a/components/engine/graphdriver/devmapper/docker-device-tool/device_tool.go b/components/engine/graphdriver/devmapper/docker-device-tool/device_tool.go index bd7831c0c5..4d1ee0cea5 100644 --- a/components/engine/graphdriver/devmapper/docker-device-tool/device_tool.go +++ b/components/engine/graphdriver/devmapper/docker-device-tool/device_tool.go @@ -7,14 +7,51 @@ import ( "os" "path" "sort" + "strconv" + "strings" ) func usage() { - fmt.Fprintf(os.Stderr, "Usage: %s [status] | [list] | [device id] | [snap new-id base-id] | [remove id] | [mount id mountpoint]\n", os.Args[0]) + fmt.Fprintf(os.Stderr, "Usage: %s [status] | [list] | [device id] | [resize new-pool-size] | [snap new-id base-id] | [remove id] | [mount id mountpoint]\n", os.Args[0]) flag.PrintDefaults() os.Exit(1) } +func byteSizeFromString(arg string) (int64, error) { + digits := "" + rest := "" + last := strings.LastIndexAny(arg, "0123456789") + if last >= 0 { + digits = arg[:last+1] + rest = arg[last+1:] + } + + val, err := strconv.ParseInt(digits, 10, 64) + if err != nil { + return val, err + } + + rest = strings.ToLower(strings.TrimSpace(rest)) + + var multiplier int64 = 1 + switch rest { + case "": + multiplier = 1 + case "k", "kb": + multiplier = 1024 + case "m", "mb": + multiplier = 1024 * 1024 + case "g", "gb": + multiplier = 1024 * 1024 * 1024 + case "t", "tb": + multiplier = 1024 * 1024 * 1024 * 1024 + default: + return 0, fmt.Errorf("Unknown size unit: %s", rest) + } + + return val * multiplier, nil +} + func main() { root := flag.String("r", "/var/lib/docker", "Docker root dir") flDebug := flag.Bool("D", false, "Debug mode") @@ -70,6 +107,24 @@ func main() { fmt.Printf("Size in Sectors: %d\n", status.SizeInSectors) fmt.Printf("Mapped Sectors: %d\n", status.MappedSectors) fmt.Printf("Highest Mapped Sector: %d\n", status.HighestMappedSector) + break + case "resize": + if flag.NArg() < 2 { + usage() + } + + size, err := byteSizeFromString(args[1]) + if err != nil { + fmt.Println("Invalid size: ", err) + os.Exit(1) + } + + err = devices.ResizePool(size) + if err != nil { + fmt.Println("Error resizeing pool: ", err) + os.Exit(1) + } + break case "snap": if flag.NArg() < 3 { From 339d427535ec7307fbcb5c9917ab571a92d1e582 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 19 Nov 2013 10:03:04 +0100 Subject: [PATCH 265/301] Move docker-device-tool to contrib Upstream-commit: 9415c2b1f08ecd168ab5a620a80a5bed34df8adb Component: engine --- .../devmapper => contrib}/docker-device-tool/device_tool.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename components/engine/{graphdriver/devmapper => contrib}/docker-device-tool/device_tool.go (100%) diff --git a/components/engine/graphdriver/devmapper/docker-device-tool/device_tool.go b/components/engine/contrib/docker-device-tool/device_tool.go similarity index 100% rename from components/engine/graphdriver/devmapper/docker-device-tool/device_tool.go rename to components/engine/contrib/docker-device-tool/device_tool.go From 99bee9426b1c84da7704849c552471aee84a4d44 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 19 Nov 2013 10:36:54 +0100 Subject: [PATCH 266/301] Implement fallback for getting the size of a container This moves Driver.Size() to Differ.DiffSize(), removing the empty implementations in devmapper and dummy, and renaming the one in aufs. Then we fall back to a container.Changes() implementation in the non-aufs case. Upstream-commit: 5d76681c3d0cbb744205a397420603ce029ce754 Component: engine --- components/engine/archive/changes.go | 14 ++++++++++ components/engine/container.go | 27 +++++++++++++------ components/engine/graphdriver/aufs/aufs.go | 2 +- .../engine/graphdriver/devmapper/driver.go | 4 --- components/engine/graphdriver/driver.go | 2 +- components/engine/graphdriver/dummy/driver.go | 4 --- 6 files changed, 35 insertions(+), 18 deletions(-) diff --git a/components/engine/archive/changes.go b/components/engine/archive/changes.go index 9305fe2311..83bdcae7cf 100644 --- a/components/engine/archive/changes.go +++ b/components/engine/archive/changes.go @@ -280,6 +280,20 @@ func ChangesDirs(newDir, oldDir string) ([]Change, error) { return newRoot.Changes(oldRoot), nil } +func ChangesSize(newDir string, changes []Change) int64 { + var size int64 + for _, change := range changes { + if change.Kind == ChangeModify || change.Kind == ChangeAdd { + file := filepath.Join(newDir, change.Path) + fileInfo, _ := os.Lstat(file) + if fileInfo != nil && !fileInfo.IsDir() { + size += fileInfo.Size() + } + } + } + return size +} + func ExportChanges(dir string, changes []Change) (Archive, error) { files := make([]string, 0) deletions := make([]string, 0) diff --git a/components/engine/container.go b/components/engine/container.go index ed4e5f4777..02296da3a5 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -7,6 +7,7 @@ import ( "flag" "fmt" "github.com/dotcloud/docker/archive" + "github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/term" "github.com/dotcloud/docker/utils" "github.com/kr/pty" @@ -1552,18 +1553,28 @@ func (container *Container) GetSize() (int64, int64) { driver = container.runtime.driver ) - sizeRw, err = driver.Size(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 } + + if differ, ok := container.runtime.driver.(graphdriver.Differ); ok { + sizeRw, err = differ.DiffSize(container.ID) + if err != nil { + utils.Errorf("Warning: driver %s couldn't return diff size of container %s: %s", driver, container.ID, err) + // FIXME: GetSize should return an error. Not changing it now in case + // there is a side-effect. + sizeRw = -1 + } + } else { + changes, _ := container.Changes() + if changes != nil { + sizeRw = archive.ChangesSize(container.RootfsPath(), changes) + } else { + sizeRw = -1 + } + } + _, err = os.Stat(container.RootfsPath()) if err == nil { filepath.Walk(container.RootfsPath(), func(path string, fileInfo os.FileInfo, err error) error { diff --git a/components/engine/graphdriver/aufs/aufs.go b/components/engine/graphdriver/aufs/aufs.go index c0bc069ce8..b80cc358c4 100644 --- a/components/engine/graphdriver/aufs/aufs.go +++ b/components/engine/graphdriver/aufs/aufs.go @@ -219,7 +219,7 @@ func (a *AufsDriver) ApplyDiff(id string, diff archive.Archive) error { } // Returns the size of the contents for the id -func (a *AufsDriver) Size(id string) (int64, error) { +func (a *AufsDriver) DiffSize(id string) (int64, error) { return utils.TreeSize(path.Join(a.rootPath(), "diff", id)) } diff --git a/components/engine/graphdriver/devmapper/driver.go b/components/engine/graphdriver/devmapper/driver.go index 315b7b091e..109681b527 100644 --- a/components/engine/graphdriver/devmapper/driver.go +++ b/components/engine/graphdriver/devmapper/driver.go @@ -72,10 +72,6 @@ func (d *Driver) Get(id string) (string, error) { return mp, nil } -func (d *Driver) Size(id string) (int64, error) { - return -1, fmt.Errorf("Not implemented") -} - func (d *Driver) mount(id, mountPoint string) error { // Create the target directories if they don't exist if err := os.MkdirAll(mountPoint, 0755); err != nil && !os.IsExist(err) { diff --git a/components/engine/graphdriver/driver.go b/components/engine/graphdriver/driver.go index e55adf4c35..914c6d7623 100644 --- a/components/engine/graphdriver/driver.go +++ b/components/engine/graphdriver/driver.go @@ -17,7 +17,6 @@ type Driver interface { Remove(id string) error Get(id string) (dir string, err error) - Size(id string) (bytes int64, err error) Status() [][2]string @@ -28,6 +27,7 @@ type Differ interface { Diff(id string) (archive.Archive, error) Changes(id string) ([]archive.Change, error) ApplyDiff(id string, diff archive.Archive) error + DiffSize(id string) (bytes int64, err error) } var ( diff --git a/components/engine/graphdriver/dummy/driver.go b/components/engine/graphdriver/dummy/driver.go index 4ca8fac58a..a12a469878 100644 --- a/components/engine/graphdriver/dummy/driver.go +++ b/components/engine/graphdriver/dummy/driver.go @@ -76,7 +76,3 @@ func (d *Driver) Get(id string) (string, error) { } return dir, nil } - -func (d *Driver) Size(id string) (int64, error) { - return -1, fmt.Errorf("Not implemented") -} From c8cec50adf54585ac0fbab2c32018e077e62aac4 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 14 Nov 2013 23:02:09 -0800 Subject: [PATCH 267/301] Add flag to set default graph driver Remove the env var DOCKER_DRIVER Upstream-commit: 6dbeed89c061b85551ab638f93282d87de8ab929 Component: engine --- components/engine/config.go | 2 ++ components/engine/docker/docker.go | 2 ++ components/engine/graphdriver/driver.go | 9 +++++---- components/engine/runtime.go | 4 ++++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/components/engine/config.go b/components/engine/config.go index 75cd0c753f..eb00e41fa1 100644 --- a/components/engine/config.go +++ b/components/engine/config.go @@ -16,6 +16,7 @@ type DaemonConfig struct { BridgeIface string DefaultIp net.IP InterContainerCommunication bool + GraphDriver string } // ConfigFromJob creates and returns a new DaemonConfig object @@ -37,5 +38,6 @@ func ConfigFromJob(job *engine.Job) *DaemonConfig { } config.DefaultIp = net.ParseIP(job.Getenv("DefaultIp")) config.InterContainerCommunication = job.GetenvBool("InterContainerCommunication") + config.GraphDriver = job.Getenv("GraphDriver") return &config } diff --git a/components/engine/docker/docker.go b/components/engine/docker/docker.go index d7d46fff1a..e0fe6d1aa5 100644 --- a/components/engine/docker/docker.go +++ b/components/engine/docker/docker.go @@ -38,6 +38,7 @@ func main() { flEnableIptables := flag.Bool("iptables", true, "Disable iptables within docker") flDefaultIp := flag.String("ip", "0.0.0.0", "Default ip address to use when binding a containers ports") flInterContainerComm := flag.Bool("icc", true, "Enable inter-container communication") + flGraphDriver := flag.String("graph-driver", "", "For docker to use a specific graph driver") flag.Parse() @@ -82,6 +83,7 @@ func main() { job.Setenv("BridgeIface", *bridgeName) job.Setenv("DefaultIp", *flDefaultIp) job.SetenvBool("InterContainerCommunication", *flInterContainerComm) + job.Setenv("GraphDriver", *flGraphDriver) if err := job.Run(); err != nil { log.Fatal(err) } diff --git a/components/engine/graphdriver/driver.go b/components/engine/graphdriver/driver.go index 914c6d7623..4359f10256 100644 --- a/components/engine/graphdriver/driver.go +++ b/components/engine/graphdriver/driver.go @@ -4,10 +4,11 @@ import ( "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/utils" - "os" "path" ) +var DefaultDriver string + type InitFunc func(root string) (Driver, error) type Driver interface { @@ -64,9 +65,9 @@ func GetDriver(name, home string) (Driver, error) { func New(root string) (Driver, error) { var driver Driver var lastError error - // Use environment variable DOCKER_DRIVER to force a choice of driver - if name := os.Getenv("DOCKER_DRIVER"); name != "" { - return GetDriver(name, root) + + if DefaultDriver != "" { + return GetDriver(DefaultDriver, root) } // Check for priority drivers first for _, name := range priority { diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 80e955d4a3..4295d27db8 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -637,6 +637,10 @@ func NewRuntime(config *DaemonConfig) (*Runtime, error) { } func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { + + // Set the default driver + graphdriver.DefaultDriver = config.GraphDriver + // Load storage driver driver, err := graphdriver.New(config.Root) if err != nil { From 2a2c0a4cdc71a84216a7e364a8404e9ca4e8f84d Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 19 Nov 2013 03:13:22 -0800 Subject: [PATCH 268/301] Check env var for setting driver in tests Upstream-commit: aea6001baf0cd0fb20a607e37e4379bf644b28fd Component: engine --- components/engine/graphdriver/driver.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/components/engine/graphdriver/driver.go b/components/engine/graphdriver/driver.go index 4359f10256..a6d075bfb7 100644 --- a/components/engine/graphdriver/driver.go +++ b/components/engine/graphdriver/driver.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/utils" + "os" "path" ) @@ -66,9 +67,15 @@ func New(root string) (Driver, error) { var driver Driver var lastError error - if DefaultDriver != "" { - return GetDriver(DefaultDriver, root) + for _, name := range []string{ + os.Getenv("DOCKER_DRIVER"), + DefaultDriver, + } { + if name != "" { + return GetDriver(name, root) + } } + // Check for priority drivers first for _, name := range priority { driver, lastError = GetDriver(name, root) From a07b5796a9fed7ed9ed40fce55f7c3f8bd0bd98f Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 19 Nov 2013 03:27:59 -0800 Subject: [PATCH 269/301] Rename AufsDriver to Driver to be consistent Upstream-commit: 51a972f38d0b6e2e7a4b27509b58fc7a6e92679f Component: engine --- components/engine/graphdriver/aufs/aufs.go | 42 +++++++++---------- .../engine/graphdriver/aufs/aufs_test.go | 6 +-- components/engine/graphdriver/aufs/migrate.go | 8 ++-- components/engine/runtime.go | 2 +- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/components/engine/graphdriver/aufs/aufs.go b/components/engine/graphdriver/aufs/aufs.go index 4f7e950c9d..fd0ed7b7af 100644 --- a/components/engine/graphdriver/aufs/aufs.go +++ b/components/engine/graphdriver/aufs/aufs.go @@ -37,7 +37,7 @@ func init() { graphdriver.Register("aufs", Init) } -type AufsDriver struct { +type Driver struct { root string } @@ -59,7 +59,7 @@ func Init(root string) (graphdriver.Driver, error) { // If not populate the dir structure if err := os.MkdirAll(root, 0755); err != nil { if os.IsExist(err) { - return &AufsDriver{root}, nil + return &Driver{root}, nil } return nil, err } @@ -69,7 +69,7 @@ func Init(root string) (graphdriver.Driver, error) { return nil, err } } - return &AufsDriver{root}, nil + return &Driver{root}, nil } // Return a nil error if the kernel supports aufs @@ -95,21 +95,21 @@ func supportsAufs() error { return fmt.Errorf("AUFS was not found in /proc/filesystems") } -func (a AufsDriver) rootPath() string { +func (a Driver) rootPath() string { return a.root } -func (AufsDriver) String() string { +func (Driver) String() string { return "aufs" } -func (AufsDriver) Status() [][2]string { +func (Driver) Status() [][2]string { return nil } // Exists returns true if the given id is registered with // this driver -func (a AufsDriver) Exists(id string) bool { +func (a Driver) Exists(id string) bool { if _, err := os.Lstat(path.Join(a.rootPath(), "layers", id)); err != nil { return false } @@ -118,7 +118,7 @@ func (a AufsDriver) Exists(id string) bool { // Three folders are created for each id // mnt, layers, and diff -func (a *AufsDriver) Create(id, parent string) error { +func (a *Driver) Create(id, parent string) error { if err := a.createDirsFor(id); err != nil { return err } @@ -147,7 +147,7 @@ func (a *AufsDriver) Create(id, parent string) error { return nil } -func (a *AufsDriver) createDirsFor(id string) error { +func (a *Driver) createDirsFor(id string) error { paths := []string{ "mnt", "diff", @@ -162,7 +162,7 @@ func (a *AufsDriver) createDirsFor(id string) error { } // Unmount and remove the dir information -func (a *AufsDriver) Remove(id string) error { +func (a *Driver) Remove(id string) error { // Make sure the dir is umounted first if err := a.unmount(id); err != nil { return err @@ -194,7 +194,7 @@ func (a *AufsDriver) Remove(id string) error { // Return the rootfs path for the id // This will mount the dir at it's given path -func (a *AufsDriver) Get(id string) (string, error) { +func (a *Driver) Get(id string) (string, error) { ids, err := getParentIds(a.rootPath(), id) if err != nil { if !os.IsNotExist(err) { @@ -216,23 +216,23 @@ func (a *AufsDriver) Get(id string) (string, error) { } // Returns an archive of the contents for the id -func (a *AufsDriver) Diff(id string) (archive.Archive, error) { +func (a *Driver) Diff(id string) (archive.Archive, error) { return archive.TarFilter(path.Join(a.rootPath(), "diff", id), &archive.TarOptions{ Recursive: true, Compression: archive.Uncompressed, }) } -func (a *AufsDriver) ApplyDiff(id string, diff archive.Archive) error { +func (a *Driver) ApplyDiff(id string, diff archive.Archive) error { return archive.Untar(diff, path.Join(a.rootPath(), "diff", id), nil) } // Returns the size of the contents for the id -func (a *AufsDriver) DiffSize(id string) (int64, error) { +func (a *Driver) DiffSize(id string) (int64, error) { return utils.TreeSize(path.Join(a.rootPath(), "diff", id)) } -func (a *AufsDriver) Changes(id string) ([]archive.Change, error) { +func (a *Driver) Changes(id string) ([]archive.Change, error) { layers, err := a.getParentLayerPaths(id) if err != nil { return nil, err @@ -240,7 +240,7 @@ func (a *AufsDriver) Changes(id string) ([]archive.Change, error) { return archive.Changes(layers, path.Join(a.rootPath(), "diff", id)) } -func (a *AufsDriver) getParentLayerPaths(id string) ([]string, error) { +func (a *Driver) getParentLayerPaths(id string) ([]string, error) { parentIds, err := getParentIds(a.rootPath(), id) if err != nil { return nil, err @@ -257,7 +257,7 @@ func (a *AufsDriver) getParentLayerPaths(id string) ([]string, error) { return layers, nil } -func (a *AufsDriver) mount(id string) error { +func (a *Driver) mount(id string) error { // If the id is mounted or we get an error return if mounted, err := a.mounted(id); err != nil || mounted { return err @@ -279,7 +279,7 @@ func (a *AufsDriver) mount(id string) error { return nil } -func (a *AufsDriver) unmount(id string) error { +func (a *Driver) unmount(id string) error { if mounted, err := a.mounted(id); err != nil || !mounted { return err } @@ -287,13 +287,13 @@ func (a *AufsDriver) unmount(id string) error { return Unmount(target) } -func (a *AufsDriver) mounted(id string) (bool, error) { +func (a *Driver) mounted(id string) (bool, error) { target := path.Join(a.rootPath(), "mnt", id) return Mounted(target) } // During cleanup aufs needs to unmount all mountpoints -func (a *AufsDriver) Cleanup() error { +func (a *Driver) Cleanup() error { ids, err := loadIds(path.Join(a.rootPath(), "layers")) if err != nil { return err @@ -306,7 +306,7 @@ func (a *AufsDriver) Cleanup() error { return nil } -func (a *AufsDriver) aufsMount(ro []string, rw, target string) error { +func (a *Driver) aufsMount(ro []string, rw, target string) error { rwBranch := fmt.Sprintf("%v=rw", rw) roBranches := "" for _, layer := range ro { diff --git a/components/engine/graphdriver/aufs/aufs_test.go b/components/engine/graphdriver/aufs/aufs_test.go index c443fc3ebc..990cf5a867 100644 --- a/components/engine/graphdriver/aufs/aufs_test.go +++ b/components/engine/graphdriver/aufs/aufs_test.go @@ -11,7 +11,7 @@ var ( tmp = path.Join(os.TempDir(), "aufs-tests", "aufs") ) -func newDriver(t *testing.T) *AufsDriver { +func newDriver(t *testing.T) *Driver { if err := os.MkdirAll(tmp, 0755); err != nil { t.Fatal(err) } @@ -20,10 +20,10 @@ func newDriver(t *testing.T) *AufsDriver { if err != nil { t.Fatal(err) } - return d.(*AufsDriver) + return d.(*Driver) } -func TestNewAufsDriver(t *testing.T) { +func TestNewDriver(t *testing.T) { if err := os.MkdirAll(tmp, 0755); err != nil { t.Fatal(err) } diff --git a/components/engine/graphdriver/aufs/migrate.go b/components/engine/graphdriver/aufs/migrate.go index 15008c27da..a37d723976 100644 --- a/components/engine/graphdriver/aufs/migrate.go +++ b/components/engine/graphdriver/aufs/migrate.go @@ -36,7 +36,7 @@ func pathExists(pth string) bool { // For the migration we try to move the folder containing the layer files, if that // fails because the data is currently mounted we will fallback to creating a // symlink. -func (a *AufsDriver) Migrate(pth string, setupInit func(p string) error) error { +func (a *Driver) Migrate(pth string, setupInit func(p string) error) error { if pathExists(path.Join(pth, "graph")) { if err := a.migrateImages(path.Join(pth, "graph")); err != nil { return err @@ -46,7 +46,7 @@ func (a *AufsDriver) Migrate(pth string, setupInit func(p string) error) error { return nil } -func (a *AufsDriver) migrateContainers(pth string, setupInit func(p string) error) error { +func (a *Driver) migrateContainers(pth string, setupInit func(p string) error) error { fis, err := ioutil.ReadDir(pth) if err != nil { return err @@ -88,7 +88,7 @@ func (a *AufsDriver) migrateContainers(pth string, setupInit func(p string) erro return nil } -func (a *AufsDriver) migrateImages(pth string) error { +func (a *Driver) migrateImages(pth string) error { fis, err := ioutil.ReadDir(pth) if err != nil { return err @@ -124,7 +124,7 @@ func (a *AufsDriver) migrateImages(pth string) error { return nil } -func (a *AufsDriver) migrateImage(m *metadata, pth string, migrated map[string]bool) error { +func (a *Driver) migrateImage(m *metadata, pth string, migrated map[string]bool) error { if !migrated[m.ID] { if m.parent != nil { a.migrateImage(m.parent, pth, migrated) diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 80e955d4a3..448ba06813 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -650,7 +650,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { return nil, err } - if ad, ok := driver.(*aufs.AufsDriver); ok { + if ad, ok := driver.(*aufs.Driver); ok { if err := ad.Migrate(config.Root, setupInitLayer); err != nil { return nil, err } From 5e0d1fa18e8341a368a1accae2709b9fdbdfad17 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 19 Nov 2013 13:09:36 +0100 Subject: [PATCH 270/301] dummy driver: Use cp --reflink=auto to copy directories On systems that supports reflinking (i.e. btrfs) this means the dummy backend is much faster at copying files and will be sharing file data in a CoW fashion. On my (btrfs) system this makes "docker run ubuntu echo hello world" go from about 3 seconds to about 1 second. Not instant, but clearly better. cp --reflink=auto is availible since coreutils 7.5 (around 2009), so this seems pretty ok to rely on. cp is also better at preserving file metadata than tar, so for instance it will copy xattrs. Upstream-commit: 242fd4b3ef32b5a20135d6031040bcf099b5df4e Component: engine --- components/engine/graphdriver/dummy/driver.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/components/engine/graphdriver/dummy/driver.go b/components/engine/graphdriver/dummy/driver.go index 4ca8fac58a..ec324a88b5 100644 --- a/components/engine/graphdriver/dummy/driver.go +++ b/components/engine/graphdriver/dummy/driver.go @@ -2,9 +2,9 @@ package dummy import ( "fmt" - "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/graphdriver" "os" + "os/exec" "path" ) @@ -35,6 +35,14 @@ func (d *Driver) Cleanup() error { return nil } +func copyDir(src, dst string) error { + cmd := exec.Command("cp", "-aT", "--reflink=auto", src, dst) + if err := cmd.Run(); err != nil { + return err + } + return nil +} + func (d *Driver) Create(id string, parent string) error { dir := d.dir(id) if err := os.MkdirAll(path.Dir(dir), 0700); err != nil { @@ -50,7 +58,7 @@ func (d *Driver) Create(id string, parent string) error { if err != nil { return fmt.Errorf("%s: %s", parent, err) } - if err := archive.CopyWithTar(parentDir, dir); err != nil { + if err := copyDir(parentDir, dir); err != nil { return err } return nil From f13b8265b6fb7d0b5ac3f348dc1f16111834f173 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 19 Nov 2013 14:40:15 +0100 Subject: [PATCH 271/301] devicemapper: Unmount when removing device Without this the remove will fail due to a busy device. Upstream-commit: 0ed762f2d2ff613052b81d06509dac06b13fc0c9 Component: engine --- components/engine/graphdriver/devmapper/driver.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/components/engine/graphdriver/devmapper/driver.go b/components/engine/graphdriver/devmapper/driver.go index 315b7b091e..84d53e666e 100644 --- a/components/engine/graphdriver/devmapper/driver.go +++ b/components/engine/graphdriver/devmapper/driver.go @@ -61,6 +61,10 @@ func (d *Driver) Create(id string, parent string) error { } func (d *Driver) Remove(id string) error { + mp := path.Join(d.home, "mnt", id) + if err := d.unmount(id, mp); err != nil { + return err + } return d.DeviceSet.RemoveDevice(id) } @@ -90,3 +94,14 @@ func (d *Driver) mount(id, mountPoint string) error { // Mount the device return d.DeviceSet.MountDevice(id, mountPoint, false) } + +func (d *Driver) unmount(id, mountPoint string) error { + // If mountpoint is not mounted, do nothing + if mounted, err := Mounted(mountPoint); err != nil { + return fmt.Errorf("Error checking mountpoint: %s", err) + } else if !mounted { + return nil + } + // Unmount the device + return d.DeviceSet.UnmountDevice(id, mountPoint, true) +} From e7eec12a53cb84e98d97c1e84ad6d32a005dc4bb Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Tue, 19 Nov 2013 15:24:14 -0800 Subject: [PATCH 272/301] Fix unit-tests Upstream-commit: 12e993549df025d072add1a0bcb9bfcc7fe5bdb5 Component: engine --- .../engine/graphdriver/aufs/aufs_test.go | 2 +- .../graphdriver/devmapper/driver_test.go | 16 +++-- .../engine/integration/container_test.go | 64 ------------------- components/engine/integration/graph_test.go | 24 +++---- 4 files changed, 23 insertions(+), 83 deletions(-) diff --git a/components/engine/graphdriver/aufs/aufs_test.go b/components/engine/graphdriver/aufs/aufs_test.go index a33441bbd0..c443fc3ebc 100644 --- a/components/engine/graphdriver/aufs/aufs_test.go +++ b/components/engine/graphdriver/aufs/aufs_test.go @@ -456,7 +456,7 @@ func TestDiffSize(t *testing.T) { t.Fatal(err) } - diffSize, err := d.Size("1") + diffSize, err := d.DiffSize("1") if err != nil { t.Fatal(err) } diff --git a/components/engine/graphdriver/devmapper/driver_test.go b/components/engine/graphdriver/devmapper/driver_test.go index c3c710fb31..f8704950ad 100644 --- a/components/engine/graphdriver/devmapper/driver_test.go +++ b/components/engine/graphdriver/devmapper/driver_test.go @@ -97,6 +97,7 @@ func TestDriverRemove(t *testing.T) { } func TestCleanup(t *testing.T) { + t.Skip("Unimplemented") d := newDriver(t) defer os.RemoveAll(d.home) @@ -160,6 +161,7 @@ func TestCleanup(t *testing.T) { } func TestNotMounted(t *testing.T) { + t.Skip("Not implemented") d := newDriver(t) defer cleanup(d) @@ -291,11 +293,11 @@ func TestDriverGetSize(t *testing.T) { } f.Close() - diffSize, err := d.Size("1") - if err != nil { - t.Fatal(err) - } - if diffSize != size { - t.Fatalf("Expected size %d got %d", size, diffSize) - } + // diffSize, err := d.DiffSize("1") + // if err != nil { + // t.Fatal(err) + // } + // if diffSize != size { + // t.Fatalf("Expected size %d got %d", size, diffSize) + // } } diff --git a/components/engine/integration/container_test.go b/components/engine/integration/container_test.go index c672d5669a..757ffb04b1 100644 --- a/components/engine/integration/container_test.go +++ b/components/engine/integration/container_test.go @@ -1676,67 +1676,3 @@ func TestRestartGhost(t *testing.T) { t.Fatal(err) } } - -func TestRemoveFile(t *testing.T) { - runtime := mkRuntime(t) - defer nuke(runtime) - - container1, _ := mkContainer(runtime, []string{"_", "/bin/sh", "-c", "touch test.txt"}, t) - defer runtime.Destroy(container1) - - if container1.State.Running { - t.Errorf("Container shouldn't be running") - } - if err := container1.Run(); err != nil { - t.Fatal(err) - } - if container1.State.Running { - t.Errorf("Container shouldn't be running") - } - - commit := func(container *Container) (*Image, error) { - rwTar, err := container.ExportRw() - if err != nil { - return nil, err - } - img, err := runtime.graph.Create(rwTar, container, "unit test commited image", "", nil) - if err != nil { - return nil, err - } - return img, nil - } - - img, err := commit(container1) - if err != nil { - t.Fatal(err) - } - - container2, _ := mkContainer(runtime, []string{img.ID, "/bin/sh", "-c", "rm /test.txt"}, t) - defer runtime.Destroy(container2) - - if err := container2.Run(); err != nil { - t.Fatal(err) - } - - containerMount, err := runtime.driver.Get(container2.ID) - if err != nil { - t.Fatal(err) - } - if _, err := os.Stat(path.Join(containerMount, "test.txt")); err == nil { - t.Fatalf("test.txt should not exist") - } - - img, err = commit(container2) - if err != nil { - t.Fatal(err) - } - - mountPoint, err := runtime.driver.Get(img.ID) - if err != nil { - t.Fatal(err) - } - file := path.Join(mountPoint, "test.txt") - if _, err := os.Stat(file); err == nil { - t.Fatalf("The file %s should not exist\n", file) - } -} diff --git a/components/engine/integration/graph_test.go b/components/engine/integration/graph_test.go index bfbedfa48f..8c517255ba 100644 --- a/components/engine/integration/graph_test.go +++ b/components/engine/integration/graph_test.go @@ -2,6 +2,7 @@ package docker import ( "github.com/dotcloud/docker" + "github.com/dotcloud/docker/graphdriver" "io/ioutil" "os" "path" @@ -9,8 +10,10 @@ import ( ) func TestMount(t *testing.T) { - graph := tempGraph(t) + graph, driver := tempGraph(t) defer os.RemoveAll(graph.Root) + defer driver.Cleanup() + archive, err := fakeTar() if err != nil { t.Fatal(err) @@ -32,26 +35,25 @@ func TestMount(t *testing.T) { if err := os.MkdirAll(rw, 0700); err != nil { t.Fatal(err) } - if err := image.Mount(rootfs, rw); err != nil { + + if _, err := driver.Get(image.ID); err != nil { t.Fatal(err) } - // FIXME: test for mount contents - defer func() { - if err := docker.Unmount(rootfs); err != nil { - t.Error(err) - } - }() } //FIXME: duplicate -func tempGraph(t *testing.T) *docker.Graph { +func tempGraph(t *testing.T) (*docker.Graph, graphdriver.Driver) { tmp, err := ioutil.TempDir("", "docker-graph-") if err != nil { t.Fatal(err) } - graph, err := docker.NewGraph(tmp) + driver, err := graphdriver.New(tmp) if err != nil { t.Fatal(err) } - return graph + graph, err := docker.NewGraph(tmp, driver) + if err != nil { + t.Fatal(err) + } + return graph, driver } From 32bf4b495f1ddbaa31d7d8d114c55859202792cc Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 19 Nov 2013 17:08:21 -0800 Subject: [PATCH 273/301] improve aufs cleanup and debugging Upstream-commit: 43899a77bf638d4baa42291c1988bcb2a75e8ef5 Component: engine --- components/engine/graphdriver/aufs/aufs.go | 9 ++++++--- components/engine/runtime.go | 20 +++++++++++++++++--- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/components/engine/graphdriver/aufs/aufs.go b/components/engine/graphdriver/aufs/aufs.go index 4f7e950c9d..c84b2465d0 100644 --- a/components/engine/graphdriver/aufs/aufs.go +++ b/components/engine/graphdriver/aufs/aufs.go @@ -182,14 +182,17 @@ func (a *AufsDriver) Remove(id string) error { return err } realPath := path.Join(a.rootPath(), p, id) - if err := os.Rename(realPath, tmp); err != nil { + if err := os.Rename(realPath, tmp); err != nil && !os.IsNotExist(err) { return err } defer os.RemoveAll(tmp) } // Remove the layers file for the id - return os.Remove(path.Join(a.rootPath(), "layers", id)) + if err := os.Remove(path.Join(a.rootPath(), "layers", id)); err != nil && !os.IsNotExist(err) { + return err + } + return nil } // Return the rootfs path for the id @@ -300,7 +303,7 @@ func (a *AufsDriver) Cleanup() error { } for _, id := range ids { if err := a.unmount(id); err != nil { - return err + utils.Errorf("Unmounting %s: %s", utils.TruncateID(id), err) } } return nil diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 80e955d4a3..1cbc9dd7b1 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -725,9 +725,23 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { } func (runtime *Runtime) Close() error { - runtime.networkManager.Close() - runtime.driver.Cleanup() - return runtime.containerGraph.Close() + errorsStrings := []string{} + if err := runtime.networkManager.Close(); err != nil { + utils.Errorf("runtime.networkManager.Close(): %s", err.Error()) + errorsStrings = append(errorsStrings, err.Error()) + } + if err := runtime.driver.Cleanup(); err != nil { + utils.Errorf("runtime.driver.Cleanup(): %s", err.Error()) + errorsStrings = append(errorsStrings, err.Error()) + } + if err := runtime.containerGraph.Close(); err != nil { + utils.Errorf("runtime.containerGraph.Close(): %s", err.Error()) + errorsStrings = append(errorsStrings, err.Error()) + } + if len(errorsStrings) > 0 { + return fmt.Errorf("%s", strings.Join(errorsStrings, ", ")) + } + return nil } func (runtime *Runtime) Mount(container *Container) error { From 7032acc12cbb69bfe294bdd59796ec3d4c0a2b6a Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 20 Nov 2013 14:42:32 +0100 Subject: [PATCH 274/301] graph_test: Clean up drivers allocated in tempGraph() If we don't do this we leak devicemapper pools with the dm backend. Upstream-commit: 7192be47c5a787094c6450b904622159c85b70de Component: engine --- components/engine/graph_test.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/components/engine/graph_test.go b/components/engine/graph_test.go index c7b295c0e8..3d08feaf6f 100644 --- a/components/engine/graph_test.go +++ b/components/engine/graph_test.go @@ -16,7 +16,7 @@ import ( func TestInit(t *testing.T) { graph := tempGraph(t) - defer os.RemoveAll(graph.Root) + defer nukeGraph(graph) // Root should exist if _, err := os.Stat(graph.Root); err != nil { t.Fatal(err) @@ -32,7 +32,7 @@ func TestInit(t *testing.T) { // Test that Register can be interrupted cleanly without side effects func TestInterruptedRegister(t *testing.T) { graph := tempGraph(t) - defer os.RemoveAll(graph.Root) + defer nukeGraph(graph) badArchive, w := io.Pipe() // Use a pipe reader as a fake archive which never yields data image := &Image{ ID: GenerateID(), @@ -59,7 +59,7 @@ func TestInterruptedRegister(t *testing.T) { // create multiple, check the amount of images and paths, etc..) func TestGraphCreate(t *testing.T) { graph := tempGraph(t) - defer os.RemoveAll(graph.Root) + defer nukeGraph(graph) archive, err := fakeTar() if err != nil { t.Fatal(err) @@ -90,7 +90,7 @@ func TestGraphCreate(t *testing.T) { func TestRegister(t *testing.T) { graph := tempGraph(t) - defer os.RemoveAll(graph.Root) + defer nukeGraph(graph) archive, err := fakeTar() if err != nil { t.Fatal(err) @@ -124,7 +124,7 @@ func TestRegister(t *testing.T) { // Test that an image can be deleted by its shorthand prefix func TestDeletePrefix(t *testing.T) { graph := tempGraph(t) - defer os.RemoveAll(graph.Root) + defer nukeGraph(graph) img := createTestImage(graph, t) if err := graph.Delete(utils.TruncateID(img.ID)); err != nil { t.Fatal(err) @@ -146,7 +146,7 @@ func createTestImage(graph *Graph, t *testing.T) *Image { func TestDelete(t *testing.T) { graph := tempGraph(t) - defer os.RemoveAll(graph.Root) + defer nukeGraph(graph) archive, err := fakeTar() if err != nil { t.Fatal(err) @@ -210,7 +210,7 @@ func TestByParent(t *testing.T) { archive3, _ := fakeTar() graph := tempGraph(t) - defer os.RemoveAll(graph.Root) + defer nukeGraph(graph) parentImage := &Image{ ID: GenerateID(), Comment: "parent", @@ -271,6 +271,11 @@ func tempGraph(t *testing.T) *Graph { return graph } +func nukeGraph(graph *Graph) { + graph.driver.Cleanup() + os.RemoveAll(graph.Root) +} + func testArchive(t *testing.T) archive.Archive { archive, err := fakeTar() if err != nil { From 2037c16136804b2d7e618e63a23894c8ae9b346a Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 20 Nov 2013 14:52:06 +0100 Subject: [PATCH 275/301] tags test: cleanup driver If not we leak a devicemapper pool Upstream-commit: cfdc284abee59057d6c6f7bc751d5648d8f8688e Component: engine --- components/engine/tags_unit_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/components/engine/tags_unit_test.go b/components/engine/tags_unit_test.go index bd8622d46d..1341b989fe 100644 --- a/components/engine/tags_unit_test.go +++ b/components/engine/tags_unit_test.go @@ -47,6 +47,7 @@ func TestLookupImage(t *testing.T) { } defer os.RemoveAll(tmp) store := mkTestTagStore(tmp, t) + defer store.graph.driver.Cleanup() if img, err := store.LookupImage(testImageName); err != nil { t.Fatal(err) From f6524ff456d94e40c975142e7a0f4a27f9be5558 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 19 Nov 2013 02:32:08 -0800 Subject: [PATCH 276/301] Handle image metadata when drivers are switched Upstream-commit: 1b28cdc7f977f265d0d8de53e8ec1d773ed54db1 Component: engine --- components/engine/graph.go | 12 +++++++++++- components/engine/graphdriver/aufs/migrate.go | 11 +++++++++++ components/engine/graphdriver/devmapper/driver.go | 4 ++++ components/engine/graphdriver/driver.go | 1 + components/engine/graphdriver/dummy/driver.go | 5 +++++ components/engine/runtime.go | 2 +- 6 files changed, 33 insertions(+), 2 deletions(-) diff --git a/components/engine/graph.go b/components/engine/graph.go index 6e2a5d7a98..f736027274 100644 --- a/components/engine/graph.go +++ b/components/engine/graph.go @@ -52,7 +52,9 @@ func (graph *Graph) restore() error { } for _, v := range dir { id := v.Name() - graph.idIndex.Add(id) + if graph.driver.Exists(id) { + graph.idIndex.Add(id) + } } return nil } @@ -137,6 +139,14 @@ func (graph *Graph) Register(jsonData []byte, layerData archive.Archive, img *Im if graph.Exists(img.ID) { return fmt.Errorf("Image %s already exists", img.ID) } + + // Ensure that the image root does not exist on the filesystem + // when it is not registered in the graph. + // This is common when you switch from one graph driver to another + if err := os.RemoveAll(graph.imageRoot(img.ID)); err != nil && !os.IsNotExist(err) { + return err + } + tmp, err := graph.Mktemp("") defer os.RemoveAll(tmp) if err != nil { diff --git a/components/engine/graphdriver/aufs/migrate.go b/components/engine/graphdriver/aufs/migrate.go index a37d723976..6018342d6c 100644 --- a/components/engine/graphdriver/aufs/migrate.go +++ b/components/engine/graphdriver/aufs/migrate.go @@ -38,6 +38,9 @@ func pathExists(pth string) bool { // symlink. func (a *Driver) Migrate(pth string, setupInit func(p string) error) error { if pathExists(path.Join(pth, "graph")) { + if err := a.migrateRepositories(pth); err != nil { + return err + } if err := a.migrateImages(path.Join(pth, "graph")); err != nil { return err } @@ -46,6 +49,14 @@ func (a *Driver) Migrate(pth string, setupInit func(p string) error) error { return nil } +func (a *Driver) migrateRepositories(pth string) error { + name := path.Join(pth, "repositories") + if err := os.Rename(name, name+"-aufs"); err != nil && !os.IsNotExist(err) { + return err + } + return nil +} + func (a *Driver) migrateContainers(pth string, setupInit func(p string) error) error { fis, err := ioutil.ReadDir(pth) if err != nil { diff --git a/components/engine/graphdriver/devmapper/driver.go b/components/engine/graphdriver/devmapper/driver.go index f98c619a5c..55b8e3e720 100644 --- a/components/engine/graphdriver/devmapper/driver.go +++ b/components/engine/graphdriver/devmapper/driver.go @@ -121,3 +121,7 @@ func (d *Driver) unmount(id, mountPoint string) error { // Unmount the device return d.DeviceSet.UnmountDevice(id, mountPoint, true) } + +func (d *Driver) Exists(id string) bool { + return d.Devices[id] != nil +} diff --git a/components/engine/graphdriver/driver.go b/components/engine/graphdriver/driver.go index a6d075bfb7..211806e6c7 100644 --- a/components/engine/graphdriver/driver.go +++ b/components/engine/graphdriver/driver.go @@ -19,6 +19,7 @@ type Driver interface { Remove(id string) error Get(id string) (dir string, err error) + Exists(id string) bool Status() [][2]string diff --git a/components/engine/graphdriver/dummy/driver.go b/components/engine/graphdriver/dummy/driver.go index 1b09d324bf..b527a84fd0 100644 --- a/components/engine/graphdriver/dummy/driver.go +++ b/components/engine/graphdriver/dummy/driver.go @@ -84,3 +84,8 @@ func (d *Driver) Get(id string) (string, error) { } return dir, nil } + +func (d *Driver) Exists(id string) bool { + _, err := os.Stat(d.dir(id)) + return err == nil +} diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 9be57b4cd3..7eb0f2bacc 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -671,7 +671,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { if err != nil { return nil, err } - repositories, err := NewTagStore(path.Join(config.Root, "repositories"), g) + repositories, err := NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g) if err != nil { return nil, fmt.Errorf("Couldn't create Tag store: %s", err) } From 495a0f953c74577d81cafb97702d688332dd3e55 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 20 Nov 2013 20:05:10 +0000 Subject: [PATCH 277/301] Mock calls to system functions to facilitate unit testing Upstream-commit: 92f94f06ae2fb4cddace5b817d6a6e326f3c9b33 Component: engine --- .../engine/graphdriver/devmapper/deviceset.go | 10 +++++----- .../engine/graphdriver/devmapper/devmapper.go | 2 +- .../graphdriver/devmapper/devmapper_wrapper.go | 8 ++++---- components/engine/graphdriver/devmapper/sys.go | 15 +++++++++++++++ 4 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 components/engine/graphdriver/devmapper/sys.go diff --git a/components/engine/graphdriver/devmapper/deviceset.go b/components/engine/graphdriver/devmapper/deviceset.go index e24840634d..66e44e3da0 100644 --- a/components/engine/graphdriver/devmapper/deviceset.go +++ b/components/engine/graphdriver/devmapper/deviceset.go @@ -344,7 +344,7 @@ func setCloseOnExec(name string) { if link == name { fd, err := strconv.Atoi(i.Name()) if err == nil { - syscall.CloseOnExec(fd) + SyscallCloseOnExec(fd) } } } @@ -716,7 +716,7 @@ func (devices *DeviceSet) Shutdown() error { for path, count := range devices.activeMounts { for i := count; i > 0; i-- { - if err := syscall.Unmount(path, 0); err != nil { + if err := SyscallUnmount(path, 0); err != nil { utils.Debugf("Shutdown unmounting %s, error: %s\n", path, err) } } @@ -758,9 +758,9 @@ func (devices *DeviceSet) MountDevice(hash, path string, readOnly bool) error { flags = flags | syscall.MS_RDONLY } - err := syscall.Mount(info.DevName(), path, "ext4", flags, "discard") + err := SyscallMount(info.DevName(), path, "ext4", flags, "discard") if err != nil && err == syscall.EINVAL { - err = syscall.Mount(info.DevName(), path, "ext4", flags, "") + err = SyscallMount(info.DevName(), path, "ext4", flags, "") } if err != nil { return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), path, err) @@ -779,7 +779,7 @@ func (devices *DeviceSet) UnmountDevice(hash, path string, deactivate bool) erro defer devices.Unlock() utils.Debugf("[devmapper] Unmount(%s)", path) - if err := syscall.Unmount(path, 0); err != nil { + if err := SyscallUnmount(path, 0); err != nil { utils.Debugf("\n--->Err: %s\n", err) return err } diff --git a/components/engine/graphdriver/devmapper/devmapper.go b/components/engine/graphdriver/devmapper/devmapper.go index 6901ba9a6e..56130bd01c 100644 --- a/components/engine/graphdriver/devmapper/devmapper.go +++ b/components/engine/graphdriver/devmapper/devmapper.go @@ -216,7 +216,7 @@ func FindLoopDeviceFor(file *os.File) *os.File { for i := 0; true; i++ { path := fmt.Sprintf("/dev/loop%d", i) - file, err := os.OpenFile(path, os.O_RDWR, 0) + file, err := OSOpenFile(path, os.O_RDWR, 0) if err != nil { if os.IsNotExist(err) { return nil diff --git a/components/engine/graphdriver/devmapper/devmapper_wrapper.go b/components/engine/graphdriver/devmapper/devmapper_wrapper.go index f9a7d039cf..e261db4351 100644 --- a/components/engine/graphdriver/devmapper/devmapper_wrapper.go +++ b/components/engine/graphdriver/devmapper/devmapper_wrapper.go @@ -241,19 +241,19 @@ func dmTaskAddTargetFct(task *CDmTask, func dmGetLoopbackBackingFile(fd uintptr) (uint64, uint64, syscall.Errno) { var lo64 C.struct_loop_info64 - _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.LOOP_GET_STATUS64, + _, _, err := SyscallSyscall(syscall.SYS_IOCTL, fd, C.LOOP_GET_STATUS64, uintptr(unsafe.Pointer(&lo64))) return uint64(lo64.lo_device), uint64(lo64.lo_inode), err } func dmLoopbackSetCapacity(fd uintptr) syscall.Errno { - _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.LOOP_SET_CAPACITY, 0) + _, _, err := SyscallSyscall(syscall.SYS_IOCTL, fd, C.LOOP_SET_CAPACITY, 0) return err } func dmGetBlockSizeFct(fd uintptr) (int64, syscall.Errno) { var size int64 - _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.BLKGETSIZE64, + _, _, err := SyscallSyscall(syscall.SYS_IOCTL, fd, C.BLKGETSIZE64, uintptr(unsafe.Pointer(&size))) return size, err } @@ -308,7 +308,7 @@ func dmAttachLoopDeviceFct(filename string, fd *int) string { } func getBlockSizeFct(fd uintptr, size *uint64) syscall.Errno { - _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.BLKGETSIZE64, + _, _, err := SyscallSyscall(syscall.SYS_IOCTL, fd, C.BLKGETSIZE64, uintptr(unsafe.Pointer(&size))) return err } diff --git a/components/engine/graphdriver/devmapper/sys.go b/components/engine/graphdriver/devmapper/sys.go new file mode 100644 index 0000000000..c2ba1263f0 --- /dev/null +++ b/components/engine/graphdriver/devmapper/sys.go @@ -0,0 +1,15 @@ +package devmapper + + +import ( + "syscall" +) + + +var ( + SyscallMount = syscall.Mount + SyscallUnmount = syscall.Unmount + SyscallCloseOnExec = syscall.CloseOnExec + SyscallSyscall = syscall.Syscall + OSOpenFile = os.OpenFile +) From 8380e89daf734506e9dd9c1f8213384e475f8a86 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 20 Nov 2013 12:49:01 -0800 Subject: [PATCH 278/301] Remove all syscall calls from devicemapper Upstream-commit: 5690139785fc2bfaa2f233ed41d5f927a8b28dbf Component: engine --- .../engine/graphdriver/devmapper/deviceset.go | 26 +++++++-------- .../engine/graphdriver/devmapper/devmapper.go | 11 +++---- .../graphdriver/devmapper/devmapper_test.go | 3 +- .../devmapper/devmapper_wrapper.go | 32 ++++++++----------- .../engine/graphdriver/devmapper/mount.go | 5 ++- .../engine/graphdriver/devmapper/sys.go | 28 ++++++++++++---- 6 files changed, 57 insertions(+), 48 deletions(-) diff --git a/components/engine/graphdriver/devmapper/deviceset.go b/components/engine/graphdriver/devmapper/deviceset.go index 66e44e3da0..ce66211cab 100644 --- a/components/engine/graphdriver/devmapper/deviceset.go +++ b/components/engine/graphdriver/devmapper/deviceset.go @@ -12,7 +12,6 @@ import ( "path/filepath" "strconv" "sync" - "syscall" "time" ) @@ -344,7 +343,7 @@ func setCloseOnExec(name string) { if link == name { fd, err := strconv.Atoi(i.Name()) if err == nil { - SyscallCloseOnExec(fd) + sysCloseOnExec(fd) } } } @@ -468,7 +467,7 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error { if err != nil { return fmt.Errorf("Error looking up dir %s: %s", devices.root, err) } - sysSt := st.Sys().(*syscall.Stat_t) + sysSt := toSysStatT(st.Sys()) // "reg-" stands for "regular file". // In the future we might use "dev-" for "device file", etc. // docker-maj,min[-inode] stands for: @@ -708,15 +707,16 @@ func (devices *DeviceSet) byHash(hash string) (devname string, err error) { } func (devices *DeviceSet) Shutdown() error { - utils.Debugf("[deviceset %s] shutdown()", devices.devicePrefix) - defer utils.Debugf("[deviceset %s] shutdown END", devices.devicePrefix) devices.Lock() - utils.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root) defer devices.Unlock() + utils.Debugf("[deviceset %s] shutdown()", devices.devicePrefix) + utils.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root) + defer utils.Debugf("[deviceset %s] shutdown END", devices.devicePrefix) + for path, count := range devices.activeMounts { for i := count; i > 0; i-- { - if err := SyscallUnmount(path, 0); err != nil { + if err := sysUnmount(path, 0); err != nil { utils.Debugf("Shutdown unmounting %s, error: %s\n", path, err) } } @@ -752,15 +752,15 @@ func (devices *DeviceSet) MountDevice(hash, path string, readOnly bool) error { info := devices.Devices[hash] - var flags uintptr = syscall.MS_MGC_VAL + var flags uintptr = sysMsMgcVal if readOnly { - flags = flags | syscall.MS_RDONLY + flags = flags | sysMsRdOnly } - err := SyscallMount(info.DevName(), path, "ext4", flags, "discard") - if err != nil && err == syscall.EINVAL { - err = SyscallMount(info.DevName(), path, "ext4", flags, "") + err := sysMount(info.DevName(), path, "ext4", flags, "discard") + if err != nil && err == sysEInval { + err = sysMount(info.DevName(), path, "ext4", flags, "") } if err != nil { return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), path, err) @@ -779,7 +779,7 @@ func (devices *DeviceSet) UnmountDevice(hash, path string, deactivate bool) erro defer devices.Unlock() utils.Debugf("[devmapper] Unmount(%s)", path) - if err := SyscallUnmount(path, 0); err != nil { + if err := sysUnmount(path, 0); err != nil { utils.Debugf("\n--->Err: %s\n", err) return err } diff --git a/components/engine/graphdriver/devmapper/devmapper.go b/components/engine/graphdriver/devmapper/devmapper.go index 56130bd01c..cccb168f95 100644 --- a/components/engine/graphdriver/devmapper/devmapper.go +++ b/components/engine/graphdriver/devmapper/devmapper.go @@ -6,7 +6,6 @@ import ( "github.com/dotcloud/docker/utils" "os" "runtime" - "syscall" ) type DevmapperLogger interface { @@ -210,13 +209,13 @@ func FindLoopDeviceFor(file *os.File) *os.File { if err != nil { return nil } - targetInode := stat.Sys().(*syscall.Stat_t).Ino - targetDevice := stat.Sys().(*syscall.Stat_t).Dev + targetInode := stat.Sys().(*sysStatT).Ino + targetDevice := stat.Sys().(*sysStatT).Dev for i := 0; true; i++ { path := fmt.Sprintf("/dev/loop%d", i) - file, err := OSOpenFile(path, os.O_RDWR, 0) + file, err := osOpenFile(path, os.O_RDWR, 0) if err != nil { if os.IsNotExist(err) { return nil @@ -394,8 +393,8 @@ func getStatus(name string) (uint64, uint64, string, string, error) { return 0, 0, "", "", fmt.Errorf("Non existing device %s", name) } - _, start, length, target_type, params := task.GetNextTarget(0) - return start, length, target_type, params, nil + _, start, length, targetType, params := task.GetNextTarget(0) + return start, length, targetType, params, nil } func setTransactionId(poolName string, oldId uint64, newId uint64) error { diff --git a/components/engine/graphdriver/devmapper/devmapper_test.go b/components/engine/graphdriver/devmapper/devmapper_test.go index 8d93ab30b1..33167f0953 100644 --- a/components/engine/graphdriver/devmapper/devmapper_test.go +++ b/components/engine/graphdriver/devmapper/devmapper_test.go @@ -1,7 +1,6 @@ package devmapper import ( - "syscall" "testing" ) @@ -264,7 +263,7 @@ func dmAttachLoopDeviceFail(filename string, fd *int) string { return "" } -func sysGetBlockSizeFail(fd uintptr, size *uint64) syscall.Errno { +func sysGetBlockSizeFail(fd uintptr, size *uint64) sysErrno { return 1 } diff --git a/components/engine/graphdriver/devmapper/devmapper_wrapper.go b/components/engine/graphdriver/devmapper/devmapper_wrapper.go index e261db4351..00b7ecb383 100644 --- a/components/engine/graphdriver/devmapper/devmapper_wrapper.go +++ b/components/engine/graphdriver/devmapper/devmapper_wrapper.go @@ -140,7 +140,6 @@ static void log_with_errno_init() import "C" import ( - "syscall" "unsafe" ) @@ -239,23 +238,22 @@ func dmTaskAddTargetFct(task *CDmTask, C.uint64_t(start), C.uint64_t(size), Cttype, Cparams)) } -func dmGetLoopbackBackingFile(fd uintptr) (uint64, uint64, syscall.Errno) { +func dmGetLoopbackBackingFile(fd uintptr) (uint64, uint64, sysErrno) { var lo64 C.struct_loop_info64 - _, _, err := SyscallSyscall(syscall.SYS_IOCTL, fd, C.LOOP_GET_STATUS64, + _, _, err := sysSyscall(sysSysIoctl, fd, C.LOOP_GET_STATUS64, uintptr(unsafe.Pointer(&lo64))) - return uint64(lo64.lo_device), uint64(lo64.lo_inode), err + return uint64(lo64.lo_device), uint64(lo64.lo_inode), sysErrno(err) } -func dmLoopbackSetCapacity(fd uintptr) syscall.Errno { - _, _, err := SyscallSyscall(syscall.SYS_IOCTL, fd, C.LOOP_SET_CAPACITY, 0) - return err +func dmLoopbackSetCapacity(fd uintptr) sysErrno { + _, _, err := sysSyscall(sysSysIoctl, fd, C.LOOP_SET_CAPACITY, 0) + return sysErrno(err) } -func dmGetBlockSizeFct(fd uintptr) (int64, syscall.Errno) { +func dmGetBlockSizeFct(fd uintptr) (int64, sysErrno) { var size int64 - _, _, err := SyscallSyscall(syscall.SYS_IOCTL, fd, C.BLKGETSIZE64, - uintptr(unsafe.Pointer(&size))) - return size, err + _, _, err := sysSyscall(sysSysIoctl, fd, C.BLKGETSIZE64, uintptr(unsafe.Pointer(&size))) + return size, sysErrno(err) } func dmTaskGetInfoFct(task *CDmTask, info *Info) int { @@ -275,9 +273,7 @@ func dmTaskGetInfoFct(task *CDmTask, info *Info) int { return int(C.dm_task_get_info((*C.struct_dm_task)(task), &Cinfo)) } -func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64, - target, params *string) uintptr { - +func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64, target, params *string) uintptr { var ( Cstart, Clength C.uint64_t CtargetType, Cparams *C.char @@ -288,6 +284,7 @@ func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64, *target = C.GoString(CtargetType) *params = C.GoString(Cparams) }() + nextp := C.dm_get_next_target((*C.struct_dm_task)(task), unsafe.Pointer(next), &Cstart, &Clength, &CtargetType, &Cparams) return uintptr(nextp) @@ -307,10 +304,9 @@ func dmAttachLoopDeviceFct(filename string, fd *int) string { return C.GoString(ret) } -func getBlockSizeFct(fd uintptr, size *uint64) syscall.Errno { - _, _, err := SyscallSyscall(syscall.SYS_IOCTL, fd, C.BLKGETSIZE64, - uintptr(unsafe.Pointer(&size))) - return err +func getBlockSizeFct(fd uintptr, size *uint64) sysErrno { + _, _, err := sysSyscall(sysSysIoctl, fd, C.BLKGETSIZE64, uintptr(unsafe.Pointer(&size))) + return sysErrno(err) } func dmUdevWaitFct(cookie uint) int { diff --git a/components/engine/graphdriver/devmapper/mount.go b/components/engine/graphdriver/devmapper/mount.go index e3a303e507..f066c51bbf 100644 --- a/components/engine/graphdriver/devmapper/mount.go +++ b/components/engine/graphdriver/devmapper/mount.go @@ -3,7 +3,6 @@ package devmapper import ( "os" "path/filepath" - "syscall" ) // FIXME: this is copy-pasted from the aufs driver. @@ -21,7 +20,7 @@ func Mounted(mountpoint string) (bool, error) { if err != nil { return false, err } - mntpointSt := mntpoint.Sys().(*syscall.Stat_t) - parentSt := parent.Sys().(*syscall.Stat_t) + mntpointSt := toSysStatT(mntpoint.Sys()) + parentSt := toSysStatT(parent.Sys()) return mntpointSt.Dev != parentSt.Dev, nil } diff --git a/components/engine/graphdriver/devmapper/sys.go b/components/engine/graphdriver/devmapper/sys.go index c2ba1263f0..d5fcde490a 100644 --- a/components/engine/graphdriver/devmapper/sys.go +++ b/components/engine/graphdriver/devmapper/sys.go @@ -1,15 +1,31 @@ package devmapper - import ( + "os" "syscall" ) +type ( + sysStatT syscall.Stat_t + sysErrno syscall.Errno +) var ( - SyscallMount = syscall.Mount - SyscallUnmount = syscall.Unmount - SyscallCloseOnExec = syscall.CloseOnExec - SyscallSyscall = syscall.Syscall - OSOpenFile = os.OpenFile + // functions + sysMount = syscall.Mount + sysUnmount = syscall.Unmount + sysCloseOnExec = syscall.CloseOnExec + sysSyscall = syscall.Syscall + osOpenFile = os.OpenFile ) + +const ( + sysMsMgcVal = syscall.MS_MGC_VAL + sysMsRdOnly = syscall.MS_RDONLY + sysEInval = syscall.EINVAL + sysSysIoctl = syscall.SYS_IOCTL +) + +func toSysStatT(i interface{}) *sysStatT { + return (*sysStatT)(i.(*syscall.Stat_t)) +} From 3bd981cb5359a3f041b57339c6de0f4c9f167641 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 20 Nov 2013 13:05:17 -0800 Subject: [PATCH 279/301] Remove os from devmapper Upstream-commit: a39bd656622c7dbea534fe34710ed6cb21dd5a90 Component: engine --- .../engine/graphdriver/devmapper/deviceset.go | 30 +++++++++---------- .../engine/graphdriver/devmapper/devmapper.go | 25 ++++++++-------- .../engine/graphdriver/devmapper/driver.go | 5 ++-- .../graphdriver/devmapper/driver_test.go | 11 ++++--- .../engine/graphdriver/devmapper/mount.go | 7 ++--- .../engine/graphdriver/devmapper/sys.go | 18 +++++++++-- 6 files changed, 52 insertions(+), 44 deletions(-) diff --git a/components/engine/graphdriver/devmapper/deviceset.go b/components/engine/graphdriver/devmapper/deviceset.go index ce66211cab..02d6e6bc3a 100644 --- a/components/engine/graphdriver/devmapper/deviceset.go +++ b/components/engine/graphdriver/devmapper/deviceset.go @@ -6,7 +6,6 @@ import ( "github.com/dotcloud/docker/utils" "io" "io/ioutil" - "os" "os/exec" "path" "path/filepath" @@ -104,7 +103,7 @@ func (devices *DeviceSet) hasImage(name string) bool { dirname := devices.loopbackDir() filename := path.Join(dirname, name) - _, err := os.Stat(filename) + _, err := osStat(filename) return err == nil } @@ -116,16 +115,16 @@ func (devices *DeviceSet) ensureImage(name string, size int64) (string, error) { dirname := devices.loopbackDir() filename := path.Join(dirname, name) - if err := os.MkdirAll(dirname, 0700); err != nil && !os.IsExist(err) { + if err := osMkdirAll(dirname, 0700); err != nil && !osIsExist(err) { return "", err } - if _, err := os.Stat(filename); err != nil { - if !os.IsNotExist(err) { + if _, err := osStat(filename); err != nil { + if !osIsNotExist(err) { return "", err } utils.Debugf("Creating loopback file %s for device-manage use", filename) - file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0600) + file, err := osOpenFile(filename, osORdWr|osOCreate, 0600) if err != nil { return "", err } @@ -173,7 +172,7 @@ func (devices *DeviceSet) saveMetadata() error { if err := tmpFile.Close(); err != nil { return fmt.Errorf("Error closing metadata file %s: %s", tmpFile.Name(), err) } - if err := os.Rename(tmpFile.Name(), devices.jsonFile()); err != nil { + if err := osRename(tmpFile.Name(), devices.jsonFile()); err != nil { return fmt.Errorf("Error committing metadata file", err) } @@ -251,7 +250,7 @@ func (devices *DeviceSet) loadMetaData() error { devices.NewTransactionId = devices.TransactionId jsonData, err := ioutil.ReadFile(devices.jsonFile()) - if err != nil && !os.IsNotExist(err) { + if err != nil && !osIsNotExist(err) { utils.Debugf("\n--->Err: %s\n", err) return err } @@ -336,10 +335,9 @@ func (devices *DeviceSet) setupBaseImage() error { } func setCloseOnExec(name string) { - fileInfos, _ := ioutil.ReadDir("/proc/self/fd") - if fileInfos != nil { + if fileInfos, _ := ioutil.ReadDir("/proc/self/fd"); fileInfos != nil { for _, i := range fileInfos { - link, _ := os.Readlink(filepath.Join("/proc/self/fd", i.Name())) + link, _ := osReadlink(filepath.Join("/proc/self/fd", i.Name())) if link == name { fd, err := strconv.Atoi(i.Name()) if err == nil { @@ -371,7 +369,7 @@ func (devices *DeviceSet) ResizePool(size int64) error { datafilename := path.Join(dirname, "data") metadatafilename := path.Join(dirname, "metadata") - datafile, err := os.OpenFile(datafilename, os.O_RDWR, 0) + datafile, err := osOpenFile(datafilename, osORdWr, 0) if datafile == nil { return err } @@ -386,19 +384,19 @@ func (devices *DeviceSet) ResizePool(size int64) error { return fmt.Errorf("Can't shrink file") } - dataloopback := FindLoopDeviceFor(datafile) + dataloopback := FindLoopDeviceFor(&osFile{File: datafile}) if dataloopback == nil { return fmt.Errorf("Unable to find loopback mount for: %s", datafilename) } defer dataloopback.Close() - metadatafile, err := os.OpenFile(metadatafilename, os.O_RDWR, 0) + metadatafile, err := osOpenFile(metadatafilename, osORdWr, 0) if metadatafile == nil { return err } defer metadatafile.Close() - metadataloopback := FindLoopDeviceFor(metadatafile) + metadataloopback := FindLoopDeviceFor(&osFile{File: metadatafile}) if metadataloopback == nil { return fmt.Errorf("Unable to find loopback mount for: %s", metadatafilename) } @@ -463,7 +461,7 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error { // Set the device prefix from the device id and inode of the docker root dir - st, err := os.Stat(devices.root) + st, err := osStat(devices.root) if err != nil { return fmt.Errorf("Error looking up dir %s: %s", devices.root, err) } diff --git a/components/engine/graphdriver/devmapper/devmapper.go b/components/engine/graphdriver/devmapper/devmapper.go index cccb168f95..103937b7ad 100644 --- a/components/engine/graphdriver/devmapper/devmapper.go +++ b/components/engine/graphdriver/devmapper/devmapper.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "github.com/dotcloud/docker/utils" - "os" "runtime" ) @@ -179,16 +178,16 @@ func (t *Task) GetNextTarget(next uintptr) (nextPtr uintptr, start uint64, start, length, targetType, params } -func AttachLoopDevice(filename string) (*os.File, error) { +func AttachLoopDevice(filename string) (*osFile, error) { var fd int res := DmAttachLoopDevice(filename, &fd) if res == "" { return nil, ErrAttachLoopbackDevice } - return os.NewFile(uintptr(fd), res), nil + return &osFile{File: osNewFile(uintptr(fd), res)}, nil } -func getLoopbackBackingFile(file *os.File) (uint64, uint64, error) { +func getLoopbackBackingFile(file *osFile) (uint64, uint64, error) { dev, inode, err := dmGetLoopbackBackingFile(file.Fd()) if err != 0 { return 0, 0, ErrGetLoopbackBackingFile @@ -196,7 +195,7 @@ func getLoopbackBackingFile(file *os.File) (uint64, uint64, error) { return dev, inode, nil } -func LoopbackSetCapacity(file *os.File) error { +func LoopbackSetCapacity(file *osFile) error { err := dmLoopbackSetCapacity(file.Fd()) if err != 0 { return ErrLoopbackSetCapacity @@ -204,7 +203,7 @@ func LoopbackSetCapacity(file *os.File) error { return nil } -func FindLoopDeviceFor(file *os.File) *os.File { +func FindLoopDeviceFor(file *osFile) *osFile { stat, err := file.Stat() if err != nil { return nil @@ -215,9 +214,9 @@ func FindLoopDeviceFor(file *os.File) *os.File { for i := 0; true; i++ { path := fmt.Sprintf("/dev/loop%d", i) - file, err := osOpenFile(path, os.O_RDWR, 0) + file, err := osOpenFile(path, osORdWr, 0) if err != nil { - if os.IsNotExist(err) { + if osIsNotExist(err) { return nil } @@ -226,9 +225,9 @@ func FindLoopDeviceFor(file *os.File) *os.File { continue } - dev, inode, err := getLoopbackBackingFile(file) + dev, inode, err := getLoopbackBackingFile(&osFile{File: file}) if err == nil && dev == targetDevice && inode == targetInode { - return file + return &osFile{File: file} } file.Close() @@ -288,7 +287,7 @@ func RemoveDevice(name string) error { return nil } -func GetBlockDeviceSize(file *os.File) (uint64, error) { +func GetBlockDeviceSize(file *osFile) (uint64, error) { size, errno := DmGetBlockSize(file.Fd()) if size == -1 || errno != 0 { return 0, ErrGetBlockSize @@ -297,7 +296,7 @@ func GetBlockDeviceSize(file *os.File) (uint64, error) { } // This is the programmatic example of "dmsetup create" -func createPool(poolName string, dataFile *os.File, metadataFile *os.File) error { +func createPool(poolName string, dataFile, metadataFile *osFile) error { task, err := createTask(DeviceCreate, poolName) if task == nil { return err @@ -327,7 +326,7 @@ func createPool(poolName string, dataFile *os.File, metadataFile *os.File) error return nil } -func reloadPool(poolName string, dataFile *os.File, metadataFile *os.File) error { +func reloadPool(poolName string, dataFile, metadataFile *osFile) error { task, err := createTask(DeviceReload, poolName) if task == nil { return err diff --git a/components/engine/graphdriver/devmapper/driver.go b/components/engine/graphdriver/devmapper/driver.go index 55b8e3e720..70e62a0a80 100644 --- a/components/engine/graphdriver/devmapper/driver.go +++ b/components/engine/graphdriver/devmapper/driver.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/dotcloud/docker/graphdriver" "io/ioutil" - "os" "path" ) @@ -67,7 +66,7 @@ func (d *Driver) Create(id string, parent string) error { return err } - if err := os.MkdirAll(path.Join(mp, "rootfs"), 0755); err != nil && !os.IsExist(err) { + if err := osMkdirAll(path.Join(mp, "rootfs"), 0755); err != nil && !osIsExist(err) { return err } @@ -98,7 +97,7 @@ func (d *Driver) Get(id string) (string, error) { func (d *Driver) mount(id, mountPoint string) error { // Create the target directories if they don't exist - if err := os.MkdirAll(mountPoint, 0755); err != nil && !os.IsExist(err) { + if err := osMkdirAll(mountPoint, 0755); err != nil && !osIsExist(err) { return err } // If mountpoint is already mounted, do nothing diff --git a/components/engine/graphdriver/devmapper/driver_test.go b/components/engine/graphdriver/devmapper/driver_test.go index f8704950ad..02c614a270 100644 --- a/components/engine/graphdriver/devmapper/driver_test.go +++ b/components/engine/graphdriver/devmapper/driver_test.go @@ -2,7 +2,6 @@ package devmapper import ( "io/ioutil" - "os" "path" "testing" ) @@ -34,12 +33,12 @@ func newDriver(t *testing.T) *Driver { func cleanup(d *Driver) { d.Cleanup() - os.RemoveAll(d.home) + osRemoveAll(d.home) } func TestInit(t *testing.T) { home := mkTestDirectory(t) - defer os.RemoveAll(home) + defer osRemoveAll(home) driver, err := Init(home) if err != nil { t.Fatal(err) @@ -58,7 +57,7 @@ func TestInit(t *testing.T) { if err != nil { t.Fatal(err) } - if st, err := os.Stat(dir); err != nil { + if st, err := osStat(dir); err != nil { t.Fatal(err) } else if !st.IsDir() { t.Fatalf("Get(%V) did not return a directory", id) @@ -99,7 +98,7 @@ func TestDriverRemove(t *testing.T) { func TestCleanup(t *testing.T) { t.Skip("Unimplemented") d := newDriver(t) - defer os.RemoveAll(d.home) + defer osRemoveAll(d.home) mountPoints := make([]string, 2) @@ -284,7 +283,7 @@ func TestDriverGetSize(t *testing.T) { size := int64(1024) - f, err := os.Create(path.Join(mountPoint, "test_file")) + f, err := osCreate(path.Join(mountPoint, "test_file")) if err != nil { t.Fatal(err) } diff --git a/components/engine/graphdriver/devmapper/mount.go b/components/engine/graphdriver/devmapper/mount.go index f066c51bbf..3f75cfbcf9 100644 --- a/components/engine/graphdriver/devmapper/mount.go +++ b/components/engine/graphdriver/devmapper/mount.go @@ -1,7 +1,6 @@ package devmapper import ( - "os" "path/filepath" ) @@ -9,14 +8,14 @@ import ( // It should be moved into the core. func Mounted(mountpoint string) (bool, error) { - mntpoint, err := os.Stat(mountpoint) + mntpoint, err := osStat(mountpoint) if err != nil { - if os.IsNotExist(err) { + if osIsNotExist(err) { return false, nil } return false, err } - parent, err := os.Stat(filepath.Join(mountpoint, "..")) + parent, err := osStat(filepath.Join(mountpoint, "..")) if err != nil { return false, err } diff --git a/components/engine/graphdriver/devmapper/sys.go b/components/engine/graphdriver/devmapper/sys.go index d5fcde490a..8250e3ed7d 100644 --- a/components/engine/graphdriver/devmapper/sys.go +++ b/components/engine/graphdriver/devmapper/sys.go @@ -8,15 +8,26 @@ import ( type ( sysStatT syscall.Stat_t sysErrno syscall.Errno + + osFile struct{ *os.File } ) var ( - // functions sysMount = syscall.Mount sysUnmount = syscall.Unmount sysCloseOnExec = syscall.CloseOnExec sysSyscall = syscall.Syscall - osOpenFile = os.OpenFile + + osOpenFile = os.OpenFile + osNewFile = os.NewFile + osCreate = os.Create + osStat = os.Stat + osIsNotExist = os.IsNotExist + osIsExist = os.IsExist + osMkdirAll = os.MkdirAll + osRemoveAll = os.RemoveAll + osRename = os.Rename + osReadlink = os.Readlink ) const ( @@ -24,6 +35,9 @@ const ( sysMsRdOnly = syscall.MS_RDONLY sysEInval = syscall.EINVAL sysSysIoctl = syscall.SYS_IOCTL + + osORdWr = os.O_RDWR + osOCreate = os.O_CREATE ) func toSysStatT(i interface{}) *sysStatT { From 02fde26286a1b5005fcd6ce17ee2535e66d49f8b Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Wed, 20 Nov 2013 13:51:05 -0800 Subject: [PATCH 280/301] wait on pull already in progress Upstream-commit: 8a756f417ee4925bad6c8526fabda05ab8a6c041 Component: engine --- components/engine/server.go | 51 ++++++++++++++------------- components/engine/server_unit_test.go | 35 +++++++----------- 2 files changed, 39 insertions(+), 47 deletions(-) diff --git a/components/engine/server.go b/components/engine/server.go index 10d3ecb2f8..0143e17835 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -732,9 +732,9 @@ func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoin id := history[i] // ensure no two downloads of the same layer happen at the same time - if err := srv.poolAdd("pull", "layer:"+id); err != nil { + if c, err := srv.poolAdd("pull", "layer:"+id); err != nil { utils.Errorf("Image (id: %s) pull is already running, skipping: %v", id, err) - return nil + <-c } defer srv.poolRemove("pull", "layer:"+id) @@ -829,7 +829,7 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName } // ensure no two downloads of the same image happen at the same time - if err := srv.poolAdd("pull", "img:"+img.ID); err != nil { + if _, err := srv.poolAdd("pull", "img:"+img.ID); err != nil { utils.Errorf("Image (id: %s) pull is already running, skipping: %v", img.ID, err) if parallel { errors <- nil @@ -900,38 +900,41 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName return nil } -func (srv *Server) poolAdd(kind, key string) error { +func (srv *Server) poolAdd(kind, key string) (chan struct{}, error) { srv.Lock() defer srv.Unlock() - if _, exists := srv.pullingPool[key]; exists { - return fmt.Errorf("pull %s is already in progress", key) + if c, exists := srv.pullingPool[key]; exists { + return c, fmt.Errorf("pull %s is already in progress", key) } - if _, exists := srv.pushingPool[key]; exists { - return fmt.Errorf("push %s is already in progress", key) + if c, exists := srv.pushingPool[key]; exists { + return c, fmt.Errorf("push %s is already in progress", key) } + c := make(chan struct{}) switch kind { case "pull": - srv.pullingPool[key] = struct{}{} - break + srv.pullingPool[key] = c case "push": - srv.pushingPool[key] = struct{}{} - break + srv.pushingPool[key] = c default: - return fmt.Errorf("Unknown pool type") + return nil, fmt.Errorf("Unknown pool type") } - return nil + return c, nil } func (srv *Server) poolRemove(kind, key string) error { switch kind { case "pull": - delete(srv.pullingPool, key) - break + if c, exists := srv.pullingPool[key]; exists { + close(c) + delete(srv.pullingPool, key) + } case "push": - delete(srv.pushingPool, key) - break + if c, exists := srv.pushingPool[key]; exists { + close(c) + delete(srv.pushingPool, key) + } default: return fmt.Errorf("Unknown pool type") } @@ -943,7 +946,7 @@ func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *ut if err != nil { return err } - if err := srv.poolAdd("pull", localName+":"+tag); err != nil { + if _, err := srv.poolAdd("pull", localName+":"+tag); err != nil { return err } defer srv.poolRemove("pull", localName+":"+tag) @@ -1138,7 +1141,7 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, // FIXME: Allow to interrupt current push when new push of same image is done. func (srv *Server) ImagePush(localName string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig, metaHeaders map[string][]string) error { - if err := srv.poolAdd("push", localName); err != nil { + if _, err := srv.poolAdd("push", localName); err != nil { return err } defer srv.poolRemove("push", localName) @@ -1769,8 +1772,8 @@ func NewServer(eng *engine.Engine, config *DaemonConfig) (*Server, error) { srv := &Server{ Eng: eng, runtime: runtime, - pullingPool: make(map[string]struct{}), - pushingPool: make(map[string]struct{}), + pullingPool: make(map[string]chan struct{}), + pushingPool: make(map[string]chan struct{}), events: make([]utils.JSONMessage, 0, 64), //only keeps the 64 last events listeners: make(map[string]chan utils.JSONMessage), reqFactory: nil, @@ -1807,8 +1810,8 @@ func (srv *Server) LogEvent(action, id, from string) *utils.JSONMessage { type Server struct { sync.Mutex runtime *Runtime - pullingPool map[string]struct{} - pushingPool map[string]struct{} + pullingPool map[string]chan struct{} + pushingPool map[string]chan struct{} events []utils.JSONMessage listeners map[string]chan utils.JSONMessage reqFactory *utils.HTTPRequestFactory diff --git a/components/engine/server_unit_test.go b/components/engine/server_unit_test.go index a51e2ddff5..4c0d24ca1d 100644 --- a/components/engine/server_unit_test.go +++ b/components/engine/server_unit_test.go @@ -8,49 +8,38 @@ import ( func TestPools(t *testing.T) { srv := &Server{ - pullingPool: make(map[string]struct{}), - pushingPool: make(map[string]struct{}), + pullingPool: make(map[string]chan struct{}), + pushingPool: make(map[string]chan struct{}), } - err := srv.poolAdd("pull", "test1") - if err != nil { + if _, err := srv.poolAdd("pull", "test1"); err != nil { t.Fatal(err) } - err = srv.poolAdd("pull", "test2") - if err != nil { + if _, err := srv.poolAdd("pull", "test2"); err != nil { t.Fatal(err) } - err = srv.poolAdd("push", "test1") - if err == nil || err.Error() != "pull test1 is already in progress" { + if _, err := srv.poolAdd("push", "test1"); err == nil || err.Error() != "pull test1 is already in progress" { t.Fatalf("Expected `pull test1 is already in progress`") } - err = srv.poolAdd("pull", "test1") - if err == nil || err.Error() != "pull test1 is already in progress" { + if _, err := srv.poolAdd("pull", "test1"); err == nil || err.Error() != "pull test1 is already in progress" { t.Fatalf("Expected `pull test1 is already in progress`") } - err = srv.poolAdd("wait", "test3") - if err == nil || err.Error() != "Unknown pool type" { + if _, err := srv.poolAdd("wait", "test3"); err == nil || err.Error() != "Unknown pool type" { t.Fatalf("Expected `Unknown pool type`") } - - err = srv.poolRemove("pull", "test2") - if err != nil { + if err := srv.poolRemove("pull", "test2"); err != nil { t.Fatal(err) } - err = srv.poolRemove("pull", "test2") - if err != nil { + if err := srv.poolRemove("pull", "test2"); err != nil { t.Fatal(err) } - err = srv.poolRemove("pull", "test1") - if err != nil { + if err := srv.poolRemove("pull", "test1"); err != nil { t.Fatal(err) } - err = srv.poolRemove("push", "test1") - if err != nil { + if err := srv.poolRemove("push", "test1"); err != nil { t.Fatal(err) } - err = srv.poolRemove("wait", "test3") - if err == nil || err.Error() != "Unknown pool type" { + if err := srv.poolRemove("wait", "test3"); err == nil || err.Error() != "Unknown pool type" { t.Fatalf("Expected `Unknown pool type`") } } From 099a15779c5b39fcdf6586e83603a766cbeeafa1 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 20 Nov 2013 14:09:46 -0800 Subject: [PATCH 281/301] Add devmapper struct doc Upstream-commit: d233894c25e2b1b7124d69bddece94c88fb44452 Component: engine --- .../graphdriver/devmapper/devmapper_doc.go | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 components/engine/graphdriver/devmapper/devmapper_doc.go diff --git a/components/engine/graphdriver/devmapper/devmapper_doc.go b/components/engine/graphdriver/devmapper/devmapper_doc.go new file mode 100644 index 0000000000..c1c3e3891b --- /dev/null +++ b/components/engine/graphdriver/devmapper/devmapper_doc.go @@ -0,0 +1,106 @@ +package devmapper + +// Definition of struct dm_task and sub structures (from lvm2) +// +// struct dm_ioctl { +// /* +// * The version number is made up of three parts: +// * major - no backward or forward compatibility, +// * minor - only backwards compatible, +// * patch - both backwards and forwards compatible. +// * +// * All clients of the ioctl interface should fill in the +// * version number of the interface that they were +// * compiled with. +// * +// * All recognised ioctl commands (ie. those that don't +// * return -ENOTTY) fill out this field, even if the +// * command failed. +// */ +// uint32_t version[3]; /* in/out */ +// uint32_t data_size; /* total size of data passed in +// * including this struct */ + +// uint32_t data_start; /* offset to start of data +// * relative to start of this struct */ + +// uint32_t target_count; /* in/out */ +// int32_t open_count; /* out */ +// uint32_t flags; /* in/out */ + +// /* +// * event_nr holds either the event number (input and output) or the +// * udev cookie value (input only). +// * The DM_DEV_WAIT ioctl takes an event number as input. +// * The DM_SUSPEND, DM_DEV_REMOVE and DM_DEV_RENAME ioctls +// * use the field as a cookie to return in the DM_COOKIE +// * variable with the uevents they issue. +// * For output, the ioctls return the event number, not the cookie. +// */ +// uint32_t event_nr; /* in/out */ +// uint32_t padding; + +// uint64_t dev; /* in/out */ + +// char name[DM_NAME_LEN]; /* device name */ +// char uuid[DM_UUID_LEN]; /* unique identifier for +// * the block device */ +// char data[7]; /* padding or data */ +// }; + +// struct target { +// uint64_t start; +// uint64_t length; +// char *type; +// char *params; + +// struct target *next; +// }; + +// typedef enum { +// DM_ADD_NODE_ON_RESUME, /* add /dev/mapper node with dmsetup resume */ +// DM_ADD_NODE_ON_CREATE /* add /dev/mapper node with dmsetup create */ +// } dm_add_node_t; + +// struct dm_task { +// int type; +// char *dev_name; +// char *mangled_dev_name; + +// struct target *head, *tail; + +// int read_only; +// uint32_t event_nr; +// int major; +// int minor; +// int allow_default_major_fallback; +// uid_t uid; +// gid_t gid; +// mode_t mode; +// uint32_t read_ahead; +// uint32_t read_ahead_flags; +// union { +// struct dm_ioctl *v4; +// } dmi; +// char *newname; +// char *message; +// char *geometry; +// uint64_t sector; +// int no_flush; +// int no_open_count; +// int skip_lockfs; +// int query_inactive_table; +// int suppress_identical_reload; +// dm_add_node_t add_node; +// uint64_t existing_table_size; +// int cookie_set; +// int new_uuid; +// int secure_data; +// int retry_remove; +// int enable_checks; +// int expected_errno; + +// char *uuid; +// char *mangled_uuid; +// }; +// From f3c5785b7c26c75f4a2df9e9cc819a4afd62514f Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 20 Nov 2013 14:51:04 -0800 Subject: [PATCH 282/301] Fix image size calc on initial save Upstream-commit: 8fdbf46afbcad9e4eab79b2804261b084267b6e4 Component: engine --- components/engine/container.go | 3 +-- components/engine/image.go | 41 +++++++++++++++++++++++++--------- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/components/engine/container.go b/components/engine/container.go index 11021b0885..5e03c65845 100644 --- a/components/engine/container.go +++ b/components/engine/container.go @@ -1575,8 +1575,7 @@ func (container *Container) GetSize() (int64, int64) { } } - _, err = os.Stat(container.RootfsPath()) - if err == nil { + if _, err = os.Stat(container.RootfsPath()); err != nil { filepath.Walk(container.RootfsPath(), func(path string, fileInfo os.FileInfo, err error) error { if fileInfo != nil { sizeRootfs += fileInfo.Size() diff --git a/components/engine/image.go b/components/engine/image.go index 77153bb136..47c4ab2d7d 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -62,19 +62,27 @@ func LoadImage(root string) (*Image, error) { return img, nil } -func StoreImage(img *Image, jsonData []byte, layerData archive.Archive, root, rootfs string) error { +func StoreImage(img *Image, jsonData []byte, layerData archive.Archive, root, layer string) error { // Store the layer - layer := rootfs + var ( + size int64 + err error + driver = img.graph.driver + ) if err := os.MkdirAll(layer, 0755); err != nil { return err } // If layerData is not nil, unpack it into the new layer if layerData != nil { - if differ, ok := img.graph.driver.(graphdriver.Differ); ok { + if differ, ok := driver.(graphdriver.Differ); ok { if err := differ.ApplyDiff(img.ID, layerData); err != nil { return err } + + if size, err = differ.DiffSize(img.ID); err != nil { + return err + } } else { start := time.Now() utils.Debugf("Start untar layer") @@ -82,6 +90,24 @@ func StoreImage(img *Image, jsonData []byte, layerData archive.Archive, root, ro return err } utils.Debugf("Untar time: %vs", time.Now().Sub(start).Seconds()) + + if img.Parent == "" { + if size, err = utils.TreeSize(layer); err != nil { + return err + } + } else { + parent, err := driver.Get(img.Parent) + if err != nil { + return err + } + changes, err := archive.ChangesDirs(layer, parent) + if err != nil { + return err + } + if size = archive.ChangesSize(layer, changes); err != nil { + return err + } + } } } @@ -90,18 +116,13 @@ func StoreImage(img *Image, jsonData []byte, layerData archive.Archive, root, ro return ioutil.WriteFile(jsonPath(root), jsonData, 0600) } // Otherwise, unmarshal the image - jsonData, err := json.Marshal(img) - if err != nil { + if jsonData, err = json.Marshal(img); err != nil { return err } if err := ioutil.WriteFile(jsonPath(root), jsonData, 0600); err != nil { return err } - // Compute and save the size of the rootfs - size, err := utils.TreeSize(rootfs) - if err != nil { - return fmt.Errorf("Error computing size of rootfs %s: %s", img.ID, err) - } + img.Size = size if err := img.SaveSize(root); err != nil { return err From d1d0a44d0fe3f57034a427d4dd90e4df4be09967 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 20 Nov 2013 23:12:19 +0000 Subject: [PATCH 283/301] devmapper: fix typo Upstream-commit: 023ff36704ef5ad8d6fc8d0c07f718fec2ec5ff0 Component: engine --- components/engine/graphdriver/devmapper/devmapper.go | 2 +- components/engine/graphdriver/devmapper/devmapper_wrapper.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/engine/graphdriver/devmapper/devmapper.go b/components/engine/graphdriver/devmapper/devmapper.go index 103937b7ad..4ba995754a 100644 --- a/components/engine/graphdriver/devmapper/devmapper.go +++ b/components/engine/graphdriver/devmapper/devmapper.go @@ -84,7 +84,7 @@ type ( func (t *Task) destroy() { if t != nil { - DmTaskDestory(t.unmanaged) + DmTaskDestroy(t.unmanaged) runtime.SetFinalizer(t, nil) } } diff --git a/components/engine/graphdriver/devmapper/devmapper_wrapper.go b/components/engine/graphdriver/devmapper/devmapper_wrapper.go index 00b7ecb383..51bf40967a 100644 --- a/components/engine/graphdriver/devmapper/devmapper_wrapper.go +++ b/components/engine/graphdriver/devmapper/devmapper_wrapper.go @@ -148,7 +148,7 @@ type ( ) var ( - DmTaskDestory = dmTaskDestroyFct + DmTaskDestroy = dmTaskDestroyFct DmTaskCreate = dmTaskCreateFct DmTaskRun = dmTaskRunFct DmTaskSetName = dmTaskSetNameFct From f3353c818bdd1f1e26e6e6222e3999f975838e7c Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 20 Nov 2013 23:25:27 +0000 Subject: [PATCH 284/301] Devmapper: remove deprecated test helpers Upstream-commit: da514223d1e441e2bab82bf3b30e1a1e0e222ac6 Component: engine --- components/engine/graphdriver/devmapper/devmapper.go | 1 - components/engine/graphdriver/devmapper/devmapper_test.go | 8 -------- 2 files changed, 9 deletions(-) diff --git a/components/engine/graphdriver/devmapper/devmapper.go b/components/engine/graphdriver/devmapper/devmapper.go index 4ba995754a..de82bde0fd 100644 --- a/components/engine/graphdriver/devmapper/devmapper.go +++ b/components/engine/graphdriver/devmapper/devmapper.go @@ -47,7 +47,6 @@ var ( ErrTaskAddTarget = errors.New("dm_task_add_target failed") ErrTaskSetSector = errors.New("dm_task_set_sector failed") ErrTaskGetInfo = errors.New("dm_task_get_info failed") - ErrTaskGetDriverVersion = errors.New("dm_task_get_driver_version failed") ErrTaskSetCookie = errors.New("dm_task_set_cookie failed") ErrNilCookie = errors.New("cookie ptr can't be nil") ErrAttachLoopbackDevice = errors.New("loopback mounting failed") diff --git a/components/engine/graphdriver/devmapper/devmapper_test.go b/components/engine/graphdriver/devmapper/devmapper_test.go index 33167f0953..8fd234cba2 100644 --- a/components/engine/graphdriver/devmapper/devmapper_test.go +++ b/components/engine/graphdriver/devmapper/devmapper_test.go @@ -246,10 +246,6 @@ func dmTaskAddTargetFail(task *CDmTask, return -1 } -func dmTaskGetDriverVersionFail(task *CDmTask, version *string) int { - return -1 -} - func dmTaskGetInfoFail(task *CDmTask, info *Info) int { return -1 } @@ -267,10 +263,6 @@ func sysGetBlockSizeFail(fd uintptr, size *uint64) sysErrno { return 1 } -func dmGetBlockSizeFail(fd uintptr) int64 { - return -1 -} - func dmUdevWaitFail(cookie uint) int { return -1 } From 6fbdd2864224d96464c041bdcc5519eb5e6e8c1e Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 20 Nov 2013 15:37:26 -0800 Subject: [PATCH 285/301] Ensure that only the layers are compressed and not mnt points Upstream-commit: 4e0c76b321cf42d36cdbedcf84103f4fcc123ecd Component: engine --- components/engine/graph.go | 2 +- components/engine/image.go | 23 ++++++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/components/engine/graph.go b/components/engine/graph.go index f736027274..b08c9cc947 100644 --- a/components/engine/graph.go +++ b/components/engine/graph.go @@ -187,7 +187,7 @@ func (graph *Graph) TempLayerArchive(id string, compression archive.Compression, if err != nil { return nil, err } - a, err := image.TarLayer(compression) + a, err := image.TarLayer() if err != nil { return nil, err } diff --git a/components/engine/image.go b/components/engine/image.go index 47c4ab2d7d..89a78eab8b 100644 --- a/components/engine/image.go +++ b/components/engine/image.go @@ -144,15 +144,32 @@ func jsonPath(root string) string { } // TarLayer returns a tar archive of the image's filesystem layer. -func (img *Image) TarLayer(compression archive.Compression) (archive.Archive, error) { +func (img *Image) TarLayer() (archive.Archive, error) { if img.graph == nil { return nil, fmt.Errorf("Can't load storage driver for unregistered image %s", img.ID) } - layerPath, err := img.graph.driver.Get(img.ID) + driver := img.graph.driver + if differ, ok := driver.(graphdriver.Differ); ok { + return differ.Diff(img.ID) + } + + imgFs, err := driver.Get(img.ID) if err != nil { return nil, err } - return archive.Tar(layerPath, compression) + if img.Parent == "" { + return archive.Tar(imgFs, archive.Uncompressed) + } else { + parentFs, err := driver.Get(img.Parent) + if err != nil { + return nil, err + } + changes, err := archive.ChangesDirs(imgFs, parentFs) + if err != nil { + return nil, err + } + return archive.ExportChanges(imgFs, changes) + } } func ValidateID(id string) error { From 91acb2bfd6bb13c17462dfad3dd141abfe547b29 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Wed, 20 Nov 2013 23:27:52 +0000 Subject: [PATCH 286/301] Devmapper: mock all calls to libdevmapper in the unit tests, and deny them by default Upstream-commit: e2390318bb4cc98886777bdad3fbb860942b8e65 Component: engine --- .../devmapper/devmapper_wrapper.go | 32 +++++----- .../graphdriver/devmapper/driver_test.go | 62 +++++++++++++++++++ 2 files changed, 78 insertions(+), 16 deletions(-) diff --git a/components/engine/graphdriver/devmapper/devmapper_wrapper.go b/components/engine/graphdriver/devmapper/devmapper_wrapper.go index 51bf40967a..1f28412197 100644 --- a/components/engine/graphdriver/devmapper/devmapper_wrapper.go +++ b/components/engine/graphdriver/devmapper/devmapper_wrapper.go @@ -148,26 +148,26 @@ type ( ) var ( - DmTaskDestroy = dmTaskDestroyFct - DmTaskCreate = dmTaskCreateFct - DmTaskRun = dmTaskRunFct - DmTaskSetName = dmTaskSetNameFct - DmTaskSetMessage = dmTaskSetMessageFct - DmTaskSetSector = dmTaskSetSectorFct - DmTaskSetCookie = dmTaskSetCookieFct - DmTaskSetAddNode = dmTaskSetAddNodeFct - DmTaskSetRo = dmTaskSetRoFct - DmTaskAddTarget = dmTaskAddTargetFct - DmTaskGetInfo = dmTaskGetInfoFct - DmGetNextTarget = dmGetNextTargetFct - DmGetBlockSize = dmGetBlockSizeFct DmAttachLoopDevice = dmAttachLoopDeviceFct - DmUdevWait = dmUdevWaitFct + DmGetBlockSize = dmGetBlockSizeFct + DmGetLibraryVersion = dmGetLibraryVersionFct + DmGetNextTarget = dmGetNextTargetFct DmLogInitVerbose = dmLogInitVerboseFct DmSetDevDir = dmSetDevDirFct - DmGetLibraryVersion = dmGetLibraryVersionFct - LogWithErrnoInit = logWithErrnoInitFct + DmTaskAddTarget = dmTaskAddTargetFct + DmTaskCreate = dmTaskCreateFct + DmTaskDestroy = dmTaskDestroyFct + DmTaskGetInfo = dmTaskGetInfoFct + DmTaskRun = dmTaskRunFct + DmTaskSetAddNode = dmTaskSetAddNodeFct + DmTaskSetCookie = dmTaskSetCookieFct + DmTaskSetMessage = dmTaskSetMessageFct + DmTaskSetName = dmTaskSetNameFct + DmTaskSetRo = dmTaskSetRoFct + DmTaskSetSector = dmTaskSetSectorFct + DmUdevWait = dmUdevWaitFct GetBlockSize = getBlockSizeFct + LogWithErrnoInit = logWithErrnoInitFct ) func free(p *C.char) { diff --git a/components/engine/graphdriver/devmapper/driver_test.go b/components/engine/graphdriver/devmapper/driver_test.go index 02c614a270..3e080c9004 100644 --- a/components/engine/graphdriver/devmapper/driver_test.go +++ b/components/engine/graphdriver/devmapper/driver_test.go @@ -12,6 +12,68 @@ func init() { DefaultMetaDataLoopbackSize = 200 * 1024 * 1024 DefaultBaseFsSize = 300 * 1024 * 1024 + // Hijack all calls to libdevmapper with default panics. + // Authorized calls are selectively hijacked in each tests. + DmTaskCreate = func(t int) *CDmTask { + panic("DmTaskCreate: this method should not be called here") + } + DmTaskRun = func(task *CDmTask) int { + panic("DmTaskRun: this method should not be called here") + } + DmTaskSetName = func(task *CDmTask, name string) int { + panic("DmTaskSetName: this method should not be called here") + } + DmTaskSetMessage = func(task *CDmTask, message string) int { + panic("DmTaskSetMessage: this method should not be called here") + } + DmTaskSetSector = func(task *CDmTask, sector uint64) int { + panic("DmTaskSetSector: this method should not be called here") + } + DmTaskSetCookie = func(task *CDmTask, cookie *uint, flags uint16) int { + panic("DmTaskSetCookie: this method should not be called here") + } + DmTaskSetAddNode = func(task *CDmTask, addNode AddNodeType) int { + panic("DmTaskSetAddNode: this method should not be called here") + } + DmTaskSetRo = func(task *CDmTask) int { + panic("DmTaskSetRo: this method should not be called here") + } + DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int { + panic("DmTaskAddTarget: this method should not be called here") + } + DmTaskGetInfo = func(task *CDmTask, info *Info) int { + panic("DmTaskGetInfo: this method should not be called here") + } + DmGetNextTarget = func(task *CDmTask, next uintptr, start, length *uint64, target, params *string) uintptr { + panic("DmGetNextTarget: this method should not be called here") + } + DmAttachLoopDevice = func(filename string, fd *int) string { + panic("DmAttachLoopDevice: this method should not be called here") + } + DmGetBlockSize = func(fd uintptr) (int64, sysErrno) { + panic("DmGetBlockSize: this method should not be called here") + } + DmUdevWait = func(cookie uint) int { + panic("DmUdevWait: this method should not be called here") + } + DmSetDevDir = func(dir string) int { + panic("DmSetDevDir: this method should not be called here") + } + DmGetLibraryVersion = func(version *string) int { + panic("DmGetLibraryVersion: this method should not be called here") + } + DmLogInitVerbose = func(level int) { + panic("DmLogInitVerbose: this method should not be called here") + } + DmTaskDestroy = func(task *CDmTask) { + panic("DmTaskDestroy: this method should not be called here") + } + GetBlockSize = func(fd uintptr, size *uint64) sysErrno { + panic("GetBlockSize: this method should not be called here") + } + LogWithErrnoInit = func() { + panic("LogWithErrnoInit: this method should not be called here") + } } func mkTestDirectory(t *testing.T) string { From 2cabd3e83355919110cf909689017a4f8af72ce7 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 20 Nov 2013 13:53:54 -0800 Subject: [PATCH 287/301] Add more aufs tests and implement Status Upstream-commit: 5306053e21cb2a22884d2d281c2cc855cf22fc69 Component: engine --- components/engine/graphdriver/aufs/aufs.go | 8 +- .../engine/graphdriver/aufs/aufs_test.go | 110 +++++++++++++++++- 2 files changed, 114 insertions(+), 4 deletions(-) diff --git a/components/engine/graphdriver/aufs/aufs.go b/components/engine/graphdriver/aufs/aufs.go index 7f9cdc2b2e..bd3ad6ebf6 100644 --- a/components/engine/graphdriver/aufs/aufs.go +++ b/components/engine/graphdriver/aufs/aufs.go @@ -103,8 +103,12 @@ func (Driver) String() string { return "aufs" } -func (Driver) Status() [][2]string { - return nil +func (a Driver) Status() [][2]string { + ids, _ := loadIds(path.Join(a.rootPath(), "layers")) + return [][2]string{ + {"Root Dir", a.rootPath()}, + {"Dirs", fmt.Sprintf("%d", len(ids))}, + } } // Exists returns true if the given id is registered with diff --git a/components/engine/graphdriver/aufs/aufs_test.go b/components/engine/graphdriver/aufs/aufs_test.go index 990cf5a867..2970311389 100644 --- a/components/engine/graphdriver/aufs/aufs_test.go +++ b/components/engine/graphdriver/aufs/aufs_test.go @@ -446,7 +446,9 @@ func TestDiffSize(t *testing.T) { if err != nil { t.Fatal(err) } - f.Truncate(size) + if err := f.Truncate(size); err != nil { + t.Fatal(err) + } s, err := f.Stat() if err != nil { t.Fatal(err) @@ -465,6 +467,108 @@ func TestDiffSize(t *testing.T) { } } +func TestChildDiffSize(t *testing.T) { + d := newDriver(t) + defer os.RemoveAll(tmp) + defer d.Cleanup() + + if err := d.Create("1", ""); err != nil { + t.Fatal(err) + } + + diffPath, err := d.Get("1") + if err != nil { + t.Fatal(err) + } + + // Add a file to the diff path with a fixed size + size := int64(1024) + + f, err := os.Create(path.Join(diffPath, "test_file")) + if err != nil { + t.Fatal(err) + } + if err := f.Truncate(size); err != nil { + t.Fatal(err) + } + s, err := f.Stat() + if err != nil { + t.Fatal(err) + } + size = s.Size() + if err := f.Close(); err != nil { + t.Fatal(err) + } + + diffSize, err := d.DiffSize("1") + if err != nil { + t.Fatal(err) + } + if diffSize != size { + t.Fatalf("Expected size to be %d got %d", size, diffSize) + } + + if err := d.Create("2", "1"); err != nil { + t.Fatal(err) + } + + diffSize, err = d.DiffSize("2") + if err != nil { + t.Fatal(err) + } + // The diff size for the child should be zero + if diffSize != 0 { + t.Fatalf("Expected size to be %d got %d", 0, diffSize) + } +} + +func TestExists(t *testing.T) { + d := newDriver(t) + defer os.RemoveAll(tmp) + defer d.Cleanup() + + if err := d.Create("1", ""); err != nil { + t.Fatal(err) + } + + if d.Exists("none") { + t.Fatal("id name should not exist in the driver") + } + + if !d.Exists("1") { + t.Fatal("id 1 should exist in the driver") + } +} + +func TestStatus(t *testing.T) { + d := newDriver(t) + defer os.RemoveAll(tmp) + defer d.Cleanup() + + if err := d.Create("1", ""); err != nil { + t.Fatal(err) + } + + status := d.Status() + if status == nil || len(status) == 0 { + t.Fatal("Status should not be nil or empty") + } + rootDir := status[0] + dirs := status[1] + if rootDir[0] != "Root Dir" { + t.Fatalf("Expected Root Dir got %s", rootDir[0]) + } + if rootDir[1] != d.rootPath() { + t.Fatalf("Expected %s got %s", d.rootPath(), rootDir[1]) + } + if dirs[0] != "Dirs" { + t.Fatalf("Expected Dirs got %s", dirs[0]) + } + if dirs[1] != "1" { + t.Fatalf("Expected 1 got %s", dirs[1]) + } +} + func TestApplyDiff(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) @@ -486,7 +590,9 @@ func TestApplyDiff(t *testing.T) { if err != nil { t.Fatal(err) } - f.Truncate(size) + if err := f.Truncate(size); err != nil { + t.Fatal(err) + } f.Close() diff, err := d.Diff("1") From 840cf279cf1c5f4ad62e9fcc87ff3dc1c4c17e50 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 21 Nov 2013 02:12:51 +0000 Subject: [PATCH 288/301] devmapper: skip test which are not unit tests Upstream-commit: 2b7c63b1b5d62515cd876f189dea6e5a5257cd49 Component: engine --- .../engine/graphdriver/devmapper/devmapper_test.go | 9 +++++++++ components/engine/graphdriver/devmapper/driver_test.go | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/components/engine/graphdriver/devmapper/devmapper_test.go b/components/engine/graphdriver/devmapper/devmapper_test.go index 8fd234cba2..ce22864361 100644 --- a/components/engine/graphdriver/devmapper/devmapper_test.go +++ b/components/engine/graphdriver/devmapper/devmapper_test.go @@ -5,6 +5,7 @@ import ( ) func TestTaskCreate(t *testing.T) { + t.Skip("FIXME: not a unit test") // Test success taskCreate(t, DeviceInfo) @@ -17,6 +18,7 @@ func TestTaskCreate(t *testing.T) { } func TestTaskRun(t *testing.T) { + t.Skip("FIXME: not a unit test") task := taskCreate(t, DeviceInfo) // Test success @@ -45,6 +47,7 @@ func TestTaskRun(t *testing.T) { } func TestTaskSetName(t *testing.T) { + t.Skip("FIXME: not a unit test") task := taskCreate(t, DeviceInfo) // Test success @@ -62,6 +65,7 @@ func TestTaskSetName(t *testing.T) { } func TestTaskSetMessage(t *testing.T) { + t.Skip("FIXME: not a unit test") task := taskCreate(t, DeviceInfo) // Test success @@ -79,6 +83,7 @@ func TestTaskSetMessage(t *testing.T) { } func TestTaskSetSector(t *testing.T) { + t.Skip("FIXME: not a unit test") task := taskCreate(t, DeviceInfo) // Test success @@ -96,6 +101,7 @@ func TestTaskSetSector(t *testing.T) { } func TestTaskSetCookie(t *testing.T) { + t.Skip("FIXME: not a unit test") var ( cookie uint = 0 task = taskCreate(t, DeviceInfo) @@ -120,6 +126,7 @@ func TestTaskSetCookie(t *testing.T) { } func TestTaskSetAddNode(t *testing.T) { + t.Skip("FIXME: not a unit test") task := taskCreate(t, DeviceInfo) // Test success @@ -141,6 +148,7 @@ func TestTaskSetAddNode(t *testing.T) { } func TestTaskSetRo(t *testing.T) { + t.Skip("FIXME: not a unit test") task := taskCreate(t, DeviceInfo) // Test success @@ -158,6 +166,7 @@ func TestTaskSetRo(t *testing.T) { } func TestTaskAddTarget(t *testing.T) { + t.Skip("FIXME: not a unit test") task := taskCreate(t, DeviceInfo) // Test success diff --git a/components/engine/graphdriver/devmapper/driver_test.go b/components/engine/graphdriver/devmapper/driver_test.go index 3e080c9004..e61bfa8981 100644 --- a/components/engine/graphdriver/devmapper/driver_test.go +++ b/components/engine/graphdriver/devmapper/driver_test.go @@ -127,6 +127,7 @@ func TestInit(t *testing.T) { } func TestDriverName(t *testing.T) { + t.Skip("FIXME: not a unit test") d := newDriver(t) defer cleanup(d) @@ -136,6 +137,7 @@ func TestDriverName(t *testing.T) { } func TestDriverCreate(t *testing.T) { + t.Skip("FIXME: not a unit test") d := newDriver(t) defer cleanup(d) @@ -145,6 +147,7 @@ func TestDriverCreate(t *testing.T) { } func TestDriverRemove(t *testing.T) { + t.Skip("FIXME: not a unit test") d := newDriver(t) defer cleanup(d) @@ -158,6 +161,7 @@ func TestDriverRemove(t *testing.T) { } func TestCleanup(t *testing.T) { + t.Skip("FIXME: not a unit test") t.Skip("Unimplemented") d := newDriver(t) defer osRemoveAll(d.home) @@ -222,6 +226,7 @@ func TestCleanup(t *testing.T) { } func TestNotMounted(t *testing.T) { + t.Skip("FIXME: not a unit test") t.Skip("Not implemented") d := newDriver(t) defer cleanup(d) @@ -240,6 +245,7 @@ func TestNotMounted(t *testing.T) { } func TestMounted(t *testing.T) { + t.Skip("FIXME: not a unit test") d := newDriver(t) defer cleanup(d) @@ -260,6 +266,7 @@ func TestMounted(t *testing.T) { } func TestInitCleanedDriver(t *testing.T) { + t.Skip("FIXME: not a unit test") d := newDriver(t) if err := d.Create("1", ""); err != nil { @@ -286,6 +293,7 @@ func TestInitCleanedDriver(t *testing.T) { } func TestMountMountedDriver(t *testing.T) { + t.Skip("FIXME: not a unit test") d := newDriver(t) defer cleanup(d) @@ -304,6 +312,7 @@ func TestMountMountedDriver(t *testing.T) { } func TestGetReturnsValidDevice(t *testing.T) { + t.Skip("FIXME: not a unit test") d := newDriver(t) defer cleanup(d) @@ -329,6 +338,7 @@ func TestGetReturnsValidDevice(t *testing.T) { } func TestDriverGetSize(t *testing.T) { + t.Skip("FIXME: not a unit test") t.Skipf("Size is currently not implemented") d := newDriver(t) From fe06edbbd147731d3fc7200a81e8de2cbd5220dc Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 21 Nov 2013 02:16:26 +0000 Subject: [PATCH 289/301] Devmapper: wrap calls to os/exec for easier mocking Upstream-commit: 60f728b170f550262b22b7905dfb280a405df4a7 Component: engine --- components/engine/graphdriver/devmapper/deviceset.go | 5 ++--- components/engine/graphdriver/devmapper/sys.go | 5 +++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/components/engine/graphdriver/devmapper/deviceset.go b/components/engine/graphdriver/devmapper/deviceset.go index 02d6e6bc3a..d232b510d2 100644 --- a/components/engine/graphdriver/devmapper/deviceset.go +++ b/components/engine/graphdriver/devmapper/deviceset.go @@ -6,7 +6,6 @@ import ( "github.com/dotcloud/docker/utils" "io" "io/ioutil" - "os/exec" "path" "path/filepath" "strconv" @@ -223,9 +222,9 @@ func (devices *DeviceSet) activateDeviceIfNeeded(hash string) error { func (devices *DeviceSet) createFilesystem(info *DevInfo) error { devname := info.DevName() - err := exec.Command("mkfs.ext4", "-E", "discard,lazy_itable_init=0,lazy_journal_init=0", devname).Run() + err := execRun("mkfs.ext4", "-E", "discard,lazy_itable_init=0,lazy_journal_init=0", devname) if err != nil { - err = exec.Command("mkfs.ext4", "-E", "discard,lazy_itable_init=0", devname).Run() + err = execRun("mkfs.ext4", "-E", "discard,lazy_itable_init=0", devname) } if err != nil { utils.Debugf("\n--->Err: %s\n", err) diff --git a/components/engine/graphdriver/devmapper/sys.go b/components/engine/graphdriver/devmapper/sys.go index 8250e3ed7d..60bafb5f6d 100644 --- a/components/engine/graphdriver/devmapper/sys.go +++ b/components/engine/graphdriver/devmapper/sys.go @@ -2,6 +2,7 @@ package devmapper import ( "os" + "os/exec" "syscall" ) @@ -28,6 +29,10 @@ var ( osRemoveAll = os.RemoveAll osRename = os.Rename osReadlink = os.Readlink + + execRun = func(name string, args ...string) error { + return exec.Command(name, args...).Run() + } ) const ( From 50d7661c9b1ba13b25e589e957f0cc4b5517b884 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 21 Nov 2013 02:17:03 +0000 Subject: [PATCH 290/301] Devmapper: test driver initialization and its interaction with libdevmapper Upstream-commit: df258f5861794057c303a33c000bf830bc543631 Component: engine --- .../graphdriver/devmapper/driver_test.go | 244 ++++++++++++++++-- 1 file changed, 225 insertions(+), 19 deletions(-) diff --git a/components/engine/graphdriver/devmapper/driver_test.go b/components/engine/graphdriver/devmapper/driver_test.go index e61bfa8981..8b74d578c5 100644 --- a/components/engine/graphdriver/devmapper/driver_test.go +++ b/components/engine/graphdriver/devmapper/driver_test.go @@ -1,8 +1,11 @@ package devmapper import ( + "fmt" "io/ioutil" "path" + "runtime" + "strings" "testing" ) @@ -11,7 +14,10 @@ func init() { DefaultDataLoopbackSize = 300 * 1024 * 1024 DefaultMetaDataLoopbackSize = 200 * 1024 * 1024 DefaultBaseFsSize = 300 * 1024 * 1024 +} +// denyAllDevmapper mocks all calls to libdevmapper in the unit tests, and denies them by default +func denyAllDevmapper() { // Hijack all calls to libdevmapper with default panics. // Authorized calls are selectively hijacked in each tests. DmTaskCreate = func(t int) *CDmTask { @@ -98,32 +104,220 @@ func cleanup(d *Driver) { osRemoveAll(d.home) } +type Set map[string]bool + +func (r Set) Assert(t *testing.T, names ...string) { + for _, key := range names { + if _, exists := r[key]; !exists { + t.Fatalf("Key not set: %s", key) + } + delete(r, key) + } + if len(r) != 0 { + t.Fatalf("Unexpected keys: %v", r) + } +} + func TestInit(t *testing.T) { home := mkTestDirectory(t) defer osRemoveAll(home) - driver, err := Init(home) - if err != nil { - t.Fatal(err) - } - defer func() { - if err := driver.Cleanup(); err != nil { + calls := make(Set) + devicesAttached := make(Set) + taskMessages := make(Set) + taskTypes := make(Set) + func() { + denyAllDevmapper() + DmSetDevDir = func(dir string) int { + calls["DmSetDevDir"] = true + expectedDir := "/dev" + if dir != expectedDir { + t.Fatalf("Wrong libdevmapper call\nExpected: DmSetDevDir(%v)\nReceived: DmSetDevDir(%v)\n", expectedDir, dir) + } + return 0 + } + LogWithErrnoInit = func() { + calls["DmLogWithErrnoInit"] = true + } + var task1 CDmTask + DmTaskCreate = func(taskType int) *CDmTask { + calls["DmTaskCreate"] = true + taskTypes[fmt.Sprintf("%d", taskType)] = true + return &task1 + } + DmTaskSetName = func(task *CDmTask, name string) int { + calls["DmTaskSetName"] = true + expectedTask := &task1 + if task != expectedTask { + t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetName(%v)\nReceived: DmTaskSetName(%v)\n", expectedTask, task) + } + // FIXME: use Set.AssertRegexp() + if !strings.HasPrefix(name, "docker-") && !strings.HasPrefix(name, "/dev/mapper/docker-") || + !strings.HasSuffix(name, "-pool") && !strings.HasSuffix(name, "-base") { + t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetName(%v)\nReceived: DmTaskSetName(%v)\n", "docker-...-pool", name) + } + return 1 + } + DmTaskRun = func(task *CDmTask) int { + calls["DmTaskRun"] = true + expectedTask := &task1 + if task != expectedTask { + t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskRun(%v)\nReceived: DmTaskRun(%v)\n", expectedTask, task) + } + return 1 + } + DmTaskGetInfo = func(task *CDmTask, info *Info) int { + calls["DmTaskGetInfo"] = true + expectedTask := &task1 + if task != expectedTask { + t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskGetInfo(%v)\nReceived: DmTaskGetInfo(%v)\n", expectedTask, task) + } + // This will crash if info is not dereferenceable + info.Exists = 0 + return 1 + } + DmTaskSetSector = func(task *CDmTask, sector uint64) int { + calls["DmTaskSetSector"] = true + expectedTask := &task1 + if task != expectedTask { + t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetSector(%v)\nReceived: DmTaskSetSector(%v)\n", expectedTask, task) + } + if expectedSector := uint64(0); sector != expectedSector { + t.Fatalf("Wrong libdevmapper call to DmTaskSetSector\nExpected: %v\nReceived: %v\n", expectedSector, sector) + } + return 1 + } + DmTaskSetMessage = func(task *CDmTask, message string) int { + calls["DmTaskSetMessage"] = true + expectedTask := &task1 + if task != expectedTask { + t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetSector(%v)\nReceived: DmTaskSetSector(%v)\n", expectedTask, task) + } + taskMessages[message] = true + return 1 + } + var ( + fakeDataLoop = "/dev/loop42" + fakeMetadataLoop = "/dev/loop43" + fakeDataLoopFd = 42 + fakeMetadataLoopFd = 43 + ) + var attachCount int + DmAttachLoopDevice = func(filename string, fd *int) string { + calls["DmAttachLoopDevice"] = true + if _, exists := devicesAttached[filename]; exists { + t.Fatalf("Already attached %s", filename) + } + devicesAttached[filename] = true + // This will crash if fd is not dereferenceable + if attachCount == 0 { + attachCount++ + *fd = fakeDataLoopFd + return fakeDataLoop + } else { + *fd = fakeMetadataLoopFd + return fakeMetadataLoop + } + } + DmTaskDestroy = func(task *CDmTask) { + calls["DmTaskDestroy"] = true + expectedTask := &task1 + if task != expectedTask { + t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskDestroy(%v)\nReceived: DmTaskDestroy(%v)\n", expectedTask, task) + } + } + fakeBlockSize := int64(4242 * 512) + DmGetBlockSize = func(fd uintptr) (int64, sysErrno) { + calls["DmGetBlockSize"] = true + if expectedFd := uintptr(42); fd != expectedFd { + t.Fatalf("Wrong libdevmapper call\nExpected: DmGetBlockSize(%v)\nReceived: DmGetBlockSize(%v)\n", expectedFd, fd) + } + return fakeBlockSize, 0 + } + DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int { + calls["DmTaskSetTarget"] = true + expectedTask := &task1 + if task != expectedTask { + t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskDestroy(%v)\nReceived: DmTaskDestroy(%v)\n", expectedTask, task) + } + if start != 0 { + t.Fatalf("Wrong start: %d != %d", start, 0) + } + if ttype != "thin" && ttype != "thin-pool" { + t.Fatalf("Wrong ttype: %s", ttype) + } + // Quick smoke test + if params == "" { + t.Fatalf("Params should not be empty") + } + return 1 + } + fakeCookie := uint(4321) + DmTaskSetCookie = func(task *CDmTask, cookie *uint, flags uint16) int { + calls["DmTaskSetCookie"] = true + expectedTask := &task1 + if task != expectedTask { + t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskDestroy(%v)\nReceived: DmTaskDestroy(%v)\n", expectedTask, task) + } + if flags != 0 { + t.Fatalf("Cookie flags should be 0 (not %x)", flags) + } + *cookie = fakeCookie + return 1 + } + DmUdevWait = func(cookie uint) int { + calls["DmUdevWait"] = true + if cookie != fakeCookie { + t.Fatalf("Wrong cookie: %d != %d", cookie, fakeCookie) + } + return 1 + } + DmTaskSetAddNode = func(task *CDmTask, addNode AddNodeType) int { + if addNode != AddNodeOnCreate { + t.Fatalf("Wrong AddNoteType: %v (expected %v)", addNode, AddNodeOnCreate) + } + calls["DmTaskSetAddNode"] = true + return 1 + } + execRun = func(name string, args ...string) error { + calls["execRun"] = true + if name != "mkfs.ext4" { + t.Fatalf("Expected %s to be executed, not %s", "mkfs.ext4", name) + } + return nil + } + driver, err := Init(home) + if err != nil { t.Fatal(err) } + defer func() { + if err := driver.Cleanup(); err != nil { + t.Fatal(err) + } + }() }() - id := "foo" - if err := driver.Create(id, ""); err != nil { - t.Fatal(err) - } - dir, err := driver.Get(id) - if err != nil { - t.Fatal(err) - } - if st, err := osStat(dir); err != nil { - t.Fatal(err) - } else if !st.IsDir() { - t.Fatalf("Get(%V) did not return a directory", id) - } + runtime.GC() + calls.Assert(t, + "DmSetDevDir", + "DmLogWithErrnoInit", + "DmTaskSetName", + "DmTaskRun", + "DmTaskGetInfo", + "DmAttachLoopDevice", + "DmTaskDestroy", + "execRun", + "DmTaskCreate", + "DmGetBlockSize", + "DmTaskSetTarget", + "DmTaskSetCookie", + "DmUdevWait", + "DmTaskSetSector", + "DmTaskSetMessage", + "DmTaskSetAddNode", + ) + devicesAttached.Assert(t, path.Join(home, "devicemapper", "data"), path.Join(home, "devicemapper", "metadata")) + taskTypes.Assert(t, "0", "6", "17") + taskMessages.Assert(t, "create_thin 0", "set_transaction_id 0 1") } func TestDriverName(t *testing.T) { @@ -372,3 +566,15 @@ func TestDriverGetSize(t *testing.T) { // t.Fatalf("Expected size %d got %d", size, diffSize) // } } + +func assertMap(t *testing.T, m map[string]bool, keys ...string) { + for _, key := range keys { + if _, exists := m[key]; !exists { + t.Fatalf("Key not set: %s", key) + } + delete(m, key) + } + if len(m) != 0 { + t.Fatalf("Unexpected keys: %v", m) + } +} From a3c4139aadd445dcffc705453aaabe3a961f4ada Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 21 Nov 2013 10:26:21 -0800 Subject: [PATCH 291/301] Update ImageExport after merge fail Upstream-commit: 253214f07da1a58931e6a256c351105eb2eee8bc Component: engine --- components/engine/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/server.go b/components/engine/server.go index 61b8fd1f4f..c04e34aeb3 100644 --- a/components/engine/server.go +++ b/components/engine/server.go @@ -282,7 +282,7 @@ func (srv *Server) exportImage(image *Image, tempdir string) error { } // serialize filesystem - fs, err := archive.Tar(path.Join(srv.runtime.graph.Root, i.ID, "layer"), archive.Uncompressed) + fs, err := i.TarLayer() if err != nil { return err } From cdba89511556cc1c1d00ea493029de0a5b38b07f Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 21 Nov 2013 00:12:07 -0700 Subject: [PATCH 292/301] Update a few flag help strings for consistency and clarity Upstream-commit: 1ab6b8bf4970b050575b008dd658471e185f2a2c Component: engine --- components/engine/docker/docker.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/components/engine/docker/docker.go b/components/engine/docker/docker.go index e0fe6d1aa5..112fbae705 100644 --- a/components/engine/docker/docker.go +++ b/components/engine/docker/docker.go @@ -25,20 +25,20 @@ func main() { } // FIXME: Switch d and D ? (to be more sshd like) flVersion := flag.Bool("v", false, "Print version information and quit") - flDaemon := flag.Bool("d", false, "Daemon mode") - flDebug := flag.Bool("D", false, "Debug mode") + flDaemon := flag.Bool("d", false, "Enable daemon mode") + flDebug := flag.Bool("D", false, "Enable debug mode") flAutoRestart := flag.Bool("r", true, "Restart previously running containers") - bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge. Use 'none' to disable container networking") - pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID") - flRoot := flag.String("g", "/var/lib/docker", "Path to use as the root of the docker runtime.") - flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.") - flDns := flag.String("dns", "", "Set custom dns servers") + bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge; use 'none' to disable container networking") + pidfile := flag.String("p", "/var/run/docker.pid", "Path to use for daemon PID file") + flRoot := flag.String("g", "/var/lib/docker", "Path to use as the root of the docker runtime") + flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS headers in the remote API") + flDns := flag.String("dns", "", "Force docker to use specific DNS servers") flHosts := utils.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)} - flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use") - flEnableIptables := flag.Bool("iptables", true, "Disable iptables within docker") - flDefaultIp := flag.String("ip", "0.0.0.0", "Default ip address to use when binding a containers ports") + flag.Var(&flHosts, "H", "Multiple tcp://host:port or unix://path/to/socket to bind in daemon mode, single connection otherwise") + flEnableIptables := flag.Bool("iptables", true, "Disable docker's addition of iptables rules") + flDefaultIp := flag.String("ip", "0.0.0.0", "Default IP address to use when binding container ports") flInterContainerComm := flag.Bool("icc", true, "Enable inter-container communication") - flGraphDriver := flag.String("graph-driver", "", "For docker to use a specific graph driver") + flGraphDriver := flag.String("graph-driver", "", "Force docker runtime to use a specific graph driver") flag.Parse() From 26fbc2e02779ed8fc6adb48897fd364af8529a04 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 21 Nov 2013 16:32:16 -0800 Subject: [PATCH 293/301] Forbid syscalls in tests, add 2 new unit tests Upstream-commit: bc82940a575944e4686db203356a8a3fb3a75217 Component: engine --- .../engine/graphdriver/devmapper/driver.go | 4 +- .../graphdriver/devmapper/driver_test.go | 336 ++++++++++++++++-- .../engine/graphdriver/devmapper/mount.go | 2 +- 3 files changed, 317 insertions(+), 25 deletions(-) diff --git a/components/engine/graphdriver/devmapper/driver.go b/components/engine/graphdriver/devmapper/driver.go index 70e62a0a80..b08d5768ef 100644 --- a/components/engine/graphdriver/devmapper/driver.go +++ b/components/engine/graphdriver/devmapper/driver.go @@ -21,7 +21,7 @@ type Driver struct { home string } -func Init(home string) (graphdriver.Driver, error) { +var Init = func(home string) (graphdriver.Driver, error) { deviceSet, err := NewDeviceSet(home, true) if err != nil { return nil, err @@ -56,7 +56,7 @@ func (d *Driver) Cleanup() error { return d.DeviceSet.Shutdown() } -func (d *Driver) Create(id string, parent string) error { +func (d *Driver) Create(id, parent string) error { if err := d.DeviceSet.AddDevice(id, parent); err != nil { return err } diff --git a/components/engine/graphdriver/devmapper/driver_test.go b/components/engine/graphdriver/devmapper/driver_test.go index 8b74d578c5..3204575dd9 100644 --- a/components/engine/graphdriver/devmapper/driver_test.go +++ b/components/engine/graphdriver/devmapper/driver_test.go @@ -2,10 +2,12 @@ package devmapper import ( "fmt" + "github.com/dotcloud/docker/graphdriver" "io/ioutil" "path" "runtime" "strings" + "syscall" "testing" ) @@ -82,6 +84,39 @@ func denyAllDevmapper() { } } +func denyAllSyscall() { + sysMount = func(source, target, fstype string, flags uintptr, data string) (err error) { + panic("sysMount: this method should not be called here") + } + sysUnmount = func(target string, flags int) (err error) { + panic("sysUnmount: this method should not be called here") + } + sysCloseOnExec = func(fd int) { + panic("sysCloseOnExec: this method should not be called here") + } + sysSyscall = func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) { + panic("sysSyscall: this method should not be called here") + } + // Not a syscall, but forbidding it here anyway + Mounted = func(mnt string) (bool, error) { + panic("devmapper.Mounted: this method should not be called here") + } + // osOpenFile = os.OpenFile + // osNewFile = os.NewFile + // osCreate = os.Create + // osStat = os.Stat + // osIsNotExist = os.IsNotExist + // osIsExist = os.IsExist + // osMkdirAll = os.MkdirAll + // osRemoveAll = os.RemoveAll + // osRename = os.Rename + // osReadlink = os.Readlink + + // execRun = func(name string, args ...string) error { + // return exec.Command(name, args...).Run() + // } +} + func mkTestDirectory(t *testing.T) string { dir, err := ioutil.TempDir("", "docker-test-devmapper-") if err != nil { @@ -119,12 +154,15 @@ func (r Set) Assert(t *testing.T, names ...string) { } func TestInit(t *testing.T) { - home := mkTestDirectory(t) + var ( + calls = make(Set) + devicesAttached = make(Set) + taskMessages = make(Set) + taskTypes = make(Set) + home = mkTestDirectory(t) + ) defer osRemoveAll(home) - calls := make(Set) - devicesAttached := make(Set) - taskMessages := make(Set) - taskTypes := make(Set) + func() { denyAllDevmapper() DmSetDevDir = func(dir string) int { @@ -295,8 +333,12 @@ func TestInit(t *testing.T) { } }() }() + // Put all tests in a funciton to make sure the garbage collection will + // occur. + // Call GC to cleanup runtime.Finalizers runtime.GC() + calls.Assert(t, "DmSetDevDir", "DmLogWithErrnoInit", @@ -320,38 +362,288 @@ func TestInit(t *testing.T) { taskMessages.Assert(t, "create_thin 0", "set_transaction_id 0 1") } -func TestDriverName(t *testing.T) { - t.Skip("FIXME: not a unit test") - d := newDriver(t) - defer cleanup(d) +func fakeInit() func(home string) (graphdriver.Driver, error) { + oldInit := Init + Init = func(home string) (graphdriver.Driver, error) { + return &Driver{ + home: home, + }, nil + } + return oldInit +} +func restoreInit(init func(home string) (graphdriver.Driver, error)) { + Init = init +} + +func mockAllDevmapper(calls Set) { + DmSetDevDir = func(dir string) int { + calls["DmSetDevDir"] = true + return 0 + } + LogWithErrnoInit = func() { + calls["DmLogWithErrnoInit"] = true + } + DmTaskCreate = func(taskType int) *CDmTask { + calls["DmTaskCreate"] = true + return &CDmTask{} + } + DmTaskSetName = func(task *CDmTask, name string) int { + calls["DmTaskSetName"] = true + return 1 + } + DmTaskRun = func(task *CDmTask) int { + calls["DmTaskRun"] = true + return 1 + } + DmTaskGetInfo = func(task *CDmTask, info *Info) int { + calls["DmTaskGetInfo"] = true + return 1 + } + DmTaskSetSector = func(task *CDmTask, sector uint64) int { + calls["DmTaskSetSector"] = true + return 1 + } + DmTaskSetMessage = func(task *CDmTask, message string) int { + calls["DmTaskSetMessage"] = true + return 1 + } + DmAttachLoopDevice = func(filename string, fd *int) string { + calls["DmAttachLoopDevice"] = true + return "/dev/loop42" + } + DmTaskDestroy = func(task *CDmTask) { + calls["DmTaskDestroy"] = true + } + DmGetBlockSize = func(fd uintptr) (int64, sysErrno) { + calls["DmGetBlockSize"] = true + return int64(4242 * 512), 0 + } + DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int { + calls["DmTaskSetTarget"] = true + return 1 + } + DmTaskSetCookie = func(task *CDmTask, cookie *uint, flags uint16) int { + calls["DmTaskSetCookie"] = true + return 1 + } + DmUdevWait = func(cookie uint) int { + calls["DmUdevWait"] = true + return 1 + } + DmTaskSetAddNode = func(task *CDmTask, addNode AddNodeType) int { + calls["DmTaskSetAddNode"] = true + return 1 + } + execRun = func(name string, args ...string) error { + calls["execRun"] = true + return nil + } +} + +func TestDriverName(t *testing.T) { + denyAllDevmapper() + defer denyAllDevmapper() + + oldInit := fakeInit() + defer restoreInit(oldInit) + + d := newDriver(t) if d.String() != "devicemapper" { t.Fatalf("Expected driver name to be devicemapper got %s", d.String()) } } func TestDriverCreate(t *testing.T) { - t.Skip("FIXME: not a unit test") - d := newDriver(t) - defer cleanup(d) + denyAllDevmapper() + denyAllSyscall() + defer denyAllSyscall() + defer denyAllDevmapper() - if err := d.Create("1", ""); err != nil { - t.Fatal(err) + calls := make(Set) + mockAllDevmapper(calls) + + sysMount = func(source, target, fstype string, flags uintptr, data string) (err error) { + calls["sysMount"] = true + // FIXME: compare the exact source and target strings (inodes + devname) + if expectedSource := "/dev/mapper/docker-"; !strings.HasPrefix(source, expectedSource) { + t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedSource, source) + } + if expectedTarget := "/tmp/docker-test-devmapper-"; !strings.HasPrefix(target, expectedTarget) { + t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedTarget, target) + } + if expectedFstype := "ext4"; fstype != expectedFstype { + t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFstype, fstype) + } + if expectedFlags := uintptr(3236757504); flags != expectedFlags { + t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFlags, flags) + } + return nil } + + Mounted = func(mnt string) (bool, error) { + calls["Mounted"] = true + if !strings.HasPrefix(mnt, "/tmp/docker-test-devmapper-") || !strings.HasSuffix(mnt, "/mnt/1") { + t.Fatalf("Wrong mounted call\nExpected: Mounted(%v)\nReceived: Mounted(%v)\n", "/tmp/docker-test-devmapper-.../mnt/1", mnt) + } + return false, nil + } + + func() { + d := newDriver(t) + + calls.Assert(t, + "DmSetDevDir", + "DmLogWithErrnoInit", + "DmTaskSetName", + "DmTaskRun", + "DmTaskGetInfo", + "DmAttachLoopDevice", + "execRun", + "DmTaskCreate", + "DmGetBlockSize", + "DmTaskSetTarget", + "DmTaskSetCookie", + "DmUdevWait", + "DmTaskSetSector", + "DmTaskSetMessage", + "DmTaskSetAddNode", + ) + + if err := d.Create("1", ""); err != nil { + t.Fatal(err) + } + calls.Assert(t, + "DmTaskCreate", + "DmTaskGetInfo", + "sysMount", + "Mounted", + "DmTaskRun", + "DmTaskSetTarget", + "DmTaskSetSector", + "DmTaskSetCookie", + "DmUdevWait", + "DmTaskSetName", + "DmTaskSetMessage", + "DmTaskSetAddNode", + ) + + }() + + runtime.GC() + + calls.Assert(t, + "DmTaskDestroy", + ) } func TestDriverRemove(t *testing.T) { - t.Skip("FIXME: not a unit test") - d := newDriver(t) - defer cleanup(d) + denyAllDevmapper() + denyAllSyscall() + defer denyAllSyscall() + defer denyAllDevmapper() - if err := d.Create("1", ""); err != nil { - t.Fatal(err) + calls := make(Set) + mockAllDevmapper(calls) + + sysMount = func(source, target, fstype string, flags uintptr, data string) (err error) { + calls["sysMount"] = true + // FIXME: compare the exact source and target strings (inodes + devname) + if expectedSource := "/dev/mapper/docker-"; !strings.HasPrefix(source, expectedSource) { + t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedSource, source) + } + if expectedTarget := "/tmp/docker-test-devmapper-"; !strings.HasPrefix(target, expectedTarget) { + t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedTarget, target) + } + if expectedFstype := "ext4"; fstype != expectedFstype { + t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFstype, fstype) + } + if expectedFlags := uintptr(3236757504); flags != expectedFlags { + t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFlags, flags) + } + return nil + } + sysUnmount = func(target string, flags int) (err error) { + calls["sysUnmount"] = true + // FIXME: compare the exact source and target strings (inodes + devname) + if expectedTarget := "/tmp/docker-test-devmapper-"; !strings.HasPrefix(target, expectedTarget) { + t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedTarget, target) + } + if expectedFlags := 0; flags != expectedFlags { + t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFlags, flags) + } + return nil + } + Mounted = func(mnt string) (bool, error) { + calls["Mounted"] = true + return false, nil } - if err := d.Remove("1"); err != nil { - t.Fatal(err) - } + func() { + d := newDriver(t) + + calls.Assert(t, + "DmSetDevDir", + "DmLogWithErrnoInit", + "DmTaskSetName", + "DmTaskRun", + "DmTaskGetInfo", + "DmAttachLoopDevice", + "execRun", + "DmTaskCreate", + "DmGetBlockSize", + "DmTaskSetTarget", + "DmTaskSetCookie", + "DmUdevWait", + "DmTaskSetSector", + "DmTaskSetMessage", + "DmTaskSetAddNode", + ) + + if err := d.Create("1", ""); err != nil { + t.Fatal(err) + } + + calls.Assert(t, + "DmTaskCreate", + "DmTaskGetInfo", + "sysMount", + "Mounted", + "DmTaskRun", + "DmTaskSetTarget", + "DmTaskSetSector", + "DmTaskSetCookie", + "DmUdevWait", + "DmTaskSetName", + "DmTaskSetMessage", + "DmTaskSetAddNode", + ) + + Mounted = func(mnt string) (bool, error) { + calls["Mounted"] = true + return true, nil + } + + if err := d.Remove("1"); err != nil { + t.Fatal(err) + } + + calls.Assert(t, + "DmTaskRun", + "DmTaskSetSector", + "DmTaskSetName", + "DmTaskSetMessage", + "DmTaskCreate", + "DmTaskGetInfo", + "Mounted", + "sysUnmount", + ) + }() + runtime.GC() + + calls.Assert(t, + "DmTaskDestroy", + ) } func TestCleanup(t *testing.T) { diff --git a/components/engine/graphdriver/devmapper/mount.go b/components/engine/graphdriver/devmapper/mount.go index 3f75cfbcf9..7a07fff1e8 100644 --- a/components/engine/graphdriver/devmapper/mount.go +++ b/components/engine/graphdriver/devmapper/mount.go @@ -7,7 +7,7 @@ import ( // FIXME: this is copy-pasted from the aufs driver. // It should be moved into the core. -func Mounted(mountpoint string) (bool, error) { +var Mounted = func(mountpoint string) (bool, error) { mntpoint, err := osStat(mountpoint) if err != nil { if osIsNotExist(err) { From a1d40e81ca70b4d582070fb91868b2cb8fd9b758 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 20 Nov 2013 11:39:15 -0800 Subject: [PATCH 294/301] Use variable to call functions in devmapper_wrapper + some formatting Upstream-commit: 79e2b33ede415736f9124089a61f4512c16ddddd Component: engine --- .../engine/graphdriver/devmapper/devmapper.go | 5 +- .../devmapper/devmapper_wrapper.go | 46 ++++++++++--------- components/engine/graphdriver/driver.go | 23 +++------- 3 files changed, 33 insertions(+), 41 deletions(-) diff --git a/components/engine/graphdriver/devmapper/devmapper.go b/components/engine/graphdriver/devmapper/devmapper.go index de82bde0fd..89c36d45ad 100644 --- a/components/engine/graphdriver/devmapper/devmapper.go +++ b/components/engine/graphdriver/devmapper/devmapper.go @@ -187,7 +187,7 @@ func AttachLoopDevice(filename string) (*osFile, error) { } func getLoopbackBackingFile(file *osFile) (uint64, uint64, error) { - dev, inode, err := dmGetLoopbackBackingFile(file.Fd()) + dev, inode, err := DmGetLoopbackBackingFile(file.Fd()) if err != 0 { return 0, 0, ErrGetLoopbackBackingFile } @@ -195,8 +195,7 @@ func getLoopbackBackingFile(file *osFile) (uint64, uint64, error) { } func LoopbackSetCapacity(file *osFile) error { - err := dmLoopbackSetCapacity(file.Fd()) - if err != 0 { + if err := DmLoopbackSetCapacity(file.Fd()); err != 0 { return ErrLoopbackSetCapacity } return nil diff --git a/components/engine/graphdriver/devmapper/devmapper_wrapper.go b/components/engine/graphdriver/devmapper/devmapper_wrapper.go index 1f28412197..818c968bbf 100644 --- a/components/engine/graphdriver/devmapper/devmapper_wrapper.go +++ b/components/engine/graphdriver/devmapper/devmapper_wrapper.go @@ -148,26 +148,28 @@ type ( ) var ( - DmAttachLoopDevice = dmAttachLoopDeviceFct - DmGetBlockSize = dmGetBlockSizeFct - DmGetLibraryVersion = dmGetLibraryVersionFct - DmGetNextTarget = dmGetNextTargetFct - DmLogInitVerbose = dmLogInitVerboseFct - DmSetDevDir = dmSetDevDirFct - DmTaskAddTarget = dmTaskAddTargetFct - DmTaskCreate = dmTaskCreateFct - DmTaskDestroy = dmTaskDestroyFct - DmTaskGetInfo = dmTaskGetInfoFct - DmTaskRun = dmTaskRunFct - DmTaskSetAddNode = dmTaskSetAddNodeFct - DmTaskSetCookie = dmTaskSetCookieFct - DmTaskSetMessage = dmTaskSetMessageFct - DmTaskSetName = dmTaskSetNameFct - DmTaskSetRo = dmTaskSetRoFct - DmTaskSetSector = dmTaskSetSectorFct - DmUdevWait = dmUdevWaitFct - GetBlockSize = getBlockSizeFct - LogWithErrnoInit = logWithErrnoInitFct + DmAttachLoopDevice = dmAttachLoopDeviceFct + DmGetBlockSize = dmGetBlockSizeFct + DmGetLibraryVersion = dmGetLibraryVersionFct + DmGetNextTarget = dmGetNextTargetFct + DmLogInitVerbose = dmLogInitVerboseFct + DmSetDevDir = dmSetDevDirFct + DmTaskAddTarget = dmTaskAddTargetFct + DmTaskCreate = dmTaskCreateFct + DmTaskDestroy = dmTaskDestroyFct + DmTaskGetInfo = dmTaskGetInfoFct + DmTaskRun = dmTaskRunFct + DmTaskSetAddNode = dmTaskSetAddNodeFct + DmTaskSetCookie = dmTaskSetCookieFct + DmTaskSetMessage = dmTaskSetMessageFct + DmTaskSetName = dmTaskSetNameFct + DmTaskSetRo = dmTaskSetRoFct + DmTaskSetSector = dmTaskSetSectorFct + DmUdevWait = dmUdevWaitFct + GetBlockSize = getBlockSizeFct + LogWithErrnoInit = logWithErrnoInitFct + DmGetLoopbackBackingFile = dmGetLoopbackBackingFileFct + DmLoopbackSetCapacity = dmLoopbackSetCapacityFct ) func free(p *C.char) { @@ -238,14 +240,14 @@ func dmTaskAddTargetFct(task *CDmTask, C.uint64_t(start), C.uint64_t(size), Cttype, Cparams)) } -func dmGetLoopbackBackingFile(fd uintptr) (uint64, uint64, sysErrno) { +func dmGetLoopbackBackingFileFct(fd uintptr) (uint64, uint64, sysErrno) { var lo64 C.struct_loop_info64 _, _, err := sysSyscall(sysSysIoctl, fd, C.LOOP_GET_STATUS64, uintptr(unsafe.Pointer(&lo64))) return uint64(lo64.lo_device), uint64(lo64.lo_inode), sysErrno(err) } -func dmLoopbackSetCapacity(fd uintptr) sysErrno { +func dmLoopbackSetCapacityFct(fd uintptr) sysErrno { _, _, err := sysSyscall(sysSysIoctl, fd, C.LOOP_SET_CAPACITY, 0) return sysErrno(err) } diff --git a/components/engine/graphdriver/driver.go b/components/engine/graphdriver/driver.go index 211806e6c7..e44deb5a69 100644 --- a/components/engine/graphdriver/driver.go +++ b/components/engine/graphdriver/driver.go @@ -8,8 +8,6 @@ import ( "path" ) -var DefaultDriver string - type InitFunc func(root string) (Driver, error) type Driver interface { @@ -34,6 +32,7 @@ type Differ interface { } var ( + DefaultDriver string // All registred drivers drivers map[string]InitFunc // Slice of drivers that should be used in an order @@ -64,14 +63,8 @@ func GetDriver(name, home string) (Driver, error) { return nil, fmt.Errorf("No such driver: %s", name) } -func New(root string) (Driver, error) { - var driver Driver - var lastError error - - for _, name := range []string{ - os.Getenv("DOCKER_DRIVER"), - DefaultDriver, - } { +func New(root string) (driver Driver, err error) { + for _, name := range []string{os.Getenv("DOCKER_DRIVER"), DefaultDriver} { if name != "" { return GetDriver(name, root) } @@ -79,9 +72,8 @@ func New(root string) (Driver, error) { // Check for priority drivers first for _, name := range priority { - driver, lastError = GetDriver(name, root) - if lastError != nil { - utils.Debugf("Error loading driver %s: %s", name, lastError) + if driver, err = GetDriver(name, root); err != nil { + utils.Debugf("Error loading driver %s: %s", name, err) continue } return driver, nil @@ -89,11 +81,10 @@ func New(root string) (Driver, error) { // Check all registered drivers if no priority driver is found for _, initFunc := range drivers { - driver, lastError = initFunc(root) - if lastError != nil { + if driver, err = initFunc(root); err != nil { continue } return driver, nil } - return nil, lastError + return nil, err } From a54274e07271d80bd20af8100ecd0e891ed3a9be Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 22 Nov 2013 14:58:19 -0800 Subject: [PATCH 295/301] Rename dummy driver to vfs Upstream-commit: cee0a292d0b9afe96a1b4a2c66910f2485af2482 Component: engine --- components/engine/graphdriver/{dummy => vfs}/driver.go | 6 +++--- components/engine/runtime.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) rename components/engine/graphdriver/{dummy => vfs}/driver.go (95%) diff --git a/components/engine/graphdriver/dummy/driver.go b/components/engine/graphdriver/vfs/driver.go similarity index 95% rename from components/engine/graphdriver/dummy/driver.go rename to components/engine/graphdriver/vfs/driver.go index b527a84fd0..fab9d06f83 100644 --- a/components/engine/graphdriver/dummy/driver.go +++ b/components/engine/graphdriver/vfs/driver.go @@ -1,4 +1,4 @@ -package dummy +package vfs import ( "fmt" @@ -9,7 +9,7 @@ import ( ) func init() { - graphdriver.Register("dummy", Init) + graphdriver.Register("vfs", Init) } func Init(home string) (graphdriver.Driver, error) { @@ -24,7 +24,7 @@ type Driver struct { } func (d *Driver) String() string { - return "dummy" + return "vfs" } func (d *Driver) Status() [][2]string { diff --git a/components/engine/runtime.go b/components/engine/runtime.go index 09ec0c988b..4118e67d97 100644 --- a/components/engine/runtime.go +++ b/components/engine/runtime.go @@ -10,7 +10,7 @@ import ( "github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/graphdriver/aufs" _ "github.com/dotcloud/docker/graphdriver/devmapper" - _ "github.com/dotcloud/docker/graphdriver/dummy" + _ "github.com/dotcloud/docker/graphdriver/vfs" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -663,7 +663,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig) (*Runtime, error) { // We don't want to use a complex driver like aufs or devmapper // for volumes, just a plain filesystem - volumesDriver, err := graphdriver.GetDriver("dummy", config.Root) + volumesDriver, err := graphdriver.GetDriver("vfs", config.Root) if err != nil { return nil, err } From 3705ed705edf8a565dd11e22f5955d7d42e5b72a Mon Sep 17 00:00:00 2001 From: Danny Yates Date: Mon, 25 Nov 2013 17:12:18 +0000 Subject: [PATCH 296/301] Clone LVM using https: instead of git: The ports for the git protocol are not open in all corporate environments Upstream-commit: ad23745456f9bb9c1c3da3796a2ab769f192bf36 Component: engine --- components/engine/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/Dockerfile b/components/engine/Dockerfile index 5bc36f84c2..adba9f215a 100644 --- a/components/engine/Dockerfile +++ b/components/engine/Dockerfile @@ -57,7 +57,7 @@ run apt-get install -y -q lxc run apt-get install -y -q aufs-tools # Get lvm2 source for compiling statically -run git clone git://git.fedorahosted.org/git/lvm2.git /usr/local/lvm2 && cd /usr/local/lvm2 && git checkout v2_02_103 +run git clone https://git.fedorahosted.org/git/lvm2.git /usr/local/lvm2 && cd /usr/local/lvm2 && git checkout v2_02_103 # see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags # note: we can't use "git clone -b" above because it requires at least git 1.7.10 to be able to use that on a tag instead of a branch and we only have 1.7.9.5 From 93120354fd44d6b0aaaa6d68aa28527e8870a916 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 22 Nov 2013 14:17:24 -0800 Subject: [PATCH 297/301] Change graph-driver flag to be s Upstream-commit: b4eeb6be617d96132cbd5998a658e5ab88334940 Component: engine --- components/engine/docker/docker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/docker/docker.go b/components/engine/docker/docker.go index 112fbae705..83df5c2171 100644 --- a/components/engine/docker/docker.go +++ b/components/engine/docker/docker.go @@ -38,7 +38,7 @@ func main() { flEnableIptables := flag.Bool("iptables", true, "Disable docker's addition of iptables rules") flDefaultIp := flag.String("ip", "0.0.0.0", "Default IP address to use when binding container ports") flInterContainerComm := flag.Bool("icc", true, "Enable inter-container communication") - flGraphDriver := flag.String("graph-driver", "", "Force docker runtime to use a specific graph driver") + flGraphDriver := flag.String("s", "", "Force the docker runtime to use a specific storage driver") flag.Parse() From 5c5c14453257e55edcf6d73f71673e640c937726 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 22 Nov 2013 14:46:07 -0800 Subject: [PATCH 298/301] Add daemon docs with selecting graph driver Upstream-commit: d8f4b733f243a3da6cf5571f44b94b7109f326c4 Component: engine --- components/engine/docs/Dockerfile | 2 +- .../engine/docs/sources/commandline/cli.rst | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/components/engine/docs/Dockerfile b/components/engine/docs/Dockerfile index d3418ee968..53a5dfba9c 100644 --- a/components/engine/docs/Dockerfile +++ b/components/engine/docs/Dockerfile @@ -9,7 +9,7 @@ run apt-get install -y python-setuptools make run easy_install pip #from docs/requirements.txt, but here to increase cacheability run pip install Sphinx==1.1.3 -run pip install sphinxcontrib-httpdomain==1.1.8 +run pip install sphinxcontrib-httpdomain==1.1.9 add . /docs run cd /docs; make docs diff --git a/components/engine/docs/sources/commandline/cli.rst b/components/engine/docs/sources/commandline/cli.rst index 9875b5ded9..395f3f4c54 100644 --- a/components/engine/docs/sources/commandline/cli.rst +++ b/components/engine/docs/sources/commandline/cli.rst @@ -18,6 +18,38 @@ To list available commands, either run ``docker`` with no parameters or execute ... +.. _cli_daemon: + +``daemon`` +---------- + +:: + + Usage of docker: + -D=false: Enable debug mode + -H=[unix:///var/run/docker.sock]: Multiple tcp://host:port or unix://path/to/socket to bind in daemon mode, single connection otherwise + -api-enable-cors=false: Enable CORS headers in the remote API + -b="": Attach containers to a pre-existing network bridge; use 'none' to disable container networking + -d=false: Enable daemon mode + -dns="": Force docker to use specific DNS servers + -g="/var/lib/docker": Path to use as the root of the docker runtime + -icc=true: Enable inter-container communication + -ip="0.0.0.0": Default IP address to use when binding container ports + -iptables=true: Disable docker's addition of iptables rules + -p="/var/run/docker.pid": Path to use for daemon PID file + -r=true: Restart previously running containers + -s="": Force the docker runtime to use a specific storage driver + -v=false: Print version information and quit + +The docker daemon is the persistent process that manages containers. Docker uses the same binary for both the +daemon and client. To run the daemon you provide the ``-d`` flag. + +To force docker to use devicemapper as the storage driver, use ``docker -d -s devicemapper`` + +To set the dns server for all docker containers, use ``docker -d -dns 8.8.8.8`` + +To run the daemon with debug output, use ``docker -d -D`` + .. _cli_attach: ``attach`` From b1b08f39e63180e85c839fa653998e768e07f347 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 25 Nov 2013 10:28:17 -0800 Subject: [PATCH 299/301] Rename dummy in driver.go to vfs Upstream-commit: 8b0b10b6f9fa152346e62b5dad1f4d430ae0e4d1 Component: engine --- components/engine/graphdriver/driver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/engine/graphdriver/driver.go b/components/engine/graphdriver/driver.go index e44deb5a69..1d5995dffc 100644 --- a/components/engine/graphdriver/driver.go +++ b/components/engine/graphdriver/driver.go @@ -39,7 +39,7 @@ var ( priority = []string{ "aufs", "devicemapper", - "dummy", + "vfs", } ) From ae2b7fc016e6b9b8f97472729046b0ad9427f97c Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Mon, 25 Nov 2013 12:06:16 -0800 Subject: [PATCH 300/301] Reduce debugf frequency to avoid terminal freeze Upstream-commit: 78d2e2dc373a30eed2bd9f9bb140aa1e153c00f0 Component: engine --- components/engine/graphdriver/devmapper/deviceset.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/components/engine/graphdriver/devmapper/deviceset.go b/components/engine/graphdriver/devmapper/deviceset.go index d232b510d2..8c2556104d 100644 --- a/components/engine/graphdriver/devmapper/deviceset.go +++ b/components/engine/graphdriver/devmapper/deviceset.go @@ -650,10 +650,13 @@ func (devices *DeviceSet) waitRemove(hash string) error { // The error might actually be something else, but we can't differentiate. return nil } - utils.Debugf("Waiting for removal of %s: exists=%d", devname, devinfo.Exists) + if i%100 == 0 { + utils.Debugf("Waiting for removal of %s: exists=%d", devname, devinfo.Exists) + } if devinfo.Exists == 0 { break } + time.Sleep(1 * time.Millisecond) } if i == 1000 { @@ -676,7 +679,9 @@ func (devices *DeviceSet) waitClose(hash string) error { if err != nil { return err } - utils.Debugf("Waiting for unmount of %s: opencount=%d", devname, devinfo.OpenCount) + if i%100 == 0 { + utils.Debugf("Waiting for unmount of %s: opencount=%d", devname, devinfo.OpenCount) + } if devinfo.OpenCount == 0 { break } From 7102c4c81c658ab59037253af6ff96fd3f25c971 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 26 Nov 2013 02:45:32 +0000 Subject: [PATCH 301/301] Docs: update install pages (ubuntu and kernel requirements) to reflect the optional nature of AUFS Upstream-commit: aaefb8c07cc39735250b0304f73f8cb0e19662bc Component: engine --- .../engine/docs/sources/installation/kernel.rst | 15 ++------------- .../docs/sources/installation/ubuntulinux.rst | 14 ++++++-------- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/components/engine/docs/sources/installation/kernel.rst b/components/engine/docs/sources/installation/kernel.rst index b9abdc2722..07ec09042f 100644 --- a/components/engine/docs/sources/installation/kernel.rst +++ b/components/engine/docs/sources/installation/kernel.rst @@ -11,10 +11,10 @@ In short, Docker has the following kernel requirements: - Linux version 3.8 or above. -- `AUFS support `_. - - Cgroups and namespaces must be enabled. +*Note: as of 0.7 docker no longer requires aufs. AUFS support is still available as an optional driver.* + The officially supported kernel is the one recommended by the :ref:`ubuntu_linux` installation path. It is the one that most developers will use, and the one that receives the most attention from the core @@ -58,17 +58,6 @@ detects something older than 3.8. See issue `#407 `_ for details. -AUFS support ------------- - -Docker currently relies on AUFS, an unioning filesystem. -While AUFS is included in the kernels built by the Debian and Ubuntu -distributions, is not part of the standard kernel. This means that if -you decide to roll your own kernel, you will have to patch your -kernel tree to add AUFS. The process is documented on -`AUFS webpage `_. - - Cgroups and namespaces ---------------------- diff --git a/components/engine/docs/sources/installation/ubuntulinux.rst b/components/engine/docs/sources/installation/ubuntulinux.rst index c9cbbe2fdc..b5aae0cb83 100644 --- a/components/engine/docs/sources/installation/ubuntulinux.rst +++ b/components/engine/docs/sources/installation/ubuntulinux.rst @@ -14,16 +14,11 @@ Ubuntu Linux .. include:: install_header.inc -Right now, the officially supported distribution are: +Docker is supported on the following versions of Ubuntu: - :ref:`ubuntu_precise` - :ref:`ubuntu_raring` -Docker has the following dependencies - -* Linux kernel 3.8 (read more about :ref:`kernel`) -* AUFS file system support (we are working on BTRFS support as an alternative) - Please read :ref:`ufw`, if you plan to use `UFW (Uncomplicated Firewall) `_ @@ -107,10 +102,13 @@ Ubuntu Raring 13.04 (64 bit) Dependencies ------------ -**AUFS filesystem support** +**Optional AUFS filesystem support** Ubuntu Raring already comes with the 3.8 kernel, so we don't need to install it. However, not all systems -have AUFS filesystem support enabled, so we need to install it. +have AUFS filesystem support enabled. AUFS support is optional as of version 0.7, but it's still available as +a driver and we recommend using it if you can. + +To make sure aufs is installed, run the following commands: .. code-block:: bash