dotcloud/docker

View on GitHub
daemon/update.go

Summary

Maintainability
A
2 hrs
Test Coverage
package daemon // import "github.com/docker/docker/daemon"

import (
    "context"
    "fmt"

    "github.com/docker/docker/api/types/container"
    "github.com/docker/docker/api/types/events"
    "github.com/docker/docker/errdefs"
    "github.com/pkg/errors"
)

// ContainerUpdate updates configuration of the container
func (daemon *Daemon) ContainerUpdate(name string, hostConfig *container.HostConfig) (container.ContainerUpdateOKBody, error) {
    var warnings []string

    daemonCfg := daemon.config()
    warnings, err := daemon.verifyContainerSettings(daemonCfg, hostConfig, nil, true)
    if err != nil {
        return container.ContainerUpdateOKBody{Warnings: warnings}, errdefs.InvalidParameter(err)
    }

    if err := daemon.update(name, hostConfig); err != nil {
        return container.ContainerUpdateOKBody{Warnings: warnings}, err
    }

    return container.ContainerUpdateOKBody{Warnings: warnings}, nil
}

func (daemon *Daemon) update(name string, hostConfig *container.HostConfig) error {
    if hostConfig == nil {
        return nil
    }

    ctr, err := daemon.GetContainer(name)
    if err != nil {
        return err
    }

    restoreConfig := false
    backupHostConfig := *ctr.HostConfig

    defer func() {
        if restoreConfig {
            ctr.Lock()
            if !ctr.RemovalInProgress && !ctr.Dead {
                ctr.HostConfig = &backupHostConfig
                ctr.CheckpointTo(context.WithoutCancel(context.TODO()), daemon.containersReplica)
            }
            ctr.Unlock()
        }
    }()

    ctr.Lock()

    if ctr.RemovalInProgress || ctr.Dead {
        ctr.Unlock()
        return errCannotUpdate(ctr.ID, fmt.Errorf(`container is marked for removal and cannot be "update"`))
    }

    if err := ctr.UpdateContainer(hostConfig); err != nil {
        restoreConfig = true
        ctr.Unlock()
        return errCannotUpdate(ctr.ID, err)
    }
    if err := ctr.CheckpointTo(context.TODO(), daemon.containersReplica); err != nil {
        restoreConfig = true
        ctr.Unlock()
        return errCannotUpdate(ctr.ID, err)
    }

    ctr.Unlock()

    // if Restart Policy changed, we need to update container monitor
    if hostConfig.RestartPolicy.Name != "" {
        ctr.UpdateMonitor(hostConfig.RestartPolicy)
    }

    defer daemon.LogContainerEvent(ctr, events.ActionUpdate)

    // If container is not running, update hostConfig struct is enough,
    // resources will be updated when the container is started again.
    // If container is running (including paused), we need to update configs
    // to the real world.
    ctr.Lock()
    isRestarting := ctr.Restarting
    tsk, err := ctr.GetRunningTask()
    ctr.Unlock()
    if errdefs.IsConflict(err) || isRestarting {
        return nil
    }
    if err != nil {
        return err
    }

    if err := tsk.UpdateResources(context.TODO(), toContainerdResources(hostConfig.Resources)); err != nil {
        restoreConfig = true
        // TODO: it would be nice if containerd responded with better errors here so we can classify this better.
        return errCannotUpdate(ctr.ID, errdefs.System(err))
    }

    return nil
}

func errCannotUpdate(containerID string, err error) error {
    return errors.Wrap(err, "Cannot update container "+containerID)
}