Browse code

Build names and links at runtime

Don't rely on sqlite db for name registration and linking.
Instead register names and links when the daemon starts to an in-memory
store.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>

Brian Goff authored on 2015/09/04 09:51:04
Showing 13 changed files
... ...
@@ -37,40 +37,36 @@ import (
37 37
 
38 38
 func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]string, error) {
39 39
 	var env []string
40
-	children, err := daemon.children(container.Name)
41
-	if err != nil {
42
-		return nil, err
43
-	}
40
+	children := daemon.children(container)
44 41
 
45 42
 	bridgeSettings := container.NetworkSettings.Networks["bridge"]
46 43
 	if bridgeSettings == nil {
47 44
 		return nil, nil
48 45
 	}
49 46
 
50
-	if len(children) > 0 {
51
-		for linkAlias, child := range children {
52
-			if !child.IsRunning() {
53
-				return nil, derr.ErrorCodeLinkNotRunning.WithArgs(child.Name, linkAlias)
54
-			}
47
+	for linkAlias, child := range children {
48
+		if !child.IsRunning() {
49
+			return nil, derr.ErrorCodeLinkNotRunning.WithArgs(child.Name, linkAlias)
50
+		}
55 51
 
56
-			childBridgeSettings := child.NetworkSettings.Networks["bridge"]
57
-			if childBridgeSettings == nil {
58
-				return nil, fmt.Errorf("container %s not attached to default bridge network", child.ID)
59
-			}
52
+		childBridgeSettings := child.NetworkSettings.Networks["bridge"]
53
+		if childBridgeSettings == nil {
54
+			return nil, fmt.Errorf("container %s not attached to default bridge network", child.ID)
55
+		}
60 56
 
61
-			link := links.NewLink(
62
-				bridgeSettings.IPAddress,
63
-				childBridgeSettings.IPAddress,
64
-				linkAlias,
65
-				child.Config.Env,
66
-				child.Config.ExposedPorts,
67
-			)
57
+		link := links.NewLink(
58
+			bridgeSettings.IPAddress,
59
+			childBridgeSettings.IPAddress,
60
+			linkAlias,
61
+			child.Config.Env,
62
+			child.Config.ExposedPorts,
63
+		)
68 64
 
69
-			for _, envVar := range link.ToEnv() {
70
-				env = append(env, envVar)
71
-			}
65
+		for _, envVar := range link.ToEnv() {
66
+			env = append(env, envVar)
72 67
 		}
73 68
 	}
69
+
74 70
 	return env, nil
75 71
 }
76 72
 
... ...
@@ -419,11 +415,7 @@ func (daemon *Daemon) buildSandboxOptions(container *container.Container, n libn
419 419
 
420 420
 	var childEndpoints, parentEndpoints []string
421 421
 
422
-	children, err := daemon.children(container.Name)
423
-	if err != nil {
424
-		return nil, err
425
-	}
426
-
422
+	children := daemon.children(container)
427 423
 	for linkAlias, child := range children {
428 424
 		if !isLinkable(child) {
429 425
 			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
443 443
 	}
444 444
 
445 445
 	bridgeSettings := container.NetworkSettings.Networks["bridge"]
446
-	refs := daemon.containerGraph().RefPaths(container.ID)
447
-	for _, ref := range refs {
448
-		if ref.ParentID == "0" {
446
+	for alias, parent := range daemon.parents(container) {
447
+		if daemon.configStore.DisableBridge || !container.HostConfig.NetworkMode.IsPrivate() {
449 448
 			continue
450 449
 		}
451 450
 
452
-		c, err := daemon.GetContainer(ref.ParentID)
453
-		if err != nil {
454
-			logrus.Error(err)
455
-		}
456
-
457
-		if c != nil && !daemon.configStore.DisableBridge && container.HostConfig.NetworkMode.IsPrivate() {
458
-			logrus.Debugf("Update /etc/hosts of %s for alias %s with ip %s", c.ID, ref.Name, bridgeSettings.IPAddress)
459
-			sboxOptions = append(sboxOptions, libnetwork.OptionParentUpdate(c.ID, ref.Name, bridgeSettings.IPAddress))
460
-			if ep.ID() != "" {
461
-				parentEndpoints = append(parentEndpoints, ep.ID())
462
-			}
451
+		_, alias = path.Split(alias)
452
+		logrus.Debugf("Update /etc/hosts of %s for alias %s with ip %s", parent.ID, alias, bridgeSettings.IPAddress)
453
+		sboxOptions = append(sboxOptions, libnetwork.OptionParentUpdate(
454
+			parent.ID,
455
+			alias,
456
+			bridgeSettings.IPAddress,
457
+		))
458
+		if ep.ID() != "" {
459
+			parentEndpoints = append(parentEndpoints, ep.ID())
463 460
 		}
464 461
 	}
465 462
 
... ...
@@ -471,7 +460,6 @@ func (daemon *Daemon) buildSandboxOptions(container *container.Container, n libn
471 471
 	}
472 472
 
473 473
 	sboxOptions = append(sboxOptions, libnetwork.OptionGeneric(linkOptions))
474
-
475 474
 	return sboxOptions, nil
476 475
 }
477 476
 
... ...
@@ -12,9 +12,9 @@ import (
12 12
 	"io/ioutil"
13 13
 	"net"
14 14
 	"os"
15
+	"path"
15 16
 	"path/filepath"
16 17
 	"runtime"
17
-	"strings"
18 18
 	"sync"
19 19
 	"syscall"
20 20
 	"time"
... ...
@@ -48,11 +48,11 @@ import (
48 48
 	"github.com/docker/docker/pkg/archive"
49 49
 	"github.com/docker/docker/pkg/discovery"
50 50
 	"github.com/docker/docker/pkg/fileutils"
51
-	"github.com/docker/docker/pkg/graphdb"
52 51
 	"github.com/docker/docker/pkg/idtools"
53 52
 	"github.com/docker/docker/pkg/mount"
54 53
 	"github.com/docker/docker/pkg/namesgenerator"
55 54
 	"github.com/docker/docker/pkg/progress"
55
+	"github.com/docker/docker/pkg/registrar"
56 56
 	"github.com/docker/docker/pkg/signal"
57 57
 	"github.com/docker/docker/pkg/streamformatter"
58 58
 	"github.com/docker/docker/pkg/stringid"
... ...
@@ -147,7 +147,6 @@ type Daemon struct {
147 147
 	trustKey                  libtrust.PrivateKey
148 148
 	idIndex                   *truncindex.TruncIndex
149 149
 	configStore               *Config
150
-	containerGraphDB          *graphdb.Database
151 150
 	execDriver                execdriver.Driver
152 151
 	statsCollector            *statsCollector
153 152
 	defaultLogConfig          containertypes.LogConfig
... ...
@@ -162,6 +161,8 @@ type Daemon struct {
162 162
 	gidMaps                   []idtools.IDMap
163 163
 	layerStore                layer.Store
164 164
 	imageStore                image.Store
165
+	nameIndex                 *registrar.Registrar
166
+	linkIndex                 *linkIndex
165 167
 }
166 168
 
167 169
 // GetContainer looks for a container using the provided information, which could be
... ...
@@ -245,8 +246,7 @@ func (daemon *Daemon) registerName(container *container.Container) error {
245 245
 			logrus.Errorf("Error saving container name to disk: %v", err)
246 246
 		}
247 247
 	}
248
-
249
-	return nil
248
+	return daemon.nameIndex.Reserve(container.Name, container.ID)
250 249
 }
251 250
 
252 251
 // Register makes a container object usable by the daemon as <container.ID>
... ...
@@ -257,10 +257,8 @@ func (daemon *Daemon) Register(container *container.Container) error {
257 257
 	} else {
258 258
 		container.NewNopInputPipe()
259 259
 	}
260
-	daemon.containers.Add(container.ID, container)
261 260
 
262
-	// don't update the Suffixarray if we're starting up
263
-	// we'll waste time if we update it for every container
261
+	daemon.containers.Add(container.ID, container)
264 262
 	daemon.idIndex.Add(container.ID)
265 263
 
266 264
 	if container.IsRunning() {
... ...
@@ -291,15 +289,10 @@ func (daemon *Daemon) Register(container *container.Container) error {
291 291
 }
292 292
 
293 293
 func (daemon *Daemon) restore() error {
294
-	type cr struct {
295
-		container  *container.Container
296
-		registered bool
297
-	}
298
-
299 294
 	var (
300 295
 		debug         = os.Getenv("DEBUG") != ""
301 296
 		currentDriver = daemon.GraphDriverName()
302
-		containers    = make(map[string]*cr)
297
+		containers    = make(map[string]*container.Container)
303 298
 	)
304 299
 
305 300
 	if !debug {
... ...
@@ -332,63 +325,48 @@ func (daemon *Daemon) restore() error {
332 332
 		if (container.Driver == "" && currentDriver == "aufs") || container.Driver == currentDriver {
333 333
 			logrus.Debugf("Loaded container %v", container.ID)
334 334
 
335
-			containers[container.ID] = &cr{container: container}
335
+			containers[container.ID] = container
336 336
 		} else {
337 337
 			logrus.Debugf("Cannot load container %s because it was created with another graph driver.", container.ID)
338 338
 		}
339 339
 	}
340 340
 
341
-	if entities := daemon.containerGraphDB.List("/", -1); entities != nil {
342
-		for _, p := range entities.Paths() {
343
-			if !debug && logrus.GetLevel() == logrus.InfoLevel {
344
-				fmt.Print(".")
345
-			}
346
-
347
-			e := entities[p]
348
-
349
-			if c, ok := containers[e.ID()]; ok {
350
-				c.registered = true
351
-			}
352
-		}
353
-	}
354
-
355 341
 	restartContainers := make(map[*container.Container]chan struct{})
356 342
 	for _, c := range containers {
357
-		if !c.registered {
358
-			// Try to set the default name for a container if it exists prior to links
359
-			c.container.Name, err = daemon.generateNewName(c.container.ID)
360
-			if err != nil {
361
-				logrus.Debugf("Setting default id - %s", err)
362
-			}
363
-			if err := daemon.registerName(c.container); err != nil {
364
-				logrus.Errorf("Failed to register container %s: %s", c.container.ID, err)
365
-				continue
366
-			}
343
+		if err := daemon.registerName(c); err != nil {
344
+			logrus.Errorf("Failed to register container %s: %s", c.ID, err)
345
+			continue
367 346
 		}
368
-
369
-		if err := daemon.Register(c.container); err != nil {
370
-			logrus.Errorf("Failed to register container %s: %s", c.container.ID, err)
347
+		if err := daemon.Register(c); err != nil {
348
+			logrus.Errorf("Failed to register container %s: %s", c.ID, err)
371 349
 			continue
372 350
 		}
351
+
373 352
 		// get list of containers we need to restart
374
-		if daemon.configStore.AutoRestart && c.container.ShouldRestart() {
375
-			restartContainers[c.container] = make(chan struct{})
353
+		if daemon.configStore.AutoRestart && c.ShouldRestart() {
354
+			restartContainers[c] = make(chan struct{})
355
+		}
356
+	}
357
+
358
+	// Now that all the containers are registered, register the links
359
+	for _, c := range containers {
360
+		if err := daemon.registerLinks(c, c.HostConfig); err != nil {
361
+			logrus.Errorf("failed to register link for container %s: %v", c.ID, err)
376 362
 		}
377 363
 	}
378 364
 
379 365
 	group := sync.WaitGroup{}
380 366
 	for c, notifier := range restartContainers {
381 367
 		group.Add(1)
382
-		go func(container *container.Container, chNotify chan struct{}) {
368
+
369
+		go func(c *container.Container, chNotify chan struct{}) {
383 370
 			defer group.Done()
384
-			logrus.Debugf("Starting container %s", container.ID)
371
+
372
+			logrus.Debugf("Starting container %s", c.ID)
385 373
 
386 374
 			// ignore errors here as this is a best effort to wait for children to be
387 375
 			//   running before we try to start the container
388
-			children, err := daemon.children(container.Name)
389
-			if err != nil {
390
-				logrus.Warnf("error getting children for %s: %v", container.Name, err)
391
-			}
376
+			children := daemon.children(c)
392 377
 			timeout := time.After(5 * time.Second)
393 378
 			for _, child := range children {
394 379
 				if notifier, exists := restartContainers[child]; exists {
... ...
@@ -398,11 +376,12 @@ func (daemon *Daemon) restore() error {
398 398
 					}
399 399
 				}
400 400
 			}
401
-			if err := daemon.containerStart(container); err != nil {
402
-				logrus.Errorf("Failed to start container %s: %s", container.ID, err)
401
+			if err := daemon.containerStart(c); err != nil {
402
+				logrus.Errorf("Failed to start container %s: %s", c.ID, err)
403 403
 			}
404 404
 			close(chNotify)
405 405
 		}(c, notifier)
406
+
406 407
 	}
407 408
 	group.Wait()
408 409
 
... ...
@@ -452,28 +431,28 @@ func (daemon *Daemon) reserveName(id, name string) (string, error) {
452 452
 	if !validContainerNamePattern.MatchString(name) {
453 453
 		return "", fmt.Errorf("Invalid container name (%s), only %s are allowed", name, validContainerNameChars)
454 454
 	}
455
-
456 455
 	if name[0] != '/' {
457 456
 		name = "/" + name
458 457
 	}
459 458
 
460
-	if _, err := daemon.containerGraphDB.Set(name, id); err != nil {
461
-		if !graphdb.IsNonUniqueNameError(err) {
462
-			return "", err
463
-		}
464
-
465
-		conflictingContainer, err := daemon.GetByName(name)
466
-		if err != nil {
467
-			return "", err
459
+	if err := daemon.nameIndex.Reserve(name, id); err != nil {
460
+		if err == registrar.ErrNameReserved {
461
+			id, err := daemon.nameIndex.Get(name)
462
+			if err != nil {
463
+				logrus.Errorf("got unexpected error while looking up reserved name: %v", err)
464
+				return "", err
465
+			}
466
+			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)
468 467
 		}
469
-		return "", fmt.Errorf(
470
-			"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, "/"),
471
-			stringid.TruncateID(conflictingContainer.ID))
472
-
468
+		return "", fmt.Errorf("error reserving name: %s, error: %v", name, err)
473 469
 	}
474 470
 	return name, nil
475 471
 }
476 472
 
473
+func (daemon *Daemon) releaseName(name string) {
474
+	daemon.nameIndex.Release(name)
475
+}
476
+
477 477
 func (daemon *Daemon) generateNewName(id string) (string, error) {
478 478
 	var name string
479 479
 	for i := 0; i < 6; i++ {
... ...
@@ -482,17 +461,17 @@ func (daemon *Daemon) generateNewName(id string) (string, error) {
482 482
 			name = "/" + name
483 483
 		}
484 484
 
485
-		if _, err := daemon.containerGraphDB.Set(name, id); err != nil {
486
-			if !graphdb.IsNonUniqueNameError(err) {
487
-				return "", err
485
+		if err := daemon.nameIndex.Reserve(name, id); err != nil {
486
+			if err == registrar.ErrNameReserved {
487
+				continue
488 488
 			}
489
-			continue
489
+			return "", err
490 490
 		}
491 491
 		return name, nil
492 492
 	}
493 493
 
494 494
 	name = "/" + stringid.TruncateID(id)
495
-	if _, err := daemon.containerGraphDB.Set(name, id); err != nil {
495
+	if err := daemon.nameIndex.Reserve(name, id); err != nil {
496 496
 		return "", err
497 497
 	}
498 498
 	return name, nil
... ...
@@ -542,32 +521,19 @@ func (daemon *Daemon) newContainer(name string, config *containertypes.Config, i
542 542
 	return base, err
543 543
 }
544 544
 
545
-// GetFullContainerName returns a constructed container name. I think
546
-// it has to do with the fact that a container is a file on disk and
547
-// this is sort of just creating a file name.
548
-func GetFullContainerName(name string) (string, error) {
549
-	if name == "" {
550
-		return "", fmt.Errorf("Container name cannot be empty")
551
-	}
552
-	if name[0] != '/' {
553
-		name = "/" + name
554
-	}
555
-	return name, nil
556
-}
557
-
558 545
 // GetByName returns a container given a name.
559 546
 func (daemon *Daemon) GetByName(name string) (*container.Container, error) {
560
-	fullName, err := GetFullContainerName(name)
561
-	if err != nil {
562
-		return nil, err
547
+	fullName := name
548
+	if name[0] != '/' {
549
+		fullName = "/" + name
563 550
 	}
564
-	entity := daemon.containerGraphDB.Get(fullName)
565
-	if entity == nil {
551
+	id, err := daemon.nameIndex.Get(fullName)
552
+	if err != nil {
566 553
 		return nil, fmt.Errorf("Could not find entity for %s", name)
567 554
 	}
568
-	e := daemon.containers.Get(entity.ID())
555
+	e := daemon.containers.Get(id)
569 556
 	if e == nil {
570
-		return nil, fmt.Errorf("Could not find container for entity id %s", entity.ID())
557
+		return nil, fmt.Errorf("Could not find container for entity id %s", id)
571 558
 	}
572 559
 	return e, nil
573 560
 }
... ...
@@ -584,48 +550,37 @@ func (daemon *Daemon) UnsubscribeFromEvents(listener chan interface{}) {
584 584
 	daemon.EventsService.Evict(listener)
585 585
 }
586 586
 
587
-// children returns all child containers of the container with the
588
-// given name. The containers are returned as a map from the container
589
-// name to a pointer to Container.
590
-func (daemon *Daemon) children(name string) (map[string]*container.Container, error) {
591
-	name, err := GetFullContainerName(name)
592
-	if err != nil {
593
-		return nil, err
587
+// GetLabels for a container or image id
588
+func (daemon *Daemon) GetLabels(id string) map[string]string {
589
+	// TODO: TestCase
590
+	container := daemon.containers.Get(id)
591
+	if container != nil {
592
+		return container.Config.Labels
594 593
 	}
595
-	children := make(map[string]*container.Container)
596 594
 
597
-	err = daemon.containerGraphDB.Walk(name, func(p string, e *graphdb.Entity) error {
598
-		c, err := daemon.GetContainer(e.ID())
599
-		if err != nil {
600
-			return err
601
-		}
602
-		children[p] = c
603
-		return nil
604
-	}, 0)
605
-
606
-	if err != nil {
607
-		return nil, err
595
+	img, err := daemon.GetImage(id)
596
+	if err == nil {
597
+		return img.ContainerConfig.Labels
608 598
 	}
609
-	return children, nil
599
+	return nil
600
+}
601
+
602
+func (daemon *Daemon) children(c *container.Container) map[string]*container.Container {
603
+	return daemon.linkIndex.children(c)
610 604
 }
611 605
 
612 606
 // parents returns the names of the parent containers of the container
613 607
 // with the given name.
614
-func (daemon *Daemon) parents(name string) ([]string, error) {
615
-	name, err := GetFullContainerName(name)
616
-	if err != nil {
617
-		return nil, err
618
-	}
619
-
620
-	return daemon.containerGraphDB.Parents(name)
608
+func (daemon *Daemon) parents(c *container.Container) map[string]*container.Container {
609
+	return daemon.linkIndex.parents(c)
621 610
 }
622 611
 
623 612
 func (daemon *Daemon) registerLink(parent, child *container.Container, alias string) error {
624
-	fullName := filepath.Join(parent.Name, alias)
625
-	if !daemon.containerGraphDB.Exists(fullName) {
626
-		_, err := daemon.containerGraphDB.Set(fullName, child.ID)
613
+	fullName := path.Join(parent.Name, alias)
614
+	if err := daemon.nameIndex.Reserve(fullName, child.ID); err != nil {
627 615
 		return err
628 616
 	}
617
+	daemon.linkIndex.link(parent, child, fullName)
629 618
 	return nil
630 619
 }
631 620
 
... ...
@@ -813,14 +768,6 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
813 813
 		return nil, fmt.Errorf("Error initializing network controller: %v", err)
814 814
 	}
815 815
 
816
-	graphdbPath := filepath.Join(config.Root, "linkgraph.db")
817
-	graph, err := graphdb.NewSqliteConn(graphdbPath)
818
-	if err != nil {
819
-		return nil, err
820
-	}
821
-
822
-	d.containerGraphDB = graph
823
-
824 816
 	sysInfo := sysinfo.New(false)
825 817
 	// Check if Devices cgroup is mounted, it is hard requirement for container security,
826 818
 	// on Linux/FreeBSD.
... ...
@@ -852,10 +799,12 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
852 852
 	d.uidMaps = uidMaps
853 853
 	d.gidMaps = gidMaps
854 854
 
855
+	d.nameIndex = registrar.NewRegistrar()
856
+	d.linkIndex = newLinkIndex()
857
+
855 858
 	if err := d.cleanupMounts(); err != nil {
856 859
 		return nil, err
857 860
 	}
858
-
859 861
 	go d.execCommandGC()
860 862
 
861 863
 	if err := d.restore(); err != nil {
... ...
@@ -933,12 +882,6 @@ func (daemon *Daemon) Shutdown() error {
933 933
 		daemon.netController.Stop()
934 934
 	}
935 935
 
936
-	if daemon.containerGraphDB != nil {
937
-		if err := daemon.containerGraphDB.Close(); err != nil {
938
-			logrus.Errorf("Error during container graph.Close(): %v", err)
939
-		}
940
-	}
941
-
942 936
 	if daemon.layerStore != nil {
943 937
 		if err := daemon.layerStore.Cleanup(); err != nil {
944 938
 			logrus.Errorf("Error during layer Store.Cleanup(): %v", err)
... ...
@@ -1340,10 +1283,6 @@ func (daemon *Daemon) ExecutionDriver() execdriver.Driver {
1340 1340
 	return daemon.execDriver
1341 1341
 }
1342 1342
 
1343
-func (daemon *Daemon) containerGraph() *graphdb.Database {
1344
-	return daemon.containerGraphDB
1345
-}
1346
-
1347 1343
 // GetUIDGIDMaps returns the current daemon's user namespace settings
1348 1344
 // for the full uid and gid maps which will be applied to containers
1349 1345
 // started in this instance.
... ...
@@ -1421,8 +1360,7 @@ func (daemon *Daemon) setHostConfig(container *container.Container, hostConfig *
1421 1421
 	}
1422 1422
 
1423 1423
 	container.HostConfig = hostConfig
1424
-	container.ToDisk()
1425
-	return nil
1424
+	return container.ToDisk()
1426 1425
 }
1427 1426
 
1428 1427
 func (daemon *Daemon) setupInitLayer(initPath string) error {
... ...
@@ -3,12 +3,11 @@ package daemon
3 3
 import (
4 4
 	"io/ioutil"
5 5
 	"os"
6
-	"path"
7 6
 	"path/filepath"
8 7
 	"testing"
9 8
 
10 9
 	"github.com/docker/docker/container"
11
-	"github.com/docker/docker/pkg/graphdb"
10
+	"github.com/docker/docker/pkg/registrar"
12 11
 	"github.com/docker/docker/pkg/truncindex"
13 12
 	"github.com/docker/docker/volume"
14 13
 	volumedrivers "github.com/docker/docker/volume/drivers"
... ...
@@ -75,23 +74,18 @@ func TestGetContainer(t *testing.T) {
75 75
 	index.Add(c4.ID)
76 76
 	index.Add(c5.ID)
77 77
 
78
-	daemonTestDbPath := path.Join(os.TempDir(), "daemon_test.db")
79
-	graph, err := graphdb.NewSqliteConn(daemonTestDbPath)
80
-	if err != nil {
81
-		t.Fatalf("Failed to create daemon test sqlite database at %s", daemonTestDbPath)
82
-	}
83
-	graph.Set(c1.Name, c1.ID)
84
-	graph.Set(c2.Name, c2.ID)
85
-	graph.Set(c3.Name, c3.ID)
86
-	graph.Set(c4.Name, c4.ID)
87
-	graph.Set(c5.Name, c5.ID)
88
-
89 78
 	daemon := &Daemon{
90
-		containers:       store,
91
-		idIndex:          index,
92
-		containerGraphDB: graph,
79
+		containers: store,
80
+		idIndex:    index,
81
+		nameIndex:  registrar.NewRegistrar(),
93 82
 	}
94 83
 
84
+	daemon.reserveName(c1.ID, c1.Name)
85
+	daemon.reserveName(c2.ID, c2.Name)
86
+	daemon.reserveName(c3.ID, c3.Name)
87
+	daemon.reserveName(c4.ID, c4.Name)
88
+	daemon.reserveName(c5.ID, c5.Name)
89
+
95 90
 	if container, _ := daemon.GetContainer("3cdbd1aa394fd68559fd1441d6eff2ab7c1e6363582c82febfaa8045df3bd8de"); container != c2 {
96 91
 		t.Fatal("Should explicitly match full container IDs")
97 92
 	}
... ...
@@ -120,8 +114,6 @@ func TestGetContainer(t *testing.T) {
120 120
 	if _, err := daemon.GetContainer("nothing"); err == nil {
121 121
 		t.Fatal("Should return an error when provided a prefix that is neither a name or a partial match to an ID")
122 122
 	}
123
-
124
-	os.Remove(daemonTestDbPath)
125 123
 }
126 124
 
127 125
 func initDaemonWithVolumeStore(tmp string) (*Daemon, error) {
... ...
@@ -206,19 +198,6 @@ func TestNetworkOptions(t *testing.T) {
206 206
 	}
207 207
 }
208 208
 
209
-func TestGetFullName(t *testing.T) {
210
-	name, err := GetFullContainerName("testing")
211
-	if err != nil {
212
-		t.Fatal(err)
213
-	}
214
-	if name != "/testing" {
215
-		t.Fatalf("Expected /testing got %s", name)
216
-	}
217
-	if _, err := GetFullContainerName(""); err == nil {
218
-		t.Fatal("Error should not be nil")
219
-	}
220
-}
221
-
222 209
 func TestValidContainerNames(t *testing.T) {
223 210
 	invalidNames := []string{"-rm", "&sdfsfd", "safd%sd"}
224 211
 	validNames := []string{"word-word", "word_word", "1weoid"}
... ...
@@ -676,7 +676,7 @@ func setupInitLayer(initLayer string, rootUID, rootGID int) error {
676 676
 
677 677
 // registerLinks writes the links to a file.
678 678
 func (daemon *Daemon) registerLinks(container *container.Container, hostConfig *containertypes.HostConfig) error {
679
-	if hostConfig == nil || hostConfig.Links == nil {
679
+	if hostConfig == nil {
680 680
 		return nil
681 681
 	}
682 682
 
... ...
@@ -707,12 +707,7 @@ func (daemon *Daemon) registerLinks(container *container.Container, hostConfig *
707 707
 
708 708
 	// After we load all the links into the daemon
709 709
 	// set them to nil on the hostconfig
710
-	hostConfig.Links = nil
711
-	if err := container.WriteHostConfig(); err != nil {
712
-		return err
713
-	}
714
-
715
-	return nil
710
+	return container.WriteHostConfig()
716 711
 }
717 712
 
718 713
 // conditionalMountOnStart is a platform specific helper function during the
... ...
@@ -1,8 +1,10 @@
1 1
 package daemon
2 2
 
3 3
 import (
4
+	"fmt"
4 5
 	"os"
5 6
 	"path"
7
+	"strings"
6 8
 
7 9
 	"github.com/Sirupsen/logrus"
8 10
 	"github.com/docker/docker/container"
... ...
@@ -38,11 +40,10 @@ func (daemon *Daemon) ContainerRm(name string, config *types.ContainerRmConfig)
38 38
 	}
39 39
 
40 40
 	if config.RemoveLink {
41
-		return daemon.rmLink(name)
41
+		return daemon.rmLink(container, name)
42 42
 	}
43 43
 
44 44
 	if err := daemon.cleanupContainer(container, config.ForceRemove); err != nil {
45
-		// return derr.ErrorCodeCantDestroy.WithArgs(name, utils.GetErrorMessage(err))
46 45
 		return err
47 46
 	}
48 47
 
... ...
@@ -53,32 +54,29 @@ func (daemon *Daemon) ContainerRm(name string, config *types.ContainerRmConfig)
53 53
 	return nil
54 54
 }
55 55
 
56
-// rmLink removes link by name from other containers
57
-func (daemon *Daemon) rmLink(name string) error {
58
-	name, err := GetFullContainerName(name)
59
-	if err != nil {
60
-		return err
56
+func (daemon *Daemon) rmLink(container *container.Container, name string) error {
57
+	if name[0] != '/' {
58
+		name = "/" + name
61 59
 	}
62 60
 	parent, n := path.Split(name)
63 61
 	if parent == "/" {
64
-		return derr.ErrorCodeDefaultName
65
-	}
66
-	pe := daemon.containerGraph().Get(parent)
67
-	if pe == nil {
68
-		return derr.ErrorCodeNoParent.WithArgs(parent, name)
62
+		return fmt.Errorf("Conflict, cannot remove the default name of the container")
69 63
 	}
70 64
 
71
-	if err := daemon.containerGraph().Delete(name); err != nil {
72
-		return err
65
+	parent = strings.TrimSuffix(parent, "/")
66
+	pe, err := daemon.nameIndex.Get(parent)
67
+	if err != nil {
68
+		return fmt.Errorf("Cannot get parent %s for name %s", parent, name)
73 69
 	}
74 70
 
75
-	parentContainer, _ := daemon.GetContainer(pe.ID())
71
+	daemon.releaseName(name)
72
+	parentContainer, _ := daemon.GetContainer(pe)
76 73
 	if parentContainer != nil {
74
+		daemon.linkIndex.unlink(name, container, parentContainer)
77 75
 		if err := daemon.updateNetwork(parentContainer); err != nil {
78 76
 			logrus.Debugf("Could not update network to remove link %s: %v", n, err)
79 77
 		}
80 78
 	}
81
-
82 79
 	return nil
83 80
 }
84 81
 
... ...
@@ -116,9 +114,8 @@ func (daemon *Daemon) cleanupContainer(container *container.Container, forceRemo
116 116
 	// indexes even if removal failed.
117 117
 	defer func() {
118 118
 		if err == nil || forceRemove {
119
-			if _, err := daemon.containerGraphDB.Purge(container.ID); err != nil {
120
-				logrus.Debugf("Unable to remove container from link graph: %s", err)
121
-			}
119
+			daemon.nameIndex.Delete(container.ID)
120
+			daemon.linkIndex.delete(container)
122 121
 			selinuxFreeLxcContexts(container.ProcessLabel)
123 122
 			daemon.idIndex.Delete(container.ID)
124 123
 			daemon.containers.Delete(container.ID)
... ...
@@ -139,7 +136,6 @@ func (daemon *Daemon) cleanupContainer(container *container.Container, forceRemo
139 139
 	if err = daemon.execDriver.Clean(container.ID); err != nil {
140 140
 		return derr.ErrorCodeRmExecDriver.WithArgs(container.ID, err)
141 141
 	}
142
-
143 142
 	return nil
144 143
 }
145 144
 
... ...
@@ -102,11 +102,12 @@ func (daemon *Daemon) getInspectData(container *container.Container, size bool)
102 102
 	// make a copy to play with
103 103
 	hostConfig := *container.HostConfig
104 104
 
105
-	if children, err := daemon.children(container.Name); err == nil {
106
-		for linkAlias, child := range children {
107
-			hostConfig.Links = append(hostConfig.Links, fmt.Sprintf("%s:%s", child.Name, linkAlias))
108
-		}
105
+	children := daemon.children(container)
106
+	hostConfig.Links = nil // do not expose the internal structure
107
+	for linkAlias, child := range children {
108
+		hostConfig.Links = append(hostConfig.Links, fmt.Sprintf("%s:%s", child.Name, linkAlias))
109 109
 	}
110
+
110 111
 	// we need this trick to preserve empty log driver, so
111 112
 	// container will use daemon defaults even if daemon change them
112 113
 	if hostConfig.LogConfig.Type == "" {
113 114
new file mode 100644
... ...
@@ -0,0 +1,87 @@
0
+package daemon
1
+
2
+import (
3
+	"sync"
4
+
5
+	"github.com/docker/docker/container"
6
+)
7
+
8
+// linkIndex stores link relationships between containers, including their specified alias
9
+// The alias is the name the parent uses to reference the child
10
+type linkIndex struct {
11
+	// idx maps a parent->alias->child relationship
12
+	idx map[*container.Container]map[string]*container.Container
13
+	// childIdx maps  child->parent->aliases
14
+	childIdx map[*container.Container]map[*container.Container]map[string]struct{}
15
+	mu       sync.Mutex
16
+}
17
+
18
+func newLinkIndex() *linkIndex {
19
+	return &linkIndex{
20
+		idx:      make(map[*container.Container]map[string]*container.Container),
21
+		childIdx: make(map[*container.Container]map[*container.Container]map[string]struct{}),
22
+	}
23
+}
24
+
25
+// link adds indexes for the passed in parent/child/alias relationships
26
+func (l *linkIndex) link(parent, child *container.Container, alias string) {
27
+	l.mu.Lock()
28
+
29
+	if l.idx[parent] == nil {
30
+		l.idx[parent] = make(map[string]*container.Container)
31
+	}
32
+	l.idx[parent][alias] = child
33
+	if l.childIdx[child] == nil {
34
+		l.childIdx[child] = make(map[*container.Container]map[string]struct{})
35
+	}
36
+	if l.childIdx[child][parent] == nil {
37
+		l.childIdx[child][parent] = make(map[string]struct{})
38
+	}
39
+	l.childIdx[child][parent][alias] = struct{}{}
40
+
41
+	l.mu.Unlock()
42
+}
43
+
44
+// unlink removes the requested alias for the given parent/child
45
+func (l *linkIndex) unlink(alias string, child, parent *container.Container) {
46
+	l.mu.Lock()
47
+	delete(l.idx[parent], alias)
48
+	delete(l.childIdx[child], parent)
49
+	l.mu.Unlock()
50
+}
51
+
52
+// children maps all the aliases-> children for the passed in parent
53
+// aliases here are the aliases the parent uses to refer to the child
54
+func (l *linkIndex) children(parent *container.Container) map[string]*container.Container {
55
+	l.mu.Lock()
56
+	children := l.idx[parent]
57
+	l.mu.Unlock()
58
+	return children
59
+}
60
+
61
+// parents maps all the aliases->parent for the passed in child
62
+// aliases here are the aliases the parents use to refer to the child
63
+func (l *linkIndex) parents(child *container.Container) map[string]*container.Container {
64
+	l.mu.Lock()
65
+
66
+	parents := make(map[string]*container.Container)
67
+	for parent, aliases := range l.childIdx[child] {
68
+		for alias := range aliases {
69
+			parents[alias] = parent
70
+		}
71
+	}
72
+
73
+	l.mu.Unlock()
74
+	return parents
75
+}
76
+
77
+// delete deletes all link relationships referencing this container
78
+func (l *linkIndex) delete(container *container.Container) {
79
+	l.mu.Lock()
80
+	for _, child := range l.idx[container] {
81
+		delete(l.childIdx[child], container)
82
+	}
83
+	delete(l.idx, container)
84
+	delete(l.childIdx, container)
85
+	l.mu.Unlock()
86
+}
... ...
@@ -9,7 +9,6 @@ import (
9 9
 	"github.com/Sirupsen/logrus"
10 10
 	"github.com/docker/docker/container"
11 11
 	"github.com/docker/docker/image"
12
-	"github.com/docker/docker/pkg/graphdb"
13 12
 	"github.com/docker/engine-api/types"
14 13
 	"github.com/docker/engine-api/types/filters"
15 14
 	"github.com/docker/go-connections/nat"
... ...
@@ -197,12 +196,6 @@ func (daemon *Daemon) foldFilter(config *ContainersConfig) (*listContext, error)
197 197
 		})
198 198
 	}
199 199
 
200
-	names := make(map[string][]string)
201
-	daemon.containerGraph().Walk("/", func(p string, e *graphdb.Entity) error {
202
-		names[e.ID()] = append(names[e.ID()], p)
203
-		return nil
204
-	}, 1)
205
-
206 200
 	if config.Before != "" && beforeContFilter == nil {
207 201
 		beforeContFilter, err = daemon.GetContainer(config.Before)
208 202
 		if err != nil {
... ...
@@ -220,12 +213,12 @@ func (daemon *Daemon) foldFilter(config *ContainersConfig) (*listContext, error)
220 220
 	return &listContext{
221 221
 		filters:          psFilters,
222 222
 		ancestorFilter:   ancestorFilter,
223
-		names:            names,
224 223
 		images:           imagesFilter,
225 224
 		exitAllowed:      filtExited,
226 225
 		beforeFilter:     beforeContFilter,
227 226
 		sinceFilter:      sinceContFilter,
228 227
 		ContainersConfig: config,
228
+		names:            daemon.nameIndex.GetAll(),
229 229
 	}, nil
230 230
 }
231 231
 
... ...
@@ -40,14 +40,11 @@ func (daemon *Daemon) ContainerRename(oldName, newName string) error {
40 40
 		if err != nil {
41 41
 			container.Name = oldName
42 42
 			daemon.reserveName(container.ID, oldName)
43
-			daemon.containerGraphDB.Delete(newName)
43
+			daemon.releaseName(newName)
44 44
 		}
45 45
 	}()
46 46
 
47
-	if err = daemon.containerGraphDB.Delete(oldName); err != nil {
48
-		return derr.ErrorCodeRenameDelete.WithArgs(oldName, err)
49
-	}
50
-
47
+	daemon.releaseName(oldName)
51 48
 	if err = container.ToDisk(); err != nil {
52 49
 		return err
53 50
 	}
... ...
@@ -76,7 +73,6 @@ func (daemon *Daemon) ContainerRename(oldName, newName string) error {
76 76
 	if err != nil {
77 77
 		return err
78 78
 	}
79
-
80 79
 	daemon.LogContainerEvent(container, "rename")
81 80
 	return nil
82 81
 }
... ...
@@ -1083,9 +1083,9 @@ func (s *DockerSuite) TestContainerApiDeleteRemoveLinks(c *check.C) {
1083 1083
 	c.Assert(err, checker.IsNil)
1084 1084
 	c.Assert(links, checker.Equals, "[\"/tlink1:/tlink2/tlink1\"]", check.Commentf("expected to have links between containers"))
1085 1085
 
1086
-	status, _, err := sockRequest("DELETE", "/containers/tlink2/tlink1?link=1", nil)
1087
-	c.Assert(err, checker.IsNil)
1088
-	c.Assert(status, checker.Equals, http.StatusNoContent)
1086
+	status, b, err := sockRequest("DELETE", "/containers/tlink2/tlink1?link=1", nil)
1087
+	c.Assert(err, check.IsNil)
1088
+	c.Assert(status, check.Equals, http.StatusNoContent, check.Commentf(string(b)))
1089 1089
 
1090 1090
 	linksPostRm, err := inspectFieldJSON(id2, "HostConfig.Links")
1091 1091
 	c.Assert(err, checker.IsNil)
... ...
@@ -1960,3 +1960,104 @@ func (s *DockerDaemonSuite) TestDaemonCgroupParent(c *check.C) {
1960 1960
 	}
1961 1961
 	c.Assert(found, checker.True, check.Commentf("Cgroup path for container (%s) doesn't found in cgroups file: %s", expectedCgroup, cgroupPaths))
1962 1962
 }
1963
+
1964
+func (s *DockerDaemonSuite) TestDaemonRestartWithLinks(c *check.C) {
1965
+	testRequires(c, DaemonIsLinux) // Windows does not support links
1966
+	err := s.d.StartWithBusybox()
1967
+	c.Assert(err, check.IsNil)
1968
+
1969
+	out, err := s.d.Cmd("run", "-d", "--name=test", "busybox", "top")
1970
+	c.Assert(err, check.IsNil, check.Commentf(out))
1971
+
1972
+	out, err = s.d.Cmd("run", "--name=test2", "--link", "test:abc", "busybox", "sh", "-c", "ping -c 1 -w 1 abc")
1973
+	c.Assert(err, check.IsNil, check.Commentf(out))
1974
+
1975
+	c.Assert(s.d.Restart(), check.IsNil)
1976
+
1977
+	// should fail since test is not running yet
1978
+	out, err = s.d.Cmd("start", "test2")
1979
+	c.Assert(err, check.NotNil, check.Commentf(out))
1980
+
1981
+	out, err = s.d.Cmd("start", "test")
1982
+	c.Assert(err, check.IsNil, check.Commentf(out))
1983
+	out, err = s.d.Cmd("start", "-a", "test2")
1984
+	c.Assert(err, check.IsNil, check.Commentf(out))
1985
+	c.Assert(strings.Contains(out, "1 packets transmitted, 1 packets received"), check.Equals, true, check.Commentf(out))
1986
+}
1987
+
1988
+func (s *DockerDaemonSuite) TestDaemonRestartWithNames(c *check.C) {
1989
+	testRequires(c, DaemonIsLinux) // Windows does not support links
1990
+	err := s.d.StartWithBusybox()
1991
+	c.Assert(err, check.IsNil)
1992
+
1993
+	out, err := s.d.Cmd("create", "--name=test", "busybox")
1994
+	c.Assert(err, check.IsNil, check.Commentf(out))
1995
+
1996
+	out, err = s.d.Cmd("run", "-d", "--name=test2", "busybox", "top")
1997
+	c.Assert(err, check.IsNil, check.Commentf(out))
1998
+	test2ID := strings.TrimSpace(out)
1999
+
2000
+	out, err = s.d.Cmd("run", "-d", "--name=test3", "--link", "test2:abc", "busybox", "top")
2001
+	test3ID := strings.TrimSpace(out)
2002
+
2003
+	c.Assert(s.d.Restart(), check.IsNil)
2004
+
2005
+	out, err = s.d.Cmd("create", "--name=test", "busybox")
2006
+	c.Assert(err, check.NotNil, check.Commentf("expected error trying to create container with duplicate name"))
2007
+	// this one is no longer needed, removing simplifies the remainder of the test
2008
+	out, err = s.d.Cmd("rm", "-f", "test")
2009
+	c.Assert(err, check.IsNil, check.Commentf(out))
2010
+
2011
+	out, err = s.d.Cmd("ps", "-a", "--no-trunc")
2012
+	c.Assert(err, check.IsNil, check.Commentf(out))
2013
+
2014
+	lines := strings.Split(strings.TrimSpace(out), "\n")[1:]
2015
+
2016
+	test2validated := false
2017
+	test3validated := false
2018
+	for _, line := range lines {
2019
+		fields := strings.Fields(line)
2020
+		names := fields[len(fields)-1]
2021
+		switch fields[0] {
2022
+		case test2ID:
2023
+			c.Assert(names, check.Equals, "test2,test3/abc")
2024
+			test2validated = true
2025
+		case test3ID:
2026
+			c.Assert(names, check.Equals, "test3")
2027
+			test3validated = true
2028
+		}
2029
+	}
2030
+
2031
+	c.Assert(test2validated, check.Equals, true)
2032
+	c.Assert(test3validated, check.Equals, true)
2033
+}
2034
+
2035
+// TestRunLinksChanged checks that creating a new container with the same name does not update links
2036
+// this ensures that the old, pre gh#16032 functionality continues on
2037
+func (s *DockerDaemonSuite) TestRunLinksChanged(c *check.C) {
2038
+	testRequires(c, DaemonIsLinux) // Windows does not support links
2039
+	err := s.d.StartWithBusybox()
2040
+	c.Assert(err, check.IsNil)
2041
+
2042
+	out, err := s.d.Cmd("run", "-d", "--name=test", "busybox", "top")
2043
+	c.Assert(err, check.IsNil, check.Commentf(out))
2044
+
2045
+	out, err = s.d.Cmd("run", "--name=test2", "--link=test:abc", "busybox", "sh", "-c", "ping -c 1 abc")
2046
+	c.Assert(err, check.IsNil, check.Commentf(out))
2047
+	c.Assert(out, checker.Contains, "1 packets transmitted, 1 packets received")
2048
+
2049
+	out, err = s.d.Cmd("rm", "-f", "test")
2050
+	c.Assert(err, check.IsNil, check.Commentf(out))
2051
+
2052
+	out, err = s.d.Cmd("run", "-d", "--name=test", "busybox", "top")
2053
+	c.Assert(err, check.IsNil, check.Commentf(out))
2054
+	out, err = s.d.Cmd("start", "-a", "test2")
2055
+	c.Assert(err, check.NotNil, check.Commentf(out))
2056
+	c.Assert(out, check.Not(checker.Contains), "1 packets transmitted, 1 packets received")
2057
+
2058
+	err = s.d.Restart()
2059
+	c.Assert(err, check.IsNil)
2060
+	out, err = s.d.Cmd("start", "-a", "test2")
2061
+	c.Assert(err, check.NotNil, check.Commentf(out))
2062
+	c.Assert(out, check.Not(checker.Contains), "1 packets transmitted, 1 packets received")
2063
+}
1963 2064
new file mode 100644
... ...
@@ -0,0 +1,127 @@
0
+// Package registrar provides name registration. It reserves a name to a given key.
1
+package registrar
2
+
3
+import (
4
+	"errors"
5
+	"sync"
6
+)
7
+
8
+var (
9
+	// ErrNameReserved is an error which is returned when a name is requested to be reserved that already is reserved
10
+	ErrNameReserved = errors.New("name is reserved")
11
+	// ErrNameNotReserved is an error which is returned when trying to find a name that is not reserved
12
+	ErrNameNotReserved = errors.New("name is not reserved")
13
+	// ErrNoSuchKey is returned when trying to find the names for a key which is not known
14
+	ErrNoSuchKey = errors.New("provided key does not exist")
15
+)
16
+
17
+// Registrar stores indexes a list of keys and their registered names as well as indexes names and the key that they are registred to
18
+// Names must be unique.
19
+// Registrar is safe for concurrent access.
20
+type Registrar struct {
21
+	idx   map[string][]string
22
+	names map[string]string
23
+	mu    sync.Mutex
24
+}
25
+
26
+// NewRegistrar creates a new Registrar with the an empty index
27
+func NewRegistrar() *Registrar {
28
+	return &Registrar{
29
+		idx:   make(map[string][]string),
30
+		names: make(map[string]string),
31
+	}
32
+}
33
+
34
+// Reserve registers a key to a name
35
+// Reserve is idempotent
36
+// Attempting to reserve a key to a name that already exists results in an `ErrNameReserved`
37
+// A name reservation is globally unique
38
+func (r *Registrar) Reserve(name, key string) error {
39
+	r.mu.Lock()
40
+	defer r.mu.Unlock()
41
+
42
+	if k, exists := r.names[name]; exists {
43
+		if k != key {
44
+			return ErrNameReserved
45
+		}
46
+		return nil
47
+	}
48
+
49
+	r.idx[key] = append(r.idx[key], name)
50
+	r.names[name] = key
51
+	return nil
52
+}
53
+
54
+// Release releases the reserved name
55
+// Once released, a name can be reserved again
56
+func (r *Registrar) Release(name string) {
57
+	r.mu.Lock()
58
+	defer r.mu.Unlock()
59
+
60
+	key, exists := r.names[name]
61
+	if !exists {
62
+		return
63
+	}
64
+
65
+	for i, n := range r.idx[key] {
66
+		if n != name {
67
+			continue
68
+		}
69
+		r.idx[key] = append(r.idx[key][:i], r.idx[key][i+1:]...)
70
+		break
71
+	}
72
+
73
+	delete(r.names, name)
74
+
75
+	if len(r.idx[key]) == 0 {
76
+		delete(r.idx, key)
77
+	}
78
+}
79
+
80
+// Delete removes all reservations for the passed in key.
81
+// All names reserved to this key are released.
82
+func (r *Registrar) Delete(key string) {
83
+	r.mu.Lock()
84
+	for _, name := range r.idx[key] {
85
+		delete(r.names, name)
86
+	}
87
+	delete(r.idx, key)
88
+	r.mu.Unlock()
89
+}
90
+
91
+// GetNames lists all the reserved names for the given key
92
+func (r *Registrar) GetNames(key string) ([]string, error) {
93
+	r.mu.Lock()
94
+	defer r.mu.Unlock()
95
+
96
+	names, exists := r.idx[key]
97
+	if !exists {
98
+		return nil, ErrNoSuchKey
99
+	}
100
+	return names, nil
101
+}
102
+
103
+// Get returns the key that the passed in name is reserved to
104
+func (r *Registrar) Get(name string) (string, error) {
105
+	r.mu.Lock()
106
+	key, exists := r.names[name]
107
+	r.mu.Unlock()
108
+
109
+	if !exists {
110
+		return "", ErrNameNotReserved
111
+	}
112
+	return key, nil
113
+}
114
+
115
+// GetAll returns all registered names
116
+func (r *Registrar) GetAll() map[string][]string {
117
+	out := make(map[string][]string)
118
+
119
+	r.mu.Lock()
120
+	// copy index into out
121
+	for id, names := range r.idx {
122
+		out[id] = names
123
+	}
124
+	r.mu.Unlock()
125
+	return out
126
+}
0 127
new file mode 100644
... ...
@@ -0,0 +1,119 @@
0
+package registrar
1
+
2
+import (
3
+	"reflect"
4
+	"testing"
5
+)
6
+
7
+func TestReserve(t *testing.T) {
8
+	r := NewRegistrar()
9
+
10
+	obj := "test1"
11
+	if err := r.Reserve("test", obj); err != nil {
12
+		t.Fatal(err)
13
+	}
14
+
15
+	if err := r.Reserve("test", obj); err != nil {
16
+		t.Fatal(err)
17
+	}
18
+
19
+	obj2 := "test2"
20
+	err := r.Reserve("test", obj2)
21
+	if err == nil {
22
+		t.Fatalf("expected error when reserving an already reserved name to another object")
23
+	}
24
+	if err != ErrNameReserved {
25
+		t.Fatal("expected `ErrNameReserved` error when attempting to reserve an already reserved name")
26
+	}
27
+}
28
+
29
+func TestRelease(t *testing.T) {
30
+	r := NewRegistrar()
31
+	obj := "testing"
32
+
33
+	if err := r.Reserve("test", obj); err != nil {
34
+		t.Fatal(err)
35
+	}
36
+	r.Release("test")
37
+	r.Release("test") // Ensure there is no panic here
38
+
39
+	if err := r.Reserve("test", obj); err != nil {
40
+		t.Fatal(err)
41
+	}
42
+}
43
+
44
+func TestGetNames(t *testing.T) {
45
+	r := NewRegistrar()
46
+	obj := "testing"
47
+	names := []string{"test1", "test2"}
48
+
49
+	for _, name := range names {
50
+		if err := r.Reserve(name, obj); err != nil {
51
+			t.Fatal(err)
52
+		}
53
+	}
54
+	r.Reserve("test3", "other")
55
+
56
+	names2, err := r.GetNames(obj)
57
+	if err != nil {
58
+		t.Fatal(err)
59
+	}
60
+
61
+	if !reflect.DeepEqual(names, names2) {
62
+		t.Fatalf("Exepected: %v, Got: %v", names, names2)
63
+	}
64
+}
65
+
66
+func TestDelete(t *testing.T) {
67
+	r := NewRegistrar()
68
+	obj := "testing"
69
+	names := []string{"test1", "test2"}
70
+	for _, name := range names {
71
+		if err := r.Reserve(name, obj); err != nil {
72
+			t.Fatal(err)
73
+		}
74
+	}
75
+
76
+	r.Reserve("test3", "other")
77
+	r.Delete(obj)
78
+
79
+	_, err := r.GetNames(obj)
80
+	if err == nil {
81
+		t.Fatal("expected error getting names for deleted key")
82
+	}
83
+
84
+	if err != ErrNoSuchKey {
85
+		t.Fatal("expected `ErrNoSuchKey`")
86
+	}
87
+}
88
+
89
+func TestGet(t *testing.T) {
90
+	r := NewRegistrar()
91
+	obj := "testing"
92
+	name := "test"
93
+
94
+	_, err := r.Get(name)
95
+	if err == nil {
96
+		t.Fatal("expected error when key does not exist")
97
+	}
98
+	if err != ErrNameNotReserved {
99
+		t.Fatal(err)
100
+	}
101
+
102
+	if err := r.Reserve(name, obj); err != nil {
103
+		t.Fatal(err)
104
+	}
105
+
106
+	if _, err = r.Get(name); err != nil {
107
+		t.Fatal(err)
108
+	}
109
+
110
+	r.Delete(obj)
111
+	_, err = r.Get(name)
112
+	if err == nil {
113
+		t.Fatal("expected error when key does not exist")
114
+	}
115
+	if err != ErrNameNotReserved {
116
+		t.Fatal(err)
117
+	}
118
+}