80c44b4b2e
Commit 8b7af1d0f
added some code to update the DNSNames of all
endpoints attached to a sandbox by loading a new instance of each
affected endpoints from the datastore through a call to
`Network.EndpointByID()`.
This method then calls `Network.getEndpointFromStore()`, that in
turn calls `store.GetObject()`, which then calls `cache.get()`,
which calls `o.CopyTo(kvObject)`. This effectively creates a fresh
new instance of an Endpoint. However, endpoints are already kept in
memory by Sandbox, meaning we now have two in-memory instances of
the same Endpoint.
As it turns out, libnetwork is built around the idea that no two objects
representing the same thing should leave in-memory, otherwise breaking
mutex locking and optimistic locking (as both instances will have a drifting
version tracking ID -- dbIndex in libnetwork parliance).
In this specific case, this bug materializes by container rename failing
when applied a second time for a given container. An integration test is
added to make sure this won't happen again.
Signed-off-by: Albin Kerouanton <albinker@gmail.com>
166 lines
4.6 KiB
Go
166 lines
4.6 KiB
Go
package daemon // import "github.com/docker/docker/daemon"
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/containerd/log"
|
|
"github.com/docker/docker/api/types/events"
|
|
dockercontainer "github.com/docker/docker/container"
|
|
"github.com/docker/docker/daemon/network"
|
|
"github.com/docker/docker/errdefs"
|
|
"github.com/docker/docker/libnetwork"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// ContainerRename changes the name of a container, using the oldName
|
|
// to find the container. An error is returned if newName is already
|
|
// reserved.
|
|
func (daemon *Daemon) ContainerRename(oldName, newName string) (retErr error) {
|
|
if oldName == "" || newName == "" {
|
|
return errdefs.InvalidParameter(errors.New("Neither old nor new names may be empty"))
|
|
}
|
|
|
|
container, err := daemon.GetContainer(oldName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
container.Lock()
|
|
defer container.Unlock()
|
|
|
|
// Canonicalize name for comparing.
|
|
if newName[0] != '/' {
|
|
newName = "/" + newName
|
|
}
|
|
if container.Name == newName {
|
|
return errdefs.InvalidParameter(errors.New("Renaming a container with the same name as its current name"))
|
|
}
|
|
|
|
links := map[string]*dockercontainer.Container{}
|
|
for k, v := range daemon.linkIndex.children(container) {
|
|
if !strings.HasPrefix(k, container.Name) {
|
|
return errdefs.InvalidParameter(errors.Errorf("Linked container %s does not match parent %s", k, container.Name))
|
|
}
|
|
links[strings.TrimPrefix(k, container.Name)] = v
|
|
}
|
|
|
|
newName, err = daemon.reserveName(container.ID, newName)
|
|
if err != nil {
|
|
return errors.Wrap(err, "Error when allocating new name")
|
|
}
|
|
|
|
for k, v := range links {
|
|
daemon.containersReplica.ReserveName(newName+k, v.ID)
|
|
daemon.linkIndex.link(container, v, newName+k)
|
|
}
|
|
|
|
oldName = container.Name
|
|
container.Name = newName
|
|
|
|
defer func() {
|
|
if retErr != nil {
|
|
container.Name = oldName
|
|
daemon.reserveName(container.ID, oldName)
|
|
for k, v := range links {
|
|
daemon.containersReplica.ReserveName(oldName+k, v.ID)
|
|
daemon.linkIndex.link(container, v, oldName+k)
|
|
daemon.linkIndex.unlink(newName+k, v, container)
|
|
daemon.containersReplica.ReleaseName(newName + k)
|
|
}
|
|
daemon.releaseName(newName)
|
|
} else {
|
|
daemon.releaseName(oldName)
|
|
}
|
|
}()
|
|
|
|
for k, v := range links {
|
|
daemon.linkIndex.unlink(oldName+k, v, container)
|
|
daemon.containersReplica.ReleaseName(oldName + k)
|
|
}
|
|
if err := container.CheckpointTo(daemon.containersReplica); err != nil {
|
|
return err
|
|
}
|
|
|
|
if !container.Running {
|
|
daemon.LogContainerEventWithAttributes(container, events.ActionRename, map[string]string{
|
|
"oldName": oldName,
|
|
})
|
|
return nil
|
|
}
|
|
|
|
defer func() {
|
|
if retErr != nil {
|
|
container.Name = oldName
|
|
if err := container.CheckpointTo(daemon.containersReplica); err != nil {
|
|
log.G(context.TODO()).WithFields(log.Fields{
|
|
"containerID": container.ID,
|
|
"error": err,
|
|
}).Error("failed to write container state to disk during rename")
|
|
}
|
|
}
|
|
}()
|
|
|
|
if sid := container.NetworkSettings.SandboxID; sid != "" && daemon.netController != nil {
|
|
sb, err := daemon.netController.SandboxByID(sid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = sb.Rename(strings.TrimPrefix(container.Name, "/")); err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if retErr != nil {
|
|
if err := sb.Rename(oldName); err != nil {
|
|
log.G(context.TODO()).WithFields(log.Fields{
|
|
"sandboxID": sid,
|
|
"oldName": oldName,
|
|
"newName": newName,
|
|
"error": err,
|
|
}).Errorf("failed to revert sandbox rename")
|
|
}
|
|
}
|
|
}()
|
|
|
|
for nwName, epConfig := range container.NetworkSettings.Networks {
|
|
nw, err := daemon.FindNetwork(nwName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ep := sb.GetEndpoint(epConfig.EndpointID)
|
|
if ep == nil {
|
|
return fmt.Errorf("no endpoint attached to network %s found", nw.Name())
|
|
}
|
|
|
|
oldDNSNames := make([]string, len(epConfig.DNSNames))
|
|
copy(oldDNSNames, epConfig.DNSNames)
|
|
|
|
epConfig.DNSNames = buildEndpointDNSNames(container, epConfig.Aliases)
|
|
if err := ep.UpdateDNSNames(epConfig.DNSNames); err != nil {
|
|
return err
|
|
}
|
|
|
|
defer func(ep *libnetwork.Endpoint, epConfig *network.EndpointSettings, oldDNSNames []string) {
|
|
if retErr == nil {
|
|
return
|
|
}
|
|
|
|
epConfig.DNSNames = oldDNSNames
|
|
if err := ep.UpdateDNSNames(epConfig.DNSNames); err != nil {
|
|
log.G(context.TODO()).WithFields(log.Fields{
|
|
"sandboxID": sid,
|
|
"oldName": oldName,
|
|
"newName": newName,
|
|
"error": err,
|
|
}).Errorf("failed to revert DNSNames update")
|
|
}
|
|
}(ep, epConfig, oldDNSNames)
|
|
}
|
|
}
|
|
|
|
daemon.LogContainerEventWithAttributes(container, events.ActionRename, map[string]string{
|
|
"oldName": oldName,
|
|
})
|
|
return nil
|
|
}
|