diff --git a/cluster/cluster_test.go b/cluster/cluster_test.go index 4040ee97e4..9367e3d1ba 100644 --- a/cluster/cluster_test.go +++ b/cluster/cluster_test.go @@ -18,6 +18,7 @@ func createNode(t *testing.T, ID string, containers ...dockerclient.Container) * client := mockclient.NewMockClient() client.On("Info").Return(mockInfo, nil) client.On("ListContainers", true, false, "").Return(containers, nil) + client.On("ListImages").Return([]*dockerclient.Image{}, nil) client.On("InspectContainer", mock.Anything).Return( &dockerclient.ContainerInfo{ Config: &dockerclient.ContainerConfig{CpuShares: 100}, diff --git a/cluster/node.go b/cluster/node.go index 7c0cec8dfe..c21bee76d0 100644 --- a/cluster/node.go +++ b/cluster/node.go @@ -46,6 +46,7 @@ type Node struct { ch chan bool containers map[string]*Container + images []*dockerclient.Image client dockerclient.Client eventHandler EventHandler healthy bool @@ -84,6 +85,10 @@ func (n *Node) connectClient(client dockerclient.Client) error { n.client = nil return err } + if err := n.refreshImages(); err != nil { + n.client = nil + return err + } // Start the update loop. go n.refreshLoop() @@ -131,6 +136,18 @@ func (n *Node) updateSpecs() error { return nil } +// Refresh the list of images on the node. +func (n *Node) refreshImages() error { + images, err := n.client.ListImages() + if err != nil { + return err + } + n.Lock() + n.images = images + n.Unlock() + return nil +} + // Refresh the list and status of containers running on the node. func (n *Node) refreshContainers() error { containers, err := n.client.ListContainers(true, false, "") @@ -237,6 +254,10 @@ func (n *Node) refreshLoop() { err = n.refreshContainers() } + if err == nil { + err = n.refreshImages() + } + if err != nil { n.healthy = false log.Errorf("[%s/%s] Flagging node as dead. Updated state failed: %v", n.ID, n.Name, err) @@ -316,23 +337,6 @@ func (n *Node) Create(config *dockerclient.ContainerConfig, name string, pullIma return n.containers[id], nil } -func (n *Node) ListImages() ([]string, error) { - images, err := n.client.ListImages() - if err != nil { - return nil, err - } - - out := []string{} - - for _, i := range images { - for _, t := range i.RepoTags { - out = append(out, t) - } - } - - return out, nil -} - // Destroy and remove a container from the node. func (n *Node) Destroy(container *Container, force bool) error { if err := n.client.RemoveContainer(container.Id, force); err != nil { @@ -374,6 +378,12 @@ func (n *Node) Containers() []*Container { return containers } +func (n *Node) Images() []*dockerclient.Image { + n.RLock() + defer n.RUnlock() + return n.images +} + func (n *Node) String() string { return fmt.Sprintf("node %s addr %s", n.ID, n.Addr) } diff --git a/cluster/node_test.go b/cluster/node_test.go index dbcf55482e..5a38bd1f3b 100644 --- a/cluster/node_test.go +++ b/cluster/node_test.go @@ -58,6 +58,7 @@ func TestNodeCpusMemory(t *testing.T) { client := mockclient.NewMockClient() client.On("Info").Return(mockInfo, nil) client.On("ListContainers", true, false, "").Return([]dockerclient.Container{}, nil) + client.On("ListImages").Return([]*dockerclient.Image{}, nil) client.On("StartMonitorEvents", mock.Anything, mock.Anything).Return() assert.NoError(t, node.connectClient(client)) @@ -77,6 +78,7 @@ func TestNodeSpecs(t *testing.T) { client := mockclient.NewMockClient() client.On("Info").Return(mockInfo, nil) client.On("ListContainers", true, false, "").Return([]dockerclient.Container{}, nil) + client.On("ListImages").Return([]*dockerclient.Image{}, nil) client.On("StartMonitorEvents", mock.Anything, mock.Anything).Return() assert.NoError(t, node.connectClient(client)) @@ -104,6 +106,7 @@ func TestNodeState(t *testing.T) { // The client will return one container at first, then a second one will appear. client.On("ListContainers", true, false, "").Return([]dockerclient.Container{{Id: "one"}}, nil).Once() + client.On("ListImages").Return([]*dockerclient.Image{}, nil).Once() client.On("InspectContainer", "one").Return(&dockerclient.ContainerInfo{Config: &dockerclient.ContainerConfig{CpuShares: 100}}, nil).Once() client.On("ListContainers", true, false, fmt.Sprintf("{%q:[%q]}", "id", "two")).Return([]dockerclient.Container{{Id: "two"}}, nil).Once() client.On("InspectContainer", "two").Return(&dockerclient.ContainerInfo{Config: &dockerclient.ContainerConfig{CpuShares: 100}}, nil).Once() @@ -147,6 +150,7 @@ func TestCreateContainer(t *testing.T) { client.On("Info").Return(mockInfo, nil) client.On("StartMonitorEvents", mock.Anything, mock.Anything).Return() client.On("ListContainers", true, false, "").Return([]dockerclient.Container{}, nil).Once() + client.On("ListImages").Return([]*dockerclient.Image{}, nil).Once() assert.NoError(t, node.connectClient(client)) assert.True(t, node.IsConnected()) @@ -158,6 +162,7 @@ func TestCreateContainer(t *testing.T) { id := "id1" client.On("CreateContainer", &mockConfig, name).Return(id, nil).Once() client.On("ListContainers", true, false, fmt.Sprintf(`{"id":[%q]}`, id)).Return([]dockerclient.Container{{Id: id}}, nil).Once() + client.On("ListImages").Return([]*dockerclient.Image{}, nil).Once() client.On("InspectContainer", id).Return(&dockerclient.ContainerInfo{Config: config}, nil).Once() container, err := node.Create(config, name, false) assert.Nil(t, err) @@ -180,6 +185,7 @@ func TestCreateContainer(t *testing.T) { client.On("CreateContainer", &mockConfig, name).Return("", dockerclient.ErrNotFound).Once() client.On("CreateContainer", &mockConfig, name).Return(id, nil).Once() client.On("ListContainers", true, false, fmt.Sprintf(`{"id":[%q]}`, id)).Return([]dockerclient.Container{{Id: id}}, nil).Once() + client.On("ListImages").Return([]*dockerclient.Image{}, nil).Once() client.On("InspectContainer", id).Return(&dockerclient.ContainerInfo{Config: config}, nil).Once() container, err = node.Create(config, name, true) assert.Nil(t, err) diff --git a/scheduler/filter/affinity.go b/scheduler/filter/affinity.go index 99c023dd44..54f84840c3 100644 --- a/scheduler/filter/affinity.go +++ b/scheduler/filter/affinity.go @@ -21,23 +21,24 @@ func (f *AffinityFilter) Filter(config *dockerclient.ContainerConfig, nodes []*c switch k { case "container": for _, container := range node.Containers() { - // "node" label is a special case pinning a container to a specific node. if match(v, container.Id) || match(v, container.Names[0]) { candidates = append(candidates, node) break } } case "image": - //TODO use cache - images, err := node.ListImages() - if err != nil { - break - } - for _, image := range images { - if match(v, image) { + done: + for _, image := range node.Images() { + if match(v, image.Id) { candidates = append(candidates, node) break } + for _, t := range image.RepoTags { + if match(v, t) { + candidates = append(candidates, node) + break done + } + } } } } diff --git a/scheduler/filter/affinity_test.go b/scheduler/filter/affinity_test.go index 0d6aaebdf7..c127130af5 100644 --- a/scheduler/filter/affinity_test.go +++ b/scheduler/filter/affinity_test.go @@ -14,9 +14,9 @@ func TestAffinityFilter(t *testing.T) { var ( f = AffinityFilter{} nodes = []*cluster.Node{ - cluster.NewNode("node-0"), - cluster.NewNode("node-1"), - cluster.NewNode("node-2"), + cluster.NewNode("node-0", 0), + cluster.NewNode("node-1", 0), + cluster.NewNode("node-2", 0), } result []*cluster.Node err error