1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Merge pull request #16032 from cpuguy83/remove_sqlite_dep

Build names and links at runtime - no more sqlite
This commit is contained in:
Alexander Morozov 2016-01-11 10:59:49 -08:00
commit 9a23569ecf
14 changed files with 751 additions and 260 deletions

View file

@ -37,40 +37,36 @@ import (
func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]string, error) { func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]string, error) {
var env []string var env []string
children, err := daemon.children(container.Name) children := daemon.children(container)
if err != nil {
return nil, err
}
bridgeSettings := container.NetworkSettings.Networks["bridge"] bridgeSettings := container.NetworkSettings.Networks["bridge"]
if bridgeSettings == nil { if bridgeSettings == nil {
return nil, nil return nil, nil
} }
if len(children) > 0 { for linkAlias, child := range children {
for linkAlias, child := range children { if !child.IsRunning() {
if !child.IsRunning() { return nil, derr.ErrorCodeLinkNotRunning.WithArgs(child.Name, linkAlias)
return nil, derr.ErrorCodeLinkNotRunning.WithArgs(child.Name, linkAlias) }
}
childBridgeSettings := child.NetworkSettings.Networks["bridge"] childBridgeSettings := child.NetworkSettings.Networks["bridge"]
if childBridgeSettings == nil { if childBridgeSettings == nil {
return nil, fmt.Errorf("container %s not attached to default bridge network", child.ID) return nil, fmt.Errorf("container %s not attached to default bridge network", child.ID)
} }
link := links.NewLink( link := links.NewLink(
bridgeSettings.IPAddress, bridgeSettings.IPAddress,
childBridgeSettings.IPAddress, childBridgeSettings.IPAddress,
linkAlias, linkAlias,
child.Config.Env, child.Config.Env,
child.Config.ExposedPorts, child.Config.ExposedPorts,
) )
for _, envVar := range link.ToEnv() { for _, envVar := range link.ToEnv() {
env = append(env, envVar) env = append(env, envVar)
}
} }
} }
return env, nil return env, nil
} }
@ -419,11 +415,7 @@ func (daemon *Daemon) buildSandboxOptions(container *container.Container, n libn
var childEndpoints, parentEndpoints []string var childEndpoints, parentEndpoints []string
children, err := daemon.children(container.Name) children := daemon.children(container)
if err != nil {
return nil, err
}
for linkAlias, child := range children { for linkAlias, child := range children {
if !isLinkable(child) { if !isLinkable(child) {
return nil, fmt.Errorf("Cannot link to %s, as it does not belong to the default network", child.Name) return nil, fmt.Errorf("Cannot link to %s, as it does not belong to the default network", child.Name)
@ -443,23 +435,20 @@ func (daemon *Daemon) buildSandboxOptions(container *container.Container, n libn
} }
bridgeSettings := container.NetworkSettings.Networks["bridge"] bridgeSettings := container.NetworkSettings.Networks["bridge"]
refs := daemon.containerGraph().RefPaths(container.ID) for alias, parent := range daemon.parents(container) {
for _, ref := range refs { if daemon.configStore.DisableBridge || !container.HostConfig.NetworkMode.IsPrivate() {
if ref.ParentID == "0" {
continue continue
} }
c, err := daemon.GetContainer(ref.ParentID) _, alias = path.Split(alias)
if err != nil { logrus.Debugf("Update /etc/hosts of %s for alias %s with ip %s", parent.ID, alias, bridgeSettings.IPAddress)
logrus.Error(err) sboxOptions = append(sboxOptions, libnetwork.OptionParentUpdate(
} parent.ID,
alias,
if c != nil && !daemon.configStore.DisableBridge && container.HostConfig.NetworkMode.IsPrivate() { bridgeSettings.IPAddress,
logrus.Debugf("Update /etc/hosts of %s for alias %s with ip %s", c.ID, ref.Name, bridgeSettings.IPAddress) ))
sboxOptions = append(sboxOptions, libnetwork.OptionParentUpdate(c.ID, ref.Name, bridgeSettings.IPAddress)) if ep.ID() != "" {
if ep.ID() != "" { parentEndpoints = append(parentEndpoints, ep.ID())
parentEndpoints = append(parentEndpoints, ep.ID())
}
} }
} }
@ -471,7 +460,6 @@ func (daemon *Daemon) buildSandboxOptions(container *container.Container, n libn
} }
sboxOptions = append(sboxOptions, libnetwork.OptionGeneric(linkOptions)) sboxOptions = append(sboxOptions, libnetwork.OptionGeneric(linkOptions))
return sboxOptions, nil return sboxOptions, nil
} }

View file

@ -12,9 +12,9 @@ import (
"io/ioutil" "io/ioutil"
"net" "net"
"os" "os"
"path"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings"
"sync" "sync"
"syscall" "syscall"
"time" "time"
@ -53,6 +53,7 @@ import (
"github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/mount"
"github.com/docker/docker/pkg/namesgenerator" "github.com/docker/docker/pkg/namesgenerator"
"github.com/docker/docker/pkg/progress" "github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/registrar"
"github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/stringid"
@ -147,7 +148,6 @@ type Daemon struct {
trustKey libtrust.PrivateKey trustKey libtrust.PrivateKey
idIndex *truncindex.TruncIndex idIndex *truncindex.TruncIndex
configStore *Config configStore *Config
containerGraphDB *graphdb.Database
execDriver execdriver.Driver execDriver execdriver.Driver
statsCollector *statsCollector statsCollector *statsCollector
defaultLogConfig containertypes.LogConfig defaultLogConfig containertypes.LogConfig
@ -162,6 +162,8 @@ type Daemon struct {
gidMaps []idtools.IDMap gidMaps []idtools.IDMap
layerStore layer.Store layerStore layer.Store
imageStore image.Store imageStore image.Store
nameIndex *registrar.Registrar
linkIndex *linkIndex
} }
// GetContainer looks for a container using the provided information, which could be // GetContainer looks for a container using the provided information, which could be
@ -245,8 +247,7 @@ func (daemon *Daemon) registerName(container *container.Container) error {
logrus.Errorf("Error saving container name to disk: %v", err) logrus.Errorf("Error saving container name to disk: %v", err)
} }
} }
return daemon.nameIndex.Reserve(container.Name, container.ID)
return nil
} }
// Register makes a container object usable by the daemon as <container.ID> // Register makes a container object usable by the daemon as <container.ID>
@ -257,10 +258,8 @@ func (daemon *Daemon) Register(container *container.Container) error {
} else { } else {
container.NewNopInputPipe() container.NewNopInputPipe()
} }
daemon.containers.Add(container.ID, container)
// don't update the Suffixarray if we're starting up daemon.containers.Add(container.ID, container)
// we'll waste time if we update it for every container
daemon.idIndex.Add(container.ID) daemon.idIndex.Add(container.ID)
if container.IsRunning() { if container.IsRunning() {
@ -291,15 +290,10 @@ func (daemon *Daemon) Register(container *container.Container) error {
} }
func (daemon *Daemon) restore() error { func (daemon *Daemon) restore() error {
type cr struct {
container *container.Container
registered bool
}
var ( var (
debug = os.Getenv("DEBUG") != "" debug = os.Getenv("DEBUG") != ""
currentDriver = daemon.GraphDriverName() currentDriver = daemon.GraphDriverName()
containers = make(map[string]*cr) containers = make(map[string]*container.Container)
) )
if !debug { if !debug {
@ -332,63 +326,70 @@ func (daemon *Daemon) restore() error {
if (container.Driver == "" && currentDriver == "aufs") || container.Driver == currentDriver { if (container.Driver == "" && currentDriver == "aufs") || container.Driver == currentDriver {
logrus.Debugf("Loaded container %v", container.ID) logrus.Debugf("Loaded container %v", container.ID)
containers[container.ID] = &cr{container: container} containers[container.ID] = container
} else { } else {
logrus.Debugf("Cannot load container %s because it was created with another graph driver.", container.ID) logrus.Debugf("Cannot load container %s because it was created with another graph driver.", container.ID)
} }
} }
if entities := daemon.containerGraphDB.List("/", -1); entities != nil { var migrateLegacyLinks bool
for _, p := range entities.Paths() { restartContainers := make(map[*container.Container]chan struct{})
if !debug && logrus.GetLevel() == logrus.InfoLevel { for _, c := range containers {
fmt.Print(".") if err := daemon.registerName(c); err != nil {
} logrus.Errorf("Failed to register container %s: %s", c.ID, err)
continue
}
if err := daemon.Register(c); err != nil {
logrus.Errorf("Failed to register container %s: %s", c.ID, err)
continue
}
e := entities[p] // get list of containers we need to restart
if daemon.configStore.AutoRestart && c.ShouldRestart() {
restartContainers[c] = make(chan struct{})
}
if c, ok := containers[e.ID()]; ok { // if c.hostConfig.Links is nil (not just empty), then it is using the old sqlite links and needs to be migrated
c.registered = true if c.HostConfig != nil && c.HostConfig.Links == nil {
} migrateLegacyLinks = true
} }
} }
restartContainers := make(map[*container.Container]chan struct{}) // migrate any legacy links from sqlite
for _, c := range containers { linkdbFile := filepath.Join(daemon.root, "linkgraph.db")
if !c.registered { var legacyLinkDB *graphdb.Database
// Try to set the default name for a container if it exists prior to links if migrateLegacyLinks {
c.container.Name, err = daemon.generateNewName(c.container.ID) legacyLinkDB, err = graphdb.NewSqliteConn(linkdbFile)
if err != nil { if err != nil {
logrus.Debugf("Setting default id - %s", err) return fmt.Errorf("error connecting to legacy link graph DB %s, container links may be lost: %v", linkdbFile, err)
}
if err := daemon.registerName(c.container); err != nil {
logrus.Errorf("Failed to register container %s: %s", c.container.ID, err)
continue
}
} }
defer legacyLinkDB.Close()
}
if err := daemon.Register(c.container); err != nil { // Now that all the containers are registered, register the links
logrus.Errorf("Failed to register container %s: %s", c.container.ID, err) for _, c := range containers {
continue if migrateLegacyLinks {
if err := daemon.migrateLegacySqliteLinks(legacyLinkDB, c); err != nil {
return err
}
} }
// get list of containers we need to restart if err := daemon.registerLinks(c, c.HostConfig); err != nil {
if daemon.configStore.AutoRestart && c.container.ShouldRestart() { logrus.Errorf("failed to register link for container %s: %v", c.ID, err)
restartContainers[c.container] = make(chan struct{})
} }
} }
group := sync.WaitGroup{} group := sync.WaitGroup{}
for c, notifier := range restartContainers { for c, notifier := range restartContainers {
group.Add(1) group.Add(1)
go func(container *container.Container, chNotify chan struct{}) {
go func(c *container.Container, chNotify chan struct{}) {
defer group.Done() defer group.Done()
logrus.Debugf("Starting container %s", container.ID)
logrus.Debugf("Starting container %s", c.ID)
// ignore errors here as this is a best effort to wait for children to be // ignore errors here as this is a best effort to wait for children to be
// running before we try to start the container // running before we try to start the container
children, err := daemon.children(container.Name) children := daemon.children(c)
if err != nil {
logrus.Warnf("error getting children for %s: %v", container.Name, err)
}
timeout := time.After(5 * time.Second) timeout := time.After(5 * time.Second)
for _, child := range children { for _, child := range children {
if notifier, exists := restartContainers[child]; exists { if notifier, exists := restartContainers[child]; exists {
@ -398,11 +399,12 @@ func (daemon *Daemon) restore() error {
} }
} }
} }
if err := daemon.containerStart(container); err != nil { if err := daemon.containerStart(c); err != nil {
logrus.Errorf("Failed to start container %s: %s", container.ID, err) logrus.Errorf("Failed to start container %s: %s", c.ID, err)
} }
close(chNotify) close(chNotify)
}(c, notifier) }(c, notifier)
} }
group.Wait() group.Wait()
@ -452,28 +454,28 @@ func (daemon *Daemon) reserveName(id, name string) (string, error) {
if !validContainerNamePattern.MatchString(name) { if !validContainerNamePattern.MatchString(name) {
return "", fmt.Errorf("Invalid container name (%s), only %s are allowed", name, validContainerNameChars) return "", fmt.Errorf("Invalid container name (%s), only %s are allowed", name, validContainerNameChars)
} }
if name[0] != '/' { if name[0] != '/' {
name = "/" + name name = "/" + name
} }
if _, err := daemon.containerGraphDB.Set(name, id); err != nil { if err := daemon.nameIndex.Reserve(name, id); err != nil {
if !graphdb.IsNonUniqueNameError(err) { if err == registrar.ErrNameReserved {
return "", err id, err := daemon.nameIndex.Get(name)
if err != nil {
logrus.Errorf("got unexpected error while looking up reserved name: %v", err)
return "", err
}
return "", fmt.Errorf("Conflict. The name %q is already in use by container %s. You have to remove (or rename) that container to be able to reuse that name.", name, id)
} }
return "", fmt.Errorf("error reserving name: %s, error: %v", name, err)
conflictingContainer, err := daemon.GetByName(name)
if err != nil {
return "", err
}
return "", fmt.Errorf(
"Conflict. The name %q is already in use by container %s. You have to remove (or rename) that container to be able to reuse that name.", strings.TrimPrefix(name, "/"),
stringid.TruncateID(conflictingContainer.ID))
} }
return name, nil return name, nil
} }
func (daemon *Daemon) releaseName(name string) {
daemon.nameIndex.Release(name)
}
func (daemon *Daemon) generateNewName(id string) (string, error) { func (daemon *Daemon) generateNewName(id string) (string, error) {
var name string var name string
for i := 0; i < 6; i++ { for i := 0; i < 6; i++ {
@ -482,17 +484,17 @@ func (daemon *Daemon) generateNewName(id string) (string, error) {
name = "/" + name name = "/" + name
} }
if _, err := daemon.containerGraphDB.Set(name, id); err != nil { if err := daemon.nameIndex.Reserve(name, id); err != nil {
if !graphdb.IsNonUniqueNameError(err) { if err == registrar.ErrNameReserved {
return "", err continue
} }
continue return "", err
} }
return name, nil return name, nil
} }
name = "/" + stringid.TruncateID(id) name = "/" + stringid.TruncateID(id)
if _, err := daemon.containerGraphDB.Set(name, id); err != nil { if err := daemon.nameIndex.Reserve(name, id); err != nil {
return "", err return "", err
} }
return name, nil return name, nil
@ -542,32 +544,19 @@ func (daemon *Daemon) newContainer(name string, config *containertypes.Config, i
return base, err return base, err
} }
// GetFullContainerName returns a constructed container name. I think
// it has to do with the fact that a container is a file on disk and
// this is sort of just creating a file name.
func GetFullContainerName(name string) (string, error) {
if name == "" {
return "", fmt.Errorf("Container name cannot be empty")
}
if name[0] != '/' {
name = "/" + name
}
return name, nil
}
// GetByName returns a container given a name. // GetByName returns a container given a name.
func (daemon *Daemon) GetByName(name string) (*container.Container, error) { func (daemon *Daemon) GetByName(name string) (*container.Container, error) {
fullName, err := GetFullContainerName(name) fullName := name
if err != nil { if name[0] != '/' {
return nil, err fullName = "/" + name
} }
entity := daemon.containerGraphDB.Get(fullName) id, err := daemon.nameIndex.Get(fullName)
if entity == nil { if err != nil {
return nil, fmt.Errorf("Could not find entity for %s", name) return nil, fmt.Errorf("Could not find entity for %s", name)
} }
e := daemon.containers.Get(entity.ID()) e := daemon.containers.Get(id)
if e == nil { if e == nil {
return nil, fmt.Errorf("Could not find container for entity id %s", entity.ID()) return nil, fmt.Errorf("Could not find container for entity id %s", id)
} }
return e, nil return e, nil
} }
@ -584,48 +573,37 @@ func (daemon *Daemon) UnsubscribeFromEvents(listener chan interface{}) {
daemon.EventsService.Evict(listener) daemon.EventsService.Evict(listener)
} }
// children returns all child containers of the container with the // GetLabels for a container or image id
// given name. The containers are returned as a map from the container func (daemon *Daemon) GetLabels(id string) map[string]string {
// name to a pointer to Container. // TODO: TestCase
func (daemon *Daemon) children(name string) (map[string]*container.Container, error) { container := daemon.containers.Get(id)
name, err := GetFullContainerName(name) if container != nil {
if err != nil { return container.Config.Labels
return nil, err
} }
children := make(map[string]*container.Container)
err = daemon.containerGraphDB.Walk(name, func(p string, e *graphdb.Entity) error { img, err := daemon.GetImage(id)
c, err := daemon.GetContainer(e.ID()) if err == nil {
if err != nil { return img.ContainerConfig.Labels
return err
}
children[p] = c
return nil
}, 0)
if err != nil {
return nil, err
} }
return children, nil return nil
}
func (daemon *Daemon) children(c *container.Container) map[string]*container.Container {
return daemon.linkIndex.children(c)
} }
// parents returns the names of the parent containers of the container // parents returns the names of the parent containers of the container
// with the given name. // with the given name.
func (daemon *Daemon) parents(name string) ([]string, error) { func (daemon *Daemon) parents(c *container.Container) map[string]*container.Container {
name, err := GetFullContainerName(name) return daemon.linkIndex.parents(c)
if err != nil {
return nil, err
}
return daemon.containerGraphDB.Parents(name)
} }
func (daemon *Daemon) registerLink(parent, child *container.Container, alias string) error { func (daemon *Daemon) registerLink(parent, child *container.Container, alias string) error {
fullName := filepath.Join(parent.Name, alias) fullName := path.Join(parent.Name, alias)
if !daemon.containerGraphDB.Exists(fullName) { if err := daemon.nameIndex.Reserve(fullName, child.ID); err != nil {
_, err := daemon.containerGraphDB.Set(fullName, child.ID)
return err return err
} }
daemon.linkIndex.link(parent, child, fullName)
return nil return nil
} }
@ -813,14 +791,6 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
return nil, fmt.Errorf("Error initializing network controller: %v", err) return nil, fmt.Errorf("Error initializing network controller: %v", err)
} }
graphdbPath := filepath.Join(config.Root, "linkgraph.db")
graph, err := graphdb.NewSqliteConn(graphdbPath)
if err != nil {
return nil, err
}
d.containerGraphDB = graph
sysInfo := sysinfo.New(false) sysInfo := sysinfo.New(false)
// Check if Devices cgroup is mounted, it is hard requirement for container security, // Check if Devices cgroup is mounted, it is hard requirement for container security,
// on Linux/FreeBSD. // on Linux/FreeBSD.
@ -852,10 +822,12 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
d.uidMaps = uidMaps d.uidMaps = uidMaps
d.gidMaps = gidMaps d.gidMaps = gidMaps
d.nameIndex = registrar.NewRegistrar()
d.linkIndex = newLinkIndex()
if err := d.cleanupMounts(); err != nil { if err := d.cleanupMounts(); err != nil {
return nil, err return nil, err
} }
go d.execCommandGC() go d.execCommandGC()
if err := d.restore(); err != nil { if err := d.restore(); err != nil {
@ -933,12 +905,6 @@ func (daemon *Daemon) Shutdown() error {
daemon.netController.Stop() daemon.netController.Stop()
} }
if daemon.containerGraphDB != nil {
if err := daemon.containerGraphDB.Close(); err != nil {
logrus.Errorf("Error during container graph.Close(): %v", err)
}
}
if daemon.layerStore != nil { if daemon.layerStore != nil {
if err := daemon.layerStore.Cleanup(); err != nil { if err := daemon.layerStore.Cleanup(); err != nil {
logrus.Errorf("Error during layer Store.Cleanup(): %v", err) logrus.Errorf("Error during layer Store.Cleanup(): %v", err)
@ -1340,10 +1306,6 @@ func (daemon *Daemon) ExecutionDriver() execdriver.Driver {
return daemon.execDriver return daemon.execDriver
} }
func (daemon *Daemon) containerGraph() *graphdb.Database {
return daemon.containerGraphDB
}
// GetUIDGIDMaps returns the current daemon's user namespace settings // GetUIDGIDMaps returns the current daemon's user namespace settings
// for the full uid and gid maps which will be applied to containers // for the full uid and gid maps which will be applied to containers
// started in this instance. // started in this instance.
@ -1420,9 +1382,14 @@ func (daemon *Daemon) setHostConfig(container *container.Container, hostConfig *
return err return err
} }
// make sure links is not nil
// this ensures that on the next daemon restart we don't try to migrate from legacy sqlite links
if hostConfig.Links == nil {
hostConfig.Links = []string{}
}
container.HostConfig = hostConfig container.HostConfig = hostConfig
container.ToDisk() return container.ToDisk()
return nil
} }
func (daemon *Daemon) setupInitLayer(initPath string) error { func (daemon *Daemon) setupInitLayer(initPath string) error {

View file

@ -3,12 +3,11 @@ package daemon
import ( import (
"io/ioutil" "io/ioutil"
"os" "os"
"path"
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/docker/docker/container" "github.com/docker/docker/container"
"github.com/docker/docker/pkg/graphdb" "github.com/docker/docker/pkg/registrar"
"github.com/docker/docker/pkg/truncindex" "github.com/docker/docker/pkg/truncindex"
"github.com/docker/docker/volume" "github.com/docker/docker/volume"
volumedrivers "github.com/docker/docker/volume/drivers" volumedrivers "github.com/docker/docker/volume/drivers"
@ -75,23 +74,18 @@ func TestGetContainer(t *testing.T) {
index.Add(c4.ID) index.Add(c4.ID)
index.Add(c5.ID) index.Add(c5.ID)
daemonTestDbPath := path.Join(os.TempDir(), "daemon_test.db")
graph, err := graphdb.NewSqliteConn(daemonTestDbPath)
if err != nil {
t.Fatalf("Failed to create daemon test sqlite database at %s", daemonTestDbPath)
}
graph.Set(c1.Name, c1.ID)
graph.Set(c2.Name, c2.ID)
graph.Set(c3.Name, c3.ID)
graph.Set(c4.Name, c4.ID)
graph.Set(c5.Name, c5.ID)
daemon := &Daemon{ daemon := &Daemon{
containers: store, containers: store,
idIndex: index, idIndex: index,
containerGraphDB: graph, nameIndex: registrar.NewRegistrar(),
} }
daemon.reserveName(c1.ID, c1.Name)
daemon.reserveName(c2.ID, c2.Name)
daemon.reserveName(c3.ID, c3.Name)
daemon.reserveName(c4.ID, c4.Name)
daemon.reserveName(c5.ID, c5.Name)
if container, _ := daemon.GetContainer("3cdbd1aa394fd68559fd1441d6eff2ab7c1e6363582c82febfaa8045df3bd8de"); container != c2 { if container, _ := daemon.GetContainer("3cdbd1aa394fd68559fd1441d6eff2ab7c1e6363582c82febfaa8045df3bd8de"); container != c2 {
t.Fatal("Should explicitly match full container IDs") t.Fatal("Should explicitly match full container IDs")
} }
@ -120,8 +114,6 @@ func TestGetContainer(t *testing.T) {
if _, err := daemon.GetContainer("nothing"); err == nil { if _, err := daemon.GetContainer("nothing"); err == nil {
t.Fatal("Should return an error when provided a prefix that is neither a name or a partial match to an ID") t.Fatal("Should return an error when provided a prefix that is neither a name or a partial match to an ID")
} }
os.Remove(daemonTestDbPath)
} }
func initDaemonWithVolumeStore(tmp string) (*Daemon, error) { func initDaemonWithVolumeStore(tmp string) (*Daemon, error) {
@ -206,19 +198,6 @@ func TestNetworkOptions(t *testing.T) {
} }
} }
func TestGetFullName(t *testing.T) {
name, err := GetFullContainerName("testing")
if err != nil {
t.Fatal(err)
}
if name != "/testing" {
t.Fatalf("Expected /testing got %s", name)
}
if _, err := GetFullContainerName(""); err == nil {
t.Fatal("Error should not be nil")
}
}
func TestValidContainerNames(t *testing.T) { func TestValidContainerNames(t *testing.T) {
invalidNames := []string{"-rm", "&sdfsfd", "safd%sd"} invalidNames := []string{"-rm", "&sdfsfd", "safd%sd"}
validNames := []string{"word-word", "word_word", "1weoid"} validNames := []string{"word-word", "word_word", "1weoid"}

View file

@ -676,7 +676,7 @@ func setupInitLayer(initLayer string, rootUID, rootGID int) error {
// registerLinks writes the links to a file. // registerLinks writes the links to a file.
func (daemon *Daemon) registerLinks(container *container.Container, hostConfig *containertypes.HostConfig) error { func (daemon *Daemon) registerLinks(container *container.Container, hostConfig *containertypes.HostConfig) error {
if hostConfig == nil || hostConfig.Links == nil { if hostConfig == nil {
return nil return nil
} }
@ -707,12 +707,7 @@ func (daemon *Daemon) registerLinks(container *container.Container, hostConfig *
// After we load all the links into the daemon // After we load all the links into the daemon
// set them to nil on the hostconfig // set them to nil on the hostconfig
hostConfig.Links = nil return container.WriteHostConfig()
if err := container.WriteHostConfig(); err != nil {
return err
}
return nil
} }
// conditionalMountOnStart is a platform specific helper function during the // conditionalMountOnStart is a platform specific helper function during the

View file

@ -1,8 +1,10 @@
package daemon package daemon
import ( import (
"fmt"
"os" "os"
"path" "path"
"strings"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/docker/container" "github.com/docker/docker/container"
@ -38,11 +40,10 @@ func (daemon *Daemon) ContainerRm(name string, config *types.ContainerRmConfig)
} }
if config.RemoveLink { if config.RemoveLink {
return daemon.rmLink(name) return daemon.rmLink(container, name)
} }
if err := daemon.cleanupContainer(container, config.ForceRemove); err != nil { if err := daemon.cleanupContainer(container, config.ForceRemove); err != nil {
// return derr.ErrorCodeCantDestroy.WithArgs(name, utils.GetErrorMessage(err))
return err return err
} }
@ -53,32 +54,29 @@ func (daemon *Daemon) ContainerRm(name string, config *types.ContainerRmConfig)
return nil return nil
} }
// rmLink removes link by name from other containers func (daemon *Daemon) rmLink(container *container.Container, name string) error {
func (daemon *Daemon) rmLink(name string) error { if name[0] != '/' {
name, err := GetFullContainerName(name) name = "/" + name
if err != nil {
return err
} }
parent, n := path.Split(name) parent, n := path.Split(name)
if parent == "/" { if parent == "/" {
return derr.ErrorCodeDefaultName return fmt.Errorf("Conflict, cannot remove the default name of the container")
}
pe := daemon.containerGraph().Get(parent)
if pe == nil {
return derr.ErrorCodeNoParent.WithArgs(parent, name)
} }
if err := daemon.containerGraph().Delete(name); err != nil { parent = strings.TrimSuffix(parent, "/")
return err pe, err := daemon.nameIndex.Get(parent)
if err != nil {
return fmt.Errorf("Cannot get parent %s for name %s", parent, name)
} }
parentContainer, _ := daemon.GetContainer(pe.ID()) daemon.releaseName(name)
parentContainer, _ := daemon.GetContainer(pe)
if parentContainer != nil { if parentContainer != nil {
daemon.linkIndex.unlink(name, container, parentContainer)
if err := daemon.updateNetwork(parentContainer); err != nil { if err := daemon.updateNetwork(parentContainer); err != nil {
logrus.Debugf("Could not update network to remove link %s: %v", n, err) logrus.Debugf("Could not update network to remove link %s: %v", n, err)
} }
} }
return nil return nil
} }
@ -116,9 +114,8 @@ func (daemon *Daemon) cleanupContainer(container *container.Container, forceRemo
// indexes even if removal failed. // indexes even if removal failed.
defer func() { defer func() {
if err == nil || forceRemove { if err == nil || forceRemove {
if _, err := daemon.containerGraphDB.Purge(container.ID); err != nil { daemon.nameIndex.Delete(container.ID)
logrus.Debugf("Unable to remove container from link graph: %s", err) daemon.linkIndex.delete(container)
}
selinuxFreeLxcContexts(container.ProcessLabel) selinuxFreeLxcContexts(container.ProcessLabel)
daemon.idIndex.Delete(container.ID) daemon.idIndex.Delete(container.ID)
daemon.containers.Delete(container.ID) daemon.containers.Delete(container.ID)
@ -139,7 +136,6 @@ func (daemon *Daemon) cleanupContainer(container *container.Container, forceRemo
if err = daemon.execDriver.Clean(container.ID); err != nil { if err = daemon.execDriver.Clean(container.ID); err != nil {
return derr.ErrorCodeRmExecDriver.WithArgs(container.ID, err) return derr.ErrorCodeRmExecDriver.WithArgs(container.ID, err)
} }
return nil return nil
} }

View file

@ -102,11 +102,12 @@ func (daemon *Daemon) getInspectData(container *container.Container, size bool)
// make a copy to play with // make a copy to play with
hostConfig := *container.HostConfig hostConfig := *container.HostConfig
if children, err := daemon.children(container.Name); err == nil { children := daemon.children(container)
for linkAlias, child := range children { hostConfig.Links = nil // do not expose the internal structure
hostConfig.Links = append(hostConfig.Links, fmt.Sprintf("%s:%s", child.Name, linkAlias)) for linkAlias, child := range children {
} hostConfig.Links = append(hostConfig.Links, fmt.Sprintf("%s:%s", child.Name, linkAlias))
} }
// we need this trick to preserve empty log driver, so // we need this trick to preserve empty log driver, so
// container will use daemon defaults even if daemon change them // container will use daemon defaults even if daemon change them
if hostConfig.LogConfig.Type == "" { if hostConfig.LogConfig.Type == "" {

128
daemon/links.go Normal file
View file

@ -0,0 +1,128 @@
package daemon
import (
"strings"
"sync"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/container"
"github.com/docker/docker/pkg/graphdb"
)
// linkIndex stores link relationships between containers, including their specified alias
// The alias is the name the parent uses to reference the child
type linkIndex struct {
// idx maps a parent->alias->child relationship
idx map[*container.Container]map[string]*container.Container
// childIdx maps child->parent->aliases
childIdx map[*container.Container]map[*container.Container]map[string]struct{}
mu sync.Mutex
}
func newLinkIndex() *linkIndex {
return &linkIndex{
idx: make(map[*container.Container]map[string]*container.Container),
childIdx: make(map[*container.Container]map[*container.Container]map[string]struct{}),
}
}
// link adds indexes for the passed in parent/child/alias relationships
func (l *linkIndex) link(parent, child *container.Container, alias string) {
l.mu.Lock()
if l.idx[parent] == nil {
l.idx[parent] = make(map[string]*container.Container)
}
l.idx[parent][alias] = child
if l.childIdx[child] == nil {
l.childIdx[child] = make(map[*container.Container]map[string]struct{})
}
if l.childIdx[child][parent] == nil {
l.childIdx[child][parent] = make(map[string]struct{})
}
l.childIdx[child][parent][alias] = struct{}{}
l.mu.Unlock()
}
// unlink removes the requested alias for the given parent/child
func (l *linkIndex) unlink(alias string, child, parent *container.Container) {
l.mu.Lock()
delete(l.idx[parent], alias)
delete(l.childIdx[child], parent)
l.mu.Unlock()
}
// children maps all the aliases-> children for the passed in parent
// aliases here are the aliases the parent uses to refer to the child
func (l *linkIndex) children(parent *container.Container) map[string]*container.Container {
l.mu.Lock()
children := l.idx[parent]
l.mu.Unlock()
return children
}
// parents maps all the aliases->parent for the passed in child
// aliases here are the aliases the parents use to refer to the child
func (l *linkIndex) parents(child *container.Container) map[string]*container.Container {
l.mu.Lock()
parents := make(map[string]*container.Container)
for parent, aliases := range l.childIdx[child] {
for alias := range aliases {
parents[alias] = parent
}
}
l.mu.Unlock()
return parents
}
// delete deletes all link relationships referencing this container
func (l *linkIndex) delete(container *container.Container) {
l.mu.Lock()
for _, child := range l.idx[container] {
delete(l.childIdx[child], container)
}
delete(l.idx, container)
delete(l.childIdx, container)
l.mu.Unlock()
}
// migrateLegacySqliteLinks migrates sqlite links to use links from HostConfig
// when sqlite links were used, hostConfig.Links was set to nil
func (daemon *Daemon) migrateLegacySqliteLinks(db *graphdb.Database, container *container.Container) error {
// if links is populated (or an empty slice), then this isn't using sqlite links and can be skipped
if container.HostConfig == nil || container.HostConfig.Links != nil {
return nil
}
logrus.Debugf("migrating legacy sqlite link info for container: %s", container.ID)
fullName := container.Name
if fullName[0] != '/' {
fullName = "/" + fullName
}
// don't use a nil slice, this ensures that the check above will skip once the migration has completed
links := []string{}
children, err := db.Children(fullName, 0)
if err != nil {
if !strings.Contains(err.Error(), "Cannot find child for") {
return err
}
// else continue... it's ok if we didn't find any children, it'll just be nil and we can continue the migration
}
for _, child := range children {
c, err := daemon.GetContainer(child.Entity.ID())
if err != nil {
return err
}
links = append(links, c.Name+":"+child.Edge.Name)
}
container.HostConfig.Links = links
return container.WriteHostConfig()
}

101
daemon/links_test.go Normal file
View file

@ -0,0 +1,101 @@
package daemon
import (
"encoding/json"
"io/ioutil"
"os"
"path"
"path/filepath"
"testing"
"github.com/docker/docker/container"
"github.com/docker/docker/pkg/graphdb"
"github.com/docker/docker/pkg/stringid"
containertypes "github.com/docker/engine-api/types/container"
)
func TestMigrateLegacySqliteLinks(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "legacy-qlite-links-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpDir)
name1 := "test1"
c1 := &container.Container{
CommonContainer: container.CommonContainer{
ID: stringid.GenerateNonCryptoID(),
Name: name1,
HostConfig: &containertypes.HostConfig{},
},
}
c1.Root = tmpDir
name2 := "test2"
c2 := &container.Container{
CommonContainer: container.CommonContainer{
ID: stringid.GenerateNonCryptoID(),
Name: name2,
},
}
store := &contStore{
s: map[string]*container.Container{
c1.ID: c1,
c2.ID: c2,
},
}
d := &Daemon{root: tmpDir, containers: store}
db, err := graphdb.NewSqliteConn(filepath.Join(d.root, "linkgraph.db"))
if err != nil {
t.Fatal(err)
}
if _, err := db.Set("/"+name1, c1.ID); err != nil {
t.Fatal(err)
}
if _, err := db.Set("/"+name2, c2.ID); err != nil {
t.Fatal(err)
}
alias := "hello"
if _, err := db.Set(path.Join(c1.Name, alias), c2.ID); err != nil {
t.Fatal(err)
}
if err := d.migrateLegacySqliteLinks(db, c1); err != nil {
t.Fatal(err)
}
if len(c1.HostConfig.Links) != 1 {
t.Fatal("expected links to be populated but is empty")
}
expected := name2 + ":" + alias
actual := c1.HostConfig.Links[0]
if actual != expected {
t.Fatalf("got wrong link value, expected: %q, got: %q", expected, actual)
}
// ensure this is persisted
b, err := ioutil.ReadFile(filepath.Join(c1.Root, "hostconfig.json"))
if err != nil {
t.Fatal(err)
}
type hc struct {
Links []string
}
var cfg hc
if err := json.Unmarshal(b, &cfg); err != nil {
t.Fatal(err)
}
if len(cfg.Links) != 1 {
t.Fatalf("expected one entry in links, got: %d", len(cfg.Links))
}
if cfg.Links[0] != expected { // same expected as above
t.Fatalf("got wrong link value, expected: %q, got: %q", expected, cfg.Links[0])
}
}

View file

@ -9,7 +9,6 @@ import (
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/docker/container" "github.com/docker/docker/container"
"github.com/docker/docker/image" "github.com/docker/docker/image"
"github.com/docker/docker/pkg/graphdb"
"github.com/docker/engine-api/types" "github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/filters" "github.com/docker/engine-api/types/filters"
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
@ -197,12 +196,6 @@ func (daemon *Daemon) foldFilter(config *ContainersConfig) (*listContext, error)
}) })
} }
names := make(map[string][]string)
daemon.containerGraph().Walk("/", func(p string, e *graphdb.Entity) error {
names[e.ID()] = append(names[e.ID()], p)
return nil
}, 1)
if config.Before != "" && beforeContFilter == nil { if config.Before != "" && beforeContFilter == nil {
beforeContFilter, err = daemon.GetContainer(config.Before) beforeContFilter, err = daemon.GetContainer(config.Before)
if err != nil { if err != nil {
@ -220,12 +213,12 @@ func (daemon *Daemon) foldFilter(config *ContainersConfig) (*listContext, error)
return &listContext{ return &listContext{
filters: psFilters, filters: psFilters,
ancestorFilter: ancestorFilter, ancestorFilter: ancestorFilter,
names: names,
images: imagesFilter, images: imagesFilter,
exitAllowed: filtExited, exitAllowed: filtExited,
beforeFilter: beforeContFilter, beforeFilter: beforeContFilter,
sinceFilter: sinceContFilter, sinceFilter: sinceContFilter,
ContainersConfig: config, ContainersConfig: config,
names: daemon.nameIndex.GetAll(),
}, nil }, nil
} }

View file

@ -40,14 +40,11 @@ func (daemon *Daemon) ContainerRename(oldName, newName string) error {
if err != nil { if err != nil {
container.Name = oldName container.Name = oldName
daemon.reserveName(container.ID, oldName) daemon.reserveName(container.ID, oldName)
daemon.containerGraphDB.Delete(newName) daemon.releaseName(newName)
} }
}() }()
if err = daemon.containerGraphDB.Delete(oldName); err != nil { daemon.releaseName(oldName)
return derr.ErrorCodeRenameDelete.WithArgs(oldName, err)
}
if err = container.ToDisk(); err != nil { if err = container.ToDisk(); err != nil {
return err return err
} }
@ -76,7 +73,6 @@ func (daemon *Daemon) ContainerRename(oldName, newName string) error {
if err != nil { if err != nil {
return err return err
} }
daemon.LogContainerEvent(container, "rename") daemon.LogContainerEvent(container, "rename")
return nil return nil
} }

View file

@ -1083,9 +1083,9 @@ func (s *DockerSuite) TestContainerApiDeleteRemoveLinks(c *check.C) {
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
c.Assert(links, checker.Equals, "[\"/tlink1:/tlink2/tlink1\"]", check.Commentf("expected to have links between containers")) c.Assert(links, checker.Equals, "[\"/tlink1:/tlink2/tlink1\"]", check.Commentf("expected to have links between containers"))
status, _, err := sockRequest("DELETE", "/containers/tlink2/tlink1?link=1", nil) status, b, err := sockRequest("DELETE", "/containers/tlink2/tlink1?link=1", nil)
c.Assert(err, checker.IsNil) c.Assert(err, check.IsNil)
c.Assert(status, checker.Equals, http.StatusNoContent) c.Assert(status, check.Equals, http.StatusNoContent, check.Commentf(string(b)))
linksPostRm, err := inspectFieldJSON(id2, "HostConfig.Links") linksPostRm, err := inspectFieldJSON(id2, "HostConfig.Links")
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)

View file

@ -1960,3 +1960,104 @@ func (s *DockerDaemonSuite) TestDaemonCgroupParent(c *check.C) {
} }
c.Assert(found, checker.True, check.Commentf("Cgroup path for container (%s) doesn't found in cgroups file: %s", expectedCgroup, cgroupPaths)) c.Assert(found, checker.True, check.Commentf("Cgroup path for container (%s) doesn't found in cgroups file: %s", expectedCgroup, cgroupPaths))
} }
func (s *DockerDaemonSuite) TestDaemonRestartWithLinks(c *check.C) {
testRequires(c, DaemonIsLinux) // Windows does not support links
err := s.d.StartWithBusybox()
c.Assert(err, check.IsNil)
out, err := s.d.Cmd("run", "-d", "--name=test", "busybox", "top")
c.Assert(err, check.IsNil, check.Commentf(out))
out, err = s.d.Cmd("run", "--name=test2", "--link", "test:abc", "busybox", "sh", "-c", "ping -c 1 -w 1 abc")
c.Assert(err, check.IsNil, check.Commentf(out))
c.Assert(s.d.Restart(), check.IsNil)
// should fail since test is not running yet
out, err = s.d.Cmd("start", "test2")
c.Assert(err, check.NotNil, check.Commentf(out))
out, err = s.d.Cmd("start", "test")
c.Assert(err, check.IsNil, check.Commentf(out))
out, err = s.d.Cmd("start", "-a", "test2")
c.Assert(err, check.IsNil, check.Commentf(out))
c.Assert(strings.Contains(out, "1 packets transmitted, 1 packets received"), check.Equals, true, check.Commentf(out))
}
func (s *DockerDaemonSuite) TestDaemonRestartWithNames(c *check.C) {
testRequires(c, DaemonIsLinux) // Windows does not support links
err := s.d.StartWithBusybox()
c.Assert(err, check.IsNil)
out, err := s.d.Cmd("create", "--name=test", "busybox")
c.Assert(err, check.IsNil, check.Commentf(out))
out, err = s.d.Cmd("run", "-d", "--name=test2", "busybox", "top")
c.Assert(err, check.IsNil, check.Commentf(out))
test2ID := strings.TrimSpace(out)
out, err = s.d.Cmd("run", "-d", "--name=test3", "--link", "test2:abc", "busybox", "top")
test3ID := strings.TrimSpace(out)
c.Assert(s.d.Restart(), check.IsNil)
out, err = s.d.Cmd("create", "--name=test", "busybox")
c.Assert(err, check.NotNil, check.Commentf("expected error trying to create container with duplicate name"))
// this one is no longer needed, removing simplifies the remainder of the test
out, err = s.d.Cmd("rm", "-f", "test")
c.Assert(err, check.IsNil, check.Commentf(out))
out, err = s.d.Cmd("ps", "-a", "--no-trunc")
c.Assert(err, check.IsNil, check.Commentf(out))
lines := strings.Split(strings.TrimSpace(out), "\n")[1:]
test2validated := false
test3validated := false
for _, line := range lines {
fields := strings.Fields(line)
names := fields[len(fields)-1]
switch fields[0] {
case test2ID:
c.Assert(names, check.Equals, "test2,test3/abc")
test2validated = true
case test3ID:
c.Assert(names, check.Equals, "test3")
test3validated = true
}
}
c.Assert(test2validated, check.Equals, true)
c.Assert(test3validated, check.Equals, true)
}
// TestRunLinksChanged checks that creating a new container with the same name does not update links
// this ensures that the old, pre gh#16032 functionality continues on
func (s *DockerDaemonSuite) TestRunLinksChanged(c *check.C) {
testRequires(c, DaemonIsLinux) // Windows does not support links
err := s.d.StartWithBusybox()
c.Assert(err, check.IsNil)
out, err := s.d.Cmd("run", "-d", "--name=test", "busybox", "top")
c.Assert(err, check.IsNil, check.Commentf(out))
out, err = s.d.Cmd("run", "--name=test2", "--link=test:abc", "busybox", "sh", "-c", "ping -c 1 abc")
c.Assert(err, check.IsNil, check.Commentf(out))
c.Assert(out, checker.Contains, "1 packets transmitted, 1 packets received")
out, err = s.d.Cmd("rm", "-f", "test")
c.Assert(err, check.IsNil, check.Commentf(out))
out, err = s.d.Cmd("run", "-d", "--name=test", "busybox", "top")
c.Assert(err, check.IsNil, check.Commentf(out))
out, err = s.d.Cmd("start", "-a", "test2")
c.Assert(err, check.NotNil, check.Commentf(out))
c.Assert(out, check.Not(checker.Contains), "1 packets transmitted, 1 packets received")
err = s.d.Restart()
c.Assert(err, check.IsNil)
out, err = s.d.Cmd("start", "-a", "test2")
c.Assert(err, check.NotNil, check.Commentf(out))
c.Assert(out, check.Not(checker.Contains), "1 packets transmitted, 1 packets received")
}

127
pkg/registrar/registrar.go Normal file
View file

@ -0,0 +1,127 @@
// Package registrar provides name registration. It reserves a name to a given key.
package registrar
import (
"errors"
"sync"
)
var (
// ErrNameReserved is an error which is returned when a name is requested to be reserved that already is reserved
ErrNameReserved = errors.New("name is reserved")
// ErrNameNotReserved is an error which is returned when trying to find a name that is not reserved
ErrNameNotReserved = errors.New("name is not reserved")
// ErrNoSuchKey is returned when trying to find the names for a key which is not known
ErrNoSuchKey = errors.New("provided key does not exist")
)
// Registrar stores indexes a list of keys and their registered names as well as indexes names and the key that they are registred to
// Names must be unique.
// Registrar is safe for concurrent access.
type Registrar struct {
idx map[string][]string
names map[string]string
mu sync.Mutex
}
// NewRegistrar creates a new Registrar with the an empty index
func NewRegistrar() *Registrar {
return &Registrar{
idx: make(map[string][]string),
names: make(map[string]string),
}
}
// Reserve registers a key to a name
// Reserve is idempotent
// Attempting to reserve a key to a name that already exists results in an `ErrNameReserved`
// A name reservation is globally unique
func (r *Registrar) Reserve(name, key string) error {
r.mu.Lock()
defer r.mu.Unlock()
if k, exists := r.names[name]; exists {
if k != key {
return ErrNameReserved
}
return nil
}
r.idx[key] = append(r.idx[key], name)
r.names[name] = key
return nil
}
// Release releases the reserved name
// Once released, a name can be reserved again
func (r *Registrar) Release(name string) {
r.mu.Lock()
defer r.mu.Unlock()
key, exists := r.names[name]
if !exists {
return
}
for i, n := range r.idx[key] {
if n != name {
continue
}
r.idx[key] = append(r.idx[key][:i], r.idx[key][i+1:]...)
break
}
delete(r.names, name)
if len(r.idx[key]) == 0 {
delete(r.idx, key)
}
}
// Delete removes all reservations for the passed in key.
// All names reserved to this key are released.
func (r *Registrar) Delete(key string) {
r.mu.Lock()
for _, name := range r.idx[key] {
delete(r.names, name)
}
delete(r.idx, key)
r.mu.Unlock()
}
// GetNames lists all the reserved names for the given key
func (r *Registrar) GetNames(key string) ([]string, error) {
r.mu.Lock()
defer r.mu.Unlock()
names, exists := r.idx[key]
if !exists {
return nil, ErrNoSuchKey
}
return names, nil
}
// Get returns the key that the passed in name is reserved to
func (r *Registrar) Get(name string) (string, error) {
r.mu.Lock()
key, exists := r.names[name]
r.mu.Unlock()
if !exists {
return "", ErrNameNotReserved
}
return key, nil
}
// GetAll returns all registered names
func (r *Registrar) GetAll() map[string][]string {
out := make(map[string][]string)
r.mu.Lock()
// copy index into out
for id, names := range r.idx {
out[id] = names
}
r.mu.Unlock()
return out
}

View file

@ -0,0 +1,119 @@
package registrar
import (
"reflect"
"testing"
)
func TestReserve(t *testing.T) {
r := NewRegistrar()
obj := "test1"
if err := r.Reserve("test", obj); err != nil {
t.Fatal(err)
}
if err := r.Reserve("test", obj); err != nil {
t.Fatal(err)
}
obj2 := "test2"
err := r.Reserve("test", obj2)
if err == nil {
t.Fatalf("expected error when reserving an already reserved name to another object")
}
if err != ErrNameReserved {
t.Fatal("expected `ErrNameReserved` error when attempting to reserve an already reserved name")
}
}
func TestRelease(t *testing.T) {
r := NewRegistrar()
obj := "testing"
if err := r.Reserve("test", obj); err != nil {
t.Fatal(err)
}
r.Release("test")
r.Release("test") // Ensure there is no panic here
if err := r.Reserve("test", obj); err != nil {
t.Fatal(err)
}
}
func TestGetNames(t *testing.T) {
r := NewRegistrar()
obj := "testing"
names := []string{"test1", "test2"}
for _, name := range names {
if err := r.Reserve(name, obj); err != nil {
t.Fatal(err)
}
}
r.Reserve("test3", "other")
names2, err := r.GetNames(obj)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(names, names2) {
t.Fatalf("Exepected: %v, Got: %v", names, names2)
}
}
func TestDelete(t *testing.T) {
r := NewRegistrar()
obj := "testing"
names := []string{"test1", "test2"}
for _, name := range names {
if err := r.Reserve(name, obj); err != nil {
t.Fatal(err)
}
}
r.Reserve("test3", "other")
r.Delete(obj)
_, err := r.GetNames(obj)
if err == nil {
t.Fatal("expected error getting names for deleted key")
}
if err != ErrNoSuchKey {
t.Fatal("expected `ErrNoSuchKey`")
}
}
func TestGet(t *testing.T) {
r := NewRegistrar()
obj := "testing"
name := "test"
_, err := r.Get(name)
if err == nil {
t.Fatal("expected error when key does not exist")
}
if err != ErrNameNotReserved {
t.Fatal(err)
}
if err := r.Reserve(name, obj); err != nil {
t.Fatal(err)
}
if _, err = r.Get(name); err != nil {
t.Fatal(err)
}
r.Delete(obj)
_, err = r.Get(name)
if err == nil {
t.Fatal("expected error when key does not exist")
}
if err != ErrNameNotReserved {
t.Fatal(err)
}
}