Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic /cmd endpoint and dm specific operations (resize/resize-pool/trim-pool) #4202

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions api/client/cli.go
Expand Up @@ -38,6 +38,12 @@ func (cli *DockerCli) ParseCommands(args ...string) error {
if len(args) > 0 {
method, exists := cli.getMethod(args[0])
if !exists {
// Look for generic methods of form "plugin:operation"
op := strings.SplitN(args[0], ":", 2)
if len(op) == 2 {
return cli.GenericCmd(op[0], op[1], args[1:]...)
}

fmt.Println("Error: Command not found:", args[0])
return cli.CmdHelp(args[1:]...)
}
Expand Down
41 changes: 41 additions & 0 deletions api/client/commands.go
Expand Up @@ -2232,3 +2232,44 @@ func (cli *DockerCli) CmdLoad(args ...string) error {
}
return nil
}

func validateGenericOpt(val string) (string, error) {
if val == "args" {
return "", fmt.Errorf("options names args are not allowed")
}
if strings.Count(val, "=") == 0 {
return "", fmt.Errorf("%s is not a key-value option", val)
}
return val, nil
}

func (cli *DockerCli) GenericCmd(target, op string, args ...string) error {
cmd := cli.Subcmd(target+":"+op, "[ARGUMENTS]", "Plugin specific operation")

flOpts := opts.NewListOpts(validateGenericOpt)
cmd.Var(&flOpts, []string{"o"}, "Set operation specific env var -o \"DEBUG=1\"")

if err := cmd.Parse(args); err != nil {
return nil
}

val := url.Values{}
for _, arg := range cmd.Args() {
val.Add("args", arg)
}

for _, o := range flOpts.GetAll() {
k, v, err := utils.ParseKeyValueOpt(o)
if err != nil {
return err
}
val.Add(k, v)
}

_, _, err := readBody(cli.call("GET", "/cmd/"+target+"/"+op+"?"+val.Encode(), nil, false))
if err != nil {
return err
}

return nil
}
31 changes: 31 additions & 0 deletions api/server/server.go
Expand Up @@ -118,6 +118,36 @@ func getBoolParam(value string) (bool, error) {
return ret, nil
}

func getCmdOperation(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
if vars == nil {
return fmt.Errorf("Missing parameter")
}
if err := parseForm(r); err != nil {
return err
}

jobname := vars["plugin"] + ":" + vars["operation"]

var args []string

if v, ok := r.Form["args"]; ok {
args = v
}

job := eng.Job(jobname, args...)

for key, value := range r.Form {
if key != "args" {
job.Setenv(key, value[0])
}
}
if err := job.Run(); err != nil {
return err
}
w.WriteHeader(http.StatusNoContent)
return nil
}

func postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
var (
authConfig, err = ioutil.ReadAll(r.Body)
Expand Down Expand Up @@ -1079,6 +1109,7 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st
"/containers/{name:.*}/top": getContainersTop,
"/containers/{name:.*}/logs": getContainersLogs,
"/containers/{name:.*}/attach/ws": wsContainersAttach,
"/cmd/{plugin:.*}/{operation:.*}": getCmdOperation,
},
"POST": {
"/auth": postAuth,
Expand Down
5 changes: 5 additions & 0 deletions daemon/daemon.go
Expand Up @@ -884,6 +884,11 @@ func NewDaemonFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (*D
if err := daemon.restore(); err != nil {
return nil, err
}

if installer, ok := daemon.driver.(engine.Installer); ok {
installer.Install(eng)
}

return daemon, nil
}

Expand Down
190 changes: 189 additions & 1 deletion daemon/graphdriver/devmapper/deviceset.go
Expand Up @@ -456,7 +456,100 @@ func minor(device uint64) uint64 {
return (device & 0xff) | ((device >> 12) & 0xfff00)
}

func (devices *DeviceSet) getBlockDevice(name string) (*os.File, error) {
dirname := devices.loopbackDir()
filename := path.Join(dirname, name)

file, err := os.OpenFile(filename, os.O_RDWR, 0)
if file == nil {
return nil, err
}
defer file.Close()

loopback := FindLoopDeviceFor(file)
if loopback == nil {
return nil, fmt.Errorf("Unable to find loopback mount for: %s", filename)
}
return loopback, nil
}

func (devices *DeviceSet) TrimPool() error {
devices.Lock()
defer devices.Unlock()

totalSizeInSectors, _, _, dataTotal, _, _, err := devices.poolStatus()
if err != nil {
return err
}
blockSizeInSectors := totalSizeInSectors / dataTotal
SectorSize := blockSizeInSectors * 512

data, err := devices.getBlockDevice("data")
if err != nil {
return err
}
defer data.Close()

dataSize, err := GetBlockDeviceSize(data)
if err != nil {
return err
}

metadata, err := devices.getBlockDevice("metadata")
if err != nil {
return err
}
defer metadata.Close()

// Suspend the pool so the metadata doesn't change and new blocks
// are not loaded
if err := suspendDevice(devices.getPoolName()); err != nil {
return fmt.Errorf("Unable to suspend pool: %s", err)
}

// Just in case, make sure everything is on disk
syscall.Sync()

ranges, err := readMetadataRanges(metadata.Name())
if err != nil {
resumeDevice(devices.getPoolName())
return err
}

lastEnd := uint64(0)

for e := ranges.Front(); e != nil; e = e.Next() {
r := e.Value.(*Range)
// Convert to bytes
rBegin := r.begin * SectorSize
rEnd := r.end * SectorSize

if rBegin > lastEnd {
if err := BlockDeviceDiscard(data, lastEnd, rBegin-lastEnd); err != nil {
return fmt.Errorf("Failing do discard block, leaving pool suspended: %v", err)
}
}
lastEnd = rEnd
}

if dataSize > lastEnd {
if err := BlockDeviceDiscard(data, lastEnd, dataSize-lastEnd); err != nil {
return fmt.Errorf("Failing do discard block, leaving pool suspended: %v", 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) ResizePool(size int64) error {
devices.Lock()
defer devices.Unlock()

dirname := devices.loopbackDir()
datafilename := path.Join(dirname, "data")
metadatafilename := path.Join(dirname, "metadata")
Expand Down Expand Up @@ -704,7 +797,7 @@ func (devices *DeviceSet) deleteDevice(info *DevInfo) error {
// on the thin pool when we remove a thinp device, so we do it
// manually
if err := devices.activateDeviceIfNeeded(info); err == nil {
if err := BlockDeviceDiscard(info.DevName()); err != nil {
if err := BlockDeviceDiscardAll(info.DevName()); err != nil {
utils.Debugf("Error discarding block on device: %s (ignoring)\n", err)
}
}
Expand Down Expand Up @@ -1148,6 +1241,101 @@ func (devices *DeviceSet) Status() *Status {
return status
}

func (devices *DeviceSet) ResizeDevice(hash string, size int64) error {
info, err := devices.lookupDevice(hash)
if err != nil {
return err
}

info.lock.Lock()
defer info.lock.Unlock()

if size < 0 || info.Size > uint64(size) {
return fmt.Errorf("Can't shrink devices")
}

devices.Lock()
defer devices.Unlock()

devinfo, err := getInfo(info.Name())
if info == nil {
return err
}

if devinfo.OpenCount != 0 {
return fmt.Errorf("Device in use")
}

if devinfo.Exists != 0 {
if err := devices.deactivateDevice(info); err != nil {
return err
}
}
oldSize := info.Size
info.Size = uint64(size)

if err := devices.saveMetadata(info); err != nil {
info.Size = oldSize
return err
}

// Activate with new size
if err := devices.activateDeviceIfNeeded(info); err != nil {
return err
}

fstype, err := ProbeFsType(info.DevName())
if err != nil {
return err
}

switch fstype {
case "xfs":
dir, err := ioutil.TempDir(devices.root, "resizemnt")
if err != nil {
return err
}

defer os.Remove(dir)

err = syscall.Mount(info.DevName(), dir, "xfs", syscall.MS_MGC_VAL, "nouuid")
if err != nil {
return err
}

err = exec.Command("xfs_growfs", dir).Run()
if err != nil {
syscall.Unmount(dir, 0)
return fmt.Errorf("xfs_growfs failed: %v", err)
}

err = syscall.Unmount(dir, 0)
if err != nil {
return err
}

case "ext4":
err = exec.Command("e2fsck", "-f", "-y", info.DevName()).Run()
if err != nil {
return fmt.Errorf("e2fsck failed: %v", err)
}

err = exec.Command("resize2fs", info.DevName()).Run()
if err != nil {
return fmt.Errorf("resizee2fs failed: %v", err)
}

default:
return fmt.Errorf("Unsupported filesystem %s", fstype)
}

if err := devices.deactivateDevice(info); err != nil {
return err
}

return nil
}

func NewDeviceSet(root string, doInit bool, options []string) (*DeviceSet, error) {
SetDevDir("/dev")

Expand Down
12 changes: 10 additions & 2 deletions daemon/graphdriver/devmapper/devmapper.go
Expand Up @@ -304,7 +304,7 @@ func GetBlockDeviceSize(file *os.File) (uint64, error) {
return uint64(size), nil
}

func BlockDeviceDiscard(path string) error {
func BlockDeviceDiscardAll(path string) error {
file, err := os.OpenFile(path, os.O_RDWR, 0)
if err != nil {
return err
Expand All @@ -316,7 +316,7 @@ func BlockDeviceDiscard(path string) error {
return err
}

if err := ioctlBlkDiscard(file.Fd(), 0, size); err != nil {
if err := BlockDeviceDiscard(file, 0, size); err != nil {
return err
}

Expand All @@ -327,6 +327,14 @@ func BlockDeviceDiscard(path string) error {
return nil
}

func BlockDeviceDiscard(file *os.File, offset, length uint64) error {
if err := ioctlBlkDiscard(file.Fd(), offset, length); err != nil {
return err
}

return nil
}

// This is the programmatic example of "dmsetup create"
func createPool(poolName string, dataFile, metadataFile *os.File) error {
task, err := createTask(DeviceCreate, poolName)
Expand Down