Browse code

Extract container store from the daemon.

- Generalize in an interface.
- Stop abusing of List for everything.

Signed-off-by: David Calavera <david.calavera@gmail.com>

David Calavera authored on 2016/01/16 08:55:46
Showing 11 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,35 @@
0
+package container
1
+
2
+import "sort"
3
+
4
+// History is a convenience type for storing a list of containers,
5
+// sorted by creation date in descendant order.
6
+type History []*Container
7
+
8
+// Len returns the number of containers in the history.
9
+func (history *History) Len() int {
10
+	return len(*history)
11
+}
12
+
13
+// Less compares two containers and returns true if the second one
14
+// was created before the first one.
15
+func (history *History) Less(i, j int) bool {
16
+	containers := *history
17
+	return containers[j].Created.Before(containers[i].Created)
18
+}
19
+
20
+// Swap switches containers i and j positions in the history.
21
+func (history *History) Swap(i, j int) {
22
+	containers := *history
23
+	containers[i], containers[j] = containers[j], containers[i]
24
+}
25
+
26
+// Add the given container to history.
27
+func (history *History) Add(container *Container) {
28
+	*history = append(*history, container)
29
+}
30
+
31
+// sort orders the history by creation date in descendant order.
32
+func (history *History) sort() {
33
+	sort.Sort(history)
34
+}
0 35
new file mode 100644
... ...
@@ -0,0 +1,91 @@
0
+package container
1
+
2
+import "sync"
3
+
4
+// memoryStore implements a Store in memory.
5
+type memoryStore struct {
6
+	s map[string]*Container
7
+	sync.Mutex
8
+}
9
+
10
+// NewMemoryStore initializes a new memory store.
11
+func NewMemoryStore() Store {
12
+	return &memoryStore{
13
+		s: make(map[string]*Container),
14
+	}
15
+}
16
+
17
+// Add appends a new container to the memory store.
18
+// It overrides the id if it existed before.
19
+func (c *memoryStore) Add(id string, cont *Container) {
20
+	c.Lock()
21
+	c.s[id] = cont
22
+	c.Unlock()
23
+}
24
+
25
+// Get returns a container from the store by id.
26
+func (c *memoryStore) Get(id string) *Container {
27
+	c.Lock()
28
+	res := c.s[id]
29
+	c.Unlock()
30
+	return res
31
+}
32
+
33
+// Delete removes a container from the store by id.
34
+func (c *memoryStore) Delete(id string) {
35
+	c.Lock()
36
+	delete(c.s, id)
37
+	c.Unlock()
38
+}
39
+
40
+// List returns a sorted list of containers from the store.
41
+// The containers are ordered by creation date.
42
+func (c *memoryStore) List() []*Container {
43
+	containers := new(History)
44
+	c.Lock()
45
+	for _, cont := range c.s {
46
+		containers.Add(cont)
47
+	}
48
+	c.Unlock()
49
+	containers.sort()
50
+	return *containers
51
+}
52
+
53
+// Size returns the number of containers in the store.
54
+func (c *memoryStore) Size() int {
55
+	c.Lock()
56
+	defer c.Unlock()
57
+	return len(c.s)
58
+}
59
+
60
+// First returns the first container found in the store by a given filter.
61
+func (c *memoryStore) First(filter StoreFilter) *Container {
62
+	c.Lock()
63
+	defer c.Unlock()
64
+	for _, cont := range c.s {
65
+		if filter(cont) {
66
+			return cont
67
+		}
68
+	}
69
+	return nil
70
+}
71
+
72
+// ApplyAll calls the reducer function with every container in the store.
73
+// This operation is asyncronous in the memory store.
74
+func (c *memoryStore) ApplyAll(apply StoreReducer) {
75
+	c.Lock()
76
+	defer c.Unlock()
77
+
78
+	wg := new(sync.WaitGroup)
79
+	for _, cont := range c.s {
80
+		wg.Add(1)
81
+		go func(container *Container) {
82
+			apply(container)
83
+			wg.Done()
84
+		}(cont)
85
+	}
86
+
87
+	wg.Wait()
88
+}
89
+
90
+var _ Store = &memoryStore{}
0 91
new file mode 100644
... ...
@@ -0,0 +1,106 @@
0
+package container
1
+
2
+import (
3
+	"testing"
4
+	"time"
5
+)
6
+
7
+func TestNewMemoryStore(t *testing.T) {
8
+	s := NewMemoryStore()
9
+	m, ok := s.(*memoryStore)
10
+	if !ok {
11
+		t.Fatalf("store is not a memory store %v", s)
12
+	}
13
+	if m.s == nil {
14
+		t.Fatal("expected store map to not be nil")
15
+	}
16
+}
17
+
18
+func TestAddContainers(t *testing.T) {
19
+	s := NewMemoryStore()
20
+	s.Add("id", NewBaseContainer("id", "root"))
21
+	if s.Size() != 1 {
22
+		t.Fatalf("expected store size 1, got %v", s.Size())
23
+	}
24
+}
25
+
26
+func TestGetContainer(t *testing.T) {
27
+	s := NewMemoryStore()
28
+	s.Add("id", NewBaseContainer("id", "root"))
29
+	c := s.Get("id")
30
+	if c == nil {
31
+		t.Fatal("expected container to not be nil")
32
+	}
33
+}
34
+
35
+func TestDeleteContainer(t *testing.T) {
36
+	s := NewMemoryStore()
37
+	s.Add("id", NewBaseContainer("id", "root"))
38
+	s.Delete("id")
39
+	if c := s.Get("id"); c != nil {
40
+		t.Fatalf("expected container to be nil after removal, got %v", c)
41
+	}
42
+
43
+	if s.Size() != 0 {
44
+		t.Fatalf("expected store size to be 0, got %v", s.Size())
45
+	}
46
+}
47
+
48
+func TestListContainers(t *testing.T) {
49
+	s := NewMemoryStore()
50
+
51
+	cont := NewBaseContainer("id", "root")
52
+	cont.Created = time.Now()
53
+	cont2 := NewBaseContainer("id2", "root")
54
+	cont2.Created = time.Now().Add(24 * time.Hour)
55
+
56
+	s.Add("id", cont)
57
+	s.Add("id2", cont2)
58
+
59
+	list := s.List()
60
+	if len(list) != 2 {
61
+		t.Fatalf("expected list size 2, got %v", len(list))
62
+	}
63
+	if list[0].ID != "id2" {
64
+		t.Fatalf("expected older container to be first, got %v", list[0].ID)
65
+	}
66
+}
67
+
68
+func TestFirstContainer(t *testing.T) {
69
+	s := NewMemoryStore()
70
+
71
+	s.Add("id", NewBaseContainer("id", "root"))
72
+	s.Add("id2", NewBaseContainer("id2", "root"))
73
+
74
+	first := s.First(func(cont *Container) bool {
75
+		return cont.ID == "id2"
76
+	})
77
+
78
+	if first == nil {
79
+		t.Fatal("expected container to not be nil")
80
+	}
81
+	if first.ID != "id2" {
82
+		t.Fatalf("expected id2, got %v", first)
83
+	}
84
+}
85
+
86
+func TestApplyAllContainer(t *testing.T) {
87
+	s := NewMemoryStore()
88
+
89
+	s.Add("id", NewBaseContainer("id", "root"))
90
+	s.Add("id2", NewBaseContainer("id2", "root"))
91
+
92
+	s.ApplyAll(func(cont *Container) {
93
+		if cont.ID == "id2" {
94
+			cont.ID = "newID"
95
+		}
96
+	})
97
+
98
+	cont := s.Get("id2")
99
+	if cont == nil {
100
+		t.Fatal("expected container to not be nil")
101
+	}
102
+	if cont.ID != "newID" {
103
+		t.Fatalf("expected newID, got %v", cont)
104
+	}
105
+}
0 106
new file mode 100644
... ...
@@ -0,0 +1,28 @@
0
+package container
1
+
2
+// StoreFilter defines a function to filter
3
+// container in the store.
4
+type StoreFilter func(*Container) bool
5
+
6
+// StoreReducer defines a function to
7
+// manipulate containers in the store
8
+type StoreReducer func(*Container)
9
+
10
+// Store defines an interface that
11
+// any container store must implement.
12
+type Store interface {
13
+	// Add appends a new container to the store.
14
+	Add(string, *Container)
15
+	// Get returns a container from the store by the identifier it was stored with.
16
+	Get(string) *Container
17
+	// Delete removes a container from the store by the identifier it was stored with.
18
+	Delete(string)
19
+	// List returns a list of containers from the store.
20
+	List() []*Container
21
+	// Size returns the number of containers in the store.
22
+	Size() int
23
+	// First returns the first container found in the store by a given filter.
24
+	First(StoreFilter) *Container
25
+	// ApplyAll calls the reducer function with every container in the store.
26
+	ApplyAll(StoreReducer)
27
+}
... ...
@@ -99,46 +99,11 @@ func (e ErrImageDoesNotExist) Error() string {
99 99
 	return fmt.Sprintf("no such id: %s", e.RefOrID)
100 100
 }
101 101
 
102
-type contStore struct {
103
-	s map[string]*container.Container
104
-	sync.Mutex
105
-}
106
-
107
-func (c *contStore) Add(id string, cont *container.Container) {
108
-	c.Lock()
109
-	c.s[id] = cont
110
-	c.Unlock()
111
-}
112
-
113
-func (c *contStore) Get(id string) *container.Container {
114
-	c.Lock()
115
-	res := c.s[id]
116
-	c.Unlock()
117
-	return res
118
-}
119
-
120
-func (c *contStore) Delete(id string) {
121
-	c.Lock()
122
-	delete(c.s, id)
123
-	c.Unlock()
124
-}
125
-
126
-func (c *contStore) List() []*container.Container {
127
-	containers := new(History)
128
-	c.Lock()
129
-	for _, cont := range c.s {
130
-		containers.Add(cont)
131
-	}
132
-	c.Unlock()
133
-	containers.sort()
134
-	return *containers
135
-}
136
-
137 102
 // Daemon holds information about the Docker daemon.
138 103
 type Daemon struct {
139 104
 	ID                        string
140 105
 	repository                string
141
-	containers                *contStore
106
+	containers                container.Store
142 107
 	execCommands              *exec.Store
143 108
 	referenceStore            reference.Store
144 109
 	downloadManager           *xfer.LayerDownloadManager
... ...
@@ -794,7 +759,7 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
794 794
 
795 795
 	d.ID = trustKey.PublicKey().KeyID()
796 796
 	d.repository = daemonRepo
797
-	d.containers = &contStore{s: make(map[string]*container.Container)}
797
+	d.containers = container.NewMemoryStore()
798 798
 	d.execCommands = exec.NewStore()
799 799
 	d.referenceStore = referenceStore
800 800
 	d.distributionMetadataStore = distributionMetadataStore
... ...
@@ -873,24 +838,18 @@ func (daemon *Daemon) shutdownContainer(c *container.Container) error {
873 873
 func (daemon *Daemon) Shutdown() error {
874 874
 	daemon.shutdown = true
875 875
 	if daemon.containers != nil {
876
-		group := sync.WaitGroup{}
877 876
 		logrus.Debug("starting clean shutdown of all containers...")
878
-		for _, cont := range daemon.List() {
879
-			if !cont.IsRunning() {
880
-				continue
877
+		daemon.containers.ApplyAll(func(c *container.Container) {
878
+			if !c.IsRunning() {
879
+				return
881 880
 			}
882
-			logrus.Debugf("stopping %s", cont.ID)
883
-			group.Add(1)
884
-			go func(c *container.Container) {
885
-				defer group.Done()
886
-				if err := daemon.shutdownContainer(c); err != nil {
887
-					logrus.Errorf("Stop container error: %v", err)
888
-					return
889
-				}
890
-				logrus.Debugf("container stopped %s", c.ID)
891
-			}(cont)
892
-		}
893
-		group.Wait()
881
+			logrus.Debugf("stopping %s", c.ID)
882
+			if err := daemon.shutdownContainer(c); err != nil {
883
+				logrus.Errorf("Stop container error: %v", err)
884
+				return
885
+			}
886
+			logrus.Debugf("container stopped %s", c.ID)
887
+		})
894 888
 	}
895 889
 
896 890
 	// trigger libnetwork Stop only if it's initialized
... ...
@@ -61,15 +61,12 @@ func TestGetContainer(t *testing.T) {
61 61
 		},
62 62
 	}
63 63
 
64
-	store := &contStore{
65
-		s: map[string]*container.Container{
66
-			c1.ID: c1,
67
-			c2.ID: c2,
68
-			c3.ID: c3,
69
-			c4.ID: c4,
70
-			c5.ID: c5,
71
-		},
72
-	}
64
+	store := container.NewMemoryStore()
65
+	store.Add(c1.ID, c1)
66
+	store.Add(c2.ID, c2)
67
+	store.Add(c3.ID, c3)
68
+	store.Add(c4.ID, c4)
69
+	store.Add(c5.ID, c5)
73 70
 
74 71
 	index := truncindex.NewTruncIndex([]string{})
75 72
 	index.Add(c1.ID)
... ...
@@ -20,7 +20,7 @@ func TestContainerDoubleDelete(t *testing.T) {
20 20
 		repository: tmp,
21 21
 		root:       tmp,
22 22
 	}
23
-	daemon.containers = &contStore{s: make(map[string]*container.Container)}
23
+	daemon.containers = container.NewMemoryStore()
24 24
 
25 25
 	container := &container.Container{
26 26
 		CommonContainer: container.CommonContainer{
27 27
deleted file mode 100644
... ...
@@ -1,34 +0,0 @@
1
-package daemon
2
-
3
-import (
4
-	"sort"
5
-
6
-	"github.com/docker/docker/container"
7
-)
8
-
9
-// History is a convenience type for storing a list of containers,
10
-// ordered by creation date.
11
-type History []*container.Container
12
-
13
-func (history *History) Len() int {
14
-	return len(*history)
15
-}
16
-
17
-func (history *History) Less(i, j int) bool {
18
-	containers := *history
19
-	return containers[j].Created.Before(containers[i].Created)
20
-}
21
-
22
-func (history *History) Swap(i, j int) {
23
-	containers := *history
24
-	containers[i], containers[j] = containers[j], containers[i]
25
-}
26
-
27
-// Add the given container to history.
28
-func (history *History) Add(container *container.Container) {
29
-	*history = append(*history, container)
30
-}
31
-
32
-func (history *History) sort() {
33
-	sort.Sort(history)
34
-}
... ...
@@ -179,13 +179,9 @@ func isImageIDPrefix(imageID, possiblePrefix string) bool {
179 179
 // getContainerUsingImage returns a container that was created using the given
180 180
 // imageID. Returns nil if there is no such container.
181 181
 func (daemon *Daemon) getContainerUsingImage(imageID image.ID) *container.Container {
182
-	for _, container := range daemon.List() {
183
-		if container.ImageID == imageID {
184
-			return container
185
-		}
186
-	}
187
-
188
-	return nil
182
+	return daemon.containers.First(func(c *container.Container) bool {
183
+		return c.ImageID == imageID
184
+	})
189 185
 }
190 186
 
191 187
 // removeImageRef attempts to parse and remove the given image reference from
... ...
@@ -328,19 +324,15 @@ func (daemon *Daemon) checkImageDeleteConflict(imgID image.ID, mask conflictType
328 328
 
329 329
 	if mask&conflictRunningContainer != 0 {
330 330
 		// Check if any running container is using the image.
331
-		for _, container := range daemon.List() {
332
-			if !container.IsRunning() {
333
-				// Skip this until we check for soft conflicts later.
334
-				continue
335
-			}
336
-
337
-			if container.ImageID == imgID {
338
-				return &imageDeleteConflict{
339
-					imgID:   imgID,
340
-					hard:    true,
341
-					used:    true,
342
-					message: fmt.Sprintf("image is being used by running container %s", stringid.TruncateID(container.ID)),
343
-				}
331
+		running := func(c *container.Container) bool {
332
+			return c.IsRunning() && c.ImageID == imgID
333
+		}
334
+		if container := daemon.containers.First(running); container != nil {
335
+			return &imageDeleteConflict{
336
+				imgID:   imgID,
337
+				hard:    true,
338
+				used:    true,
339
+				message: fmt.Sprintf("image is being used by running container %s", stringid.TruncateID(container.ID)),
344 340
 			}
345 341
 		}
346 342
 	}
... ...
@@ -355,18 +347,14 @@ func (daemon *Daemon) checkImageDeleteConflict(imgID image.ID, mask conflictType
355 355
 
356 356
 	if mask&conflictStoppedContainer != 0 {
357 357
 		// Check if any stopped containers reference this image.
358
-		for _, container := range daemon.List() {
359
-			if container.IsRunning() {
360
-				// Skip this as it was checked above in hard conflict conditions.
361
-				continue
362
-			}
363
-
364
-			if container.ImageID == imgID {
365
-				return &imageDeleteConflict{
366
-					imgID:   imgID,
367
-					used:    true,
368
-					message: fmt.Sprintf("image is being used by stopped container %s", stringid.TruncateID(container.ID)),
369
-				}
358
+		stopped := func(c *container.Container) bool {
359
+			return !c.IsRunning() && c.ImageID == imgID
360
+		}
361
+		if container := daemon.containers.First(stopped); container != nil {
362
+			return &imageDeleteConflict{
363
+				imgID:   imgID,
364
+				used:    true,
365
+				message: fmt.Sprintf("image is being used by stopped container %s", stringid.TruncateID(container.ID)),
370 366
 			}
371 367
 		}
372 368
 	}
... ...
@@ -4,9 +4,11 @@ import (
4 4
 	"os"
5 5
 	"runtime"
6 6
 	"strings"
7
+	"sync/atomic"
7 8
 	"time"
8 9
 
9 10
 	"github.com/Sirupsen/logrus"
11
+	"github.com/docker/docker/container"
10 12
 	"github.com/docker/docker/dockerversion"
11 13
 	"github.com/docker/docker/pkg/fileutils"
12 14
 	"github.com/docker/docker/pkg/parsers/kernel"
... ...
@@ -54,24 +56,24 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) {
54 54
 	initPath := utils.DockerInitPath("")
55 55
 	sysInfo := sysinfo.New(true)
56 56
 
57
-	var cRunning, cPaused, cStopped int
58
-	for _, c := range daemon.List() {
57
+	var cRunning, cPaused, cStopped int32
58
+	daemon.containers.ApplyAll(func(c *container.Container) {
59 59
 		switch c.StateString() {
60 60
 		case "paused":
61
-			cPaused++
61
+			atomic.AddInt32(&cPaused, 1)
62 62
 		case "running":
63
-			cRunning++
63
+			atomic.AddInt32(&cRunning, 1)
64 64
 		default:
65
-			cStopped++
65
+			atomic.AddInt32(&cStopped, 1)
66 66
 		}
67
-	}
67
+	})
68 68
 
69 69
 	v := &types.Info{
70 70
 		ID:                 daemon.ID,
71
-		Containers:         len(daemon.List()),
72
-		ContainersRunning:  cRunning,
73
-		ContainersPaused:   cPaused,
74
-		ContainersStopped:  cStopped,
71
+		Containers:         int(cRunning + cPaused + cStopped),
72
+		ContainersRunning:  int(cRunning),
73
+		ContainersPaused:   int(cPaused),
74
+		ContainersStopped:  int(cStopped),
75 75
 		Images:             len(daemon.imageStore.Map()),
76 76
 		Driver:             daemon.GraphDriverName(),
77 77
 		DriverStatus:       daemon.layerStore.DriverStatus(),
... ...
@@ -39,12 +39,9 @@ func TestMigrateLegacySqliteLinks(t *testing.T) {
39 39
 		},
40 40
 	}
41 41
 
42
-	store := &contStore{
43
-		s: map[string]*container.Container{
44
-			c1.ID: c1,
45
-			c2.ID: c2,
46
-		},
47
-	}
42
+	store := container.NewMemoryStore()
43
+	store.Add(c1.ID, c1)
44
+	store.Add(c2.ID, c2)
48 45
 
49 46
 	d := &Daemon{root: tmpDir, containers: store}
50 47
 	db, err := graphdb.NewSqliteConn(filepath.Join(d.root, "linkgraph.db"))