mirror of https://github.com/docker/docs.git
Dependency Filter: co-schedule dependent containers on the same node.
Supported dependencies: - Shared volumes: `--volumes-from=dependency` - Links: `--link=dependency:alias` - Shared network stack: `--net=container:dependency` Fixes #251 Signed-off-by: Andrea Luzzardi <aluzzardi@gmail.com>
This commit is contained in:
parent
67e347fa1a
commit
97334b0193
4
flags.go
4
flags.go
|
|
@ -86,12 +86,12 @@ var (
|
|||
}
|
||||
|
||||
// hack for go vet
|
||||
flFilterValue = cli.StringSlice([]string{"constraint", "affinity", "health", "port"})
|
||||
flFilterValue = cli.StringSlice([]string{"constraint", "affinity", "health", "port", "dependency"})
|
||||
DEFAULT_FILTER_NUMBER = len(flFilterValue)
|
||||
|
||||
flFilter = cli.StringSliceFlag{
|
||||
Name: "filter, f",
|
||||
Usage: "filter to use [constraint, affinity, health, port]",
|
||||
Usage: "filter to use [constraint, affinity, health, port, dependency]",
|
||||
Value: &flFilterValue,
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -236,6 +236,25 @@ $ docker run -d -p 80:80 nginx
|
|||
2014/10/29 00:33:20 Error response from daemon: no resources available to schedule container
|
||||
```
|
||||
|
||||
## Dependency Filter
|
||||
|
||||
This filter co-schedules dependent containers on the same node.
|
||||
|
||||
Currently, dependencies are declared as follows:
|
||||
|
||||
- Shared volumes: `--volumes-from=dependency`
|
||||
- Links: `--link=dependency:alias`
|
||||
- Shared network stack: `--net=container:dependency`
|
||||
|
||||
Swarm will attempt to co-locate the dependent container on the same node. If it
|
||||
cannot be done (because the dependent container doesn't exist, or because the
|
||||
node doesn't have enough resources), it will prevent the container creation.
|
||||
|
||||
The combination of multiple dependencies will be honored if possible. For
|
||||
instance, `--volumes-from=A --net=container:B` will attempt to co-locate the
|
||||
container on the same node as `A` and `B`. If those containers are running on
|
||||
different nodes, Swarm will prevent you from scheduling the container.
|
||||
|
||||
## Health Filter
|
||||
|
||||
This filter will prevent scheduling containers on unhealthy nodes.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
package filter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/swarm/cluster"
|
||||
"github.com/samalba/dockerclient"
|
||||
)
|
||||
|
||||
// DependencyFilter co-schedules dependent containers on the same node.
|
||||
type DependencyFilter struct {
|
||||
}
|
||||
|
||||
func (f *DependencyFilter) Filter(config *dockerclient.ContainerConfig, nodes []*cluster.Node) ([]*cluster.Node, error) {
|
||||
if len(nodes) == 0 {
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// Extract containers from links.
|
||||
links := []string{}
|
||||
for _, link := range config.HostConfig.Links {
|
||||
links = append(links, strings.SplitN(link, ":", 2)[0])
|
||||
}
|
||||
|
||||
// Check if --net points to a container.
|
||||
net := []string{}
|
||||
if strings.HasPrefix(config.HostConfig.NetworkMode, "container:") {
|
||||
net = append(net, strings.TrimPrefix(config.HostConfig.NetworkMode, "container:"))
|
||||
}
|
||||
|
||||
candidates := []*cluster.Node{}
|
||||
for _, node := range nodes {
|
||||
if f.check(config.HostConfig.VolumesFrom, node) &&
|
||||
f.check(links, node) &&
|
||||
f.check(net, node) {
|
||||
candidates = append(candidates, node)
|
||||
}
|
||||
}
|
||||
|
||||
if len(candidates) == 0 {
|
||||
return nil, fmt.Errorf("Unable to find a node fulfilling all dependencies: %s", f.String(config))
|
||||
}
|
||||
|
||||
return candidates, nil
|
||||
}
|
||||
|
||||
// Get a string representation of the dependencies found in the container config.
|
||||
func (f *DependencyFilter) String(config *dockerclient.ContainerConfig) string {
|
||||
dependencies := []string{}
|
||||
for _, volume := range config.HostConfig.VolumesFrom {
|
||||
dependencies = append(dependencies, fmt.Sprintf("--volumes-from=%s", volume))
|
||||
}
|
||||
for _, link := range config.HostConfig.Links {
|
||||
dependencies = append(dependencies, fmt.Sprintf("--link=%s", link))
|
||||
}
|
||||
if strings.HasPrefix(config.HostConfig.NetworkMode, "container:") {
|
||||
dependencies = append(dependencies, fmt.Sprintf("--net=%s", config.HostConfig.NetworkMode))
|
||||
}
|
||||
return strings.Join(dependencies, " ")
|
||||
}
|
||||
|
||||
// Ensure that the node contains all dependent containers.
|
||||
func (f *DependencyFilter) check(dependencies []string, node *cluster.Node) bool {
|
||||
for _, dependency := range dependencies {
|
||||
if node.Container(dependency) == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
package filter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/swarm/cluster"
|
||||
"github.com/samalba/dockerclient"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDependencyFilterSimple(t *testing.T) {
|
||||
var (
|
||||
f = DependencyFilter{}
|
||||
nodes = []*cluster.Node{
|
||||
cluster.NewNode("node-1", 0),
|
||||
cluster.NewNode("node-2", 0),
|
||||
cluster.NewNode("node-3", 0),
|
||||
}
|
||||
result []*cluster.Node
|
||||
err error
|
||||
container *cluster.Container
|
||||
config *dockerclient.ContainerConfig
|
||||
)
|
||||
|
||||
container = &cluster.Container{Container: dockerclient.Container{Id: "c0"}}
|
||||
assert.NoError(t, nodes[0].AddContainer(container))
|
||||
|
||||
container = &cluster.Container{Container: dockerclient.Container{Id: "c1"}}
|
||||
assert.NoError(t, nodes[1].AddContainer(container))
|
||||
|
||||
container = &cluster.Container{Container: dockerclient.Container{Id: "c2"}}
|
||||
assert.NoError(t, nodes[2].AddContainer(container))
|
||||
|
||||
// No dependencies - make sure we don't filter anything out.
|
||||
config = &dockerclient.ContainerConfig{}
|
||||
result, err = f.Filter(config, nodes)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, result, nodes)
|
||||
|
||||
// volumes-from.
|
||||
config = &dockerclient.ContainerConfig{
|
||||
HostConfig: dockerclient.HostConfig{
|
||||
VolumesFrom: []string{"c0"},
|
||||
},
|
||||
}
|
||||
result, err = f.Filter(config, nodes)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, result, 1)
|
||||
assert.Equal(t, result[0], nodes[0])
|
||||
|
||||
// link.
|
||||
config = &dockerclient.ContainerConfig{
|
||||
HostConfig: dockerclient.HostConfig{
|
||||
Links: []string{"c1:foobar"},
|
||||
},
|
||||
}
|
||||
result, err = f.Filter(config, nodes)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, result, 1)
|
||||
assert.Equal(t, result[0], nodes[1])
|
||||
|
||||
// net.
|
||||
config = &dockerclient.ContainerConfig{
|
||||
HostConfig: dockerclient.HostConfig{
|
||||
NetworkMode: "container:c2",
|
||||
},
|
||||
}
|
||||
result, err = f.Filter(config, nodes)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, result, 1)
|
||||
assert.Equal(t, result[0], nodes[2])
|
||||
|
||||
// net not prefixed by "container:" should be ignored.
|
||||
config = &dockerclient.ContainerConfig{
|
||||
HostConfig: dockerclient.HostConfig{
|
||||
NetworkMode: "bridge",
|
||||
},
|
||||
}
|
||||
result, err = f.Filter(config, nodes)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, result, nodes)
|
||||
}
|
||||
|
||||
func TestDependencyFilterMulti(t *testing.T) {
|
||||
var (
|
||||
f = DependencyFilter{}
|
||||
nodes = []*cluster.Node{
|
||||
cluster.NewNode("node-1", 0),
|
||||
cluster.NewNode("node-2", 0),
|
||||
cluster.NewNode("node-3", 0),
|
||||
}
|
||||
result []*cluster.Node
|
||||
err error
|
||||
container *cluster.Container
|
||||
config *dockerclient.ContainerConfig
|
||||
)
|
||||
|
||||
// nodes[0] has c0 and c1
|
||||
container = &cluster.Container{Container: dockerclient.Container{Id: "c0"}}
|
||||
assert.NoError(t, nodes[0].AddContainer(container))
|
||||
container = &cluster.Container{Container: dockerclient.Container{Id: "c1"}}
|
||||
assert.NoError(t, nodes[0].AddContainer(container))
|
||||
|
||||
// nodes[1] has c2
|
||||
container = &cluster.Container{Container: dockerclient.Container{Id: "c2"}}
|
||||
assert.NoError(t, nodes[1].AddContainer(container))
|
||||
|
||||
// nodes[2] has nothing
|
||||
|
||||
// Depend on c0 which is on nodes[0]
|
||||
config = &dockerclient.ContainerConfig{
|
||||
HostConfig: dockerclient.HostConfig{
|
||||
VolumesFrom: []string{"c0"},
|
||||
},
|
||||
}
|
||||
result, err = f.Filter(config, nodes)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, result, 1)
|
||||
assert.Equal(t, result[0], nodes[0])
|
||||
|
||||
// Depend on c1 which is on nodes[0]
|
||||
config = &dockerclient.ContainerConfig{
|
||||
HostConfig: dockerclient.HostConfig{
|
||||
VolumesFrom: []string{"c1"},
|
||||
},
|
||||
}
|
||||
result, err = f.Filter(config, nodes)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, result, 1)
|
||||
assert.Equal(t, result[0], nodes[0])
|
||||
|
||||
// Depend on c0 AND c1 which are both on nodes[0]
|
||||
config = &dockerclient.ContainerConfig{
|
||||
HostConfig: dockerclient.HostConfig{
|
||||
VolumesFrom: []string{"c0", "c1"},
|
||||
},
|
||||
}
|
||||
result, err = f.Filter(config, nodes)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, result, 1)
|
||||
assert.Equal(t, result[0], nodes[0])
|
||||
|
||||
// Depend on c0 AND c2 which are on different nodes.
|
||||
config = &dockerclient.ContainerConfig{
|
||||
HostConfig: dockerclient.HostConfig{
|
||||
VolumesFrom: []string{"c0", "c2"},
|
||||
},
|
||||
}
|
||||
result, err = f.Filter(config, nodes)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestDependencyFilterChaining(t *testing.T) {
|
||||
var (
|
||||
f = DependencyFilter{}
|
||||
nodes = []*cluster.Node{
|
||||
cluster.NewNode("node-1", 0),
|
||||
cluster.NewNode("node-2", 0),
|
||||
cluster.NewNode("node-3", 0),
|
||||
}
|
||||
result []*cluster.Node
|
||||
err error
|
||||
container *cluster.Container
|
||||
config *dockerclient.ContainerConfig
|
||||
)
|
||||
|
||||
// nodes[0] has c0 and c1
|
||||
container = &cluster.Container{Container: dockerclient.Container{Id: "c0"}}
|
||||
assert.NoError(t, nodes[0].AddContainer(container))
|
||||
container = &cluster.Container{Container: dockerclient.Container{Id: "c1"}}
|
||||
assert.NoError(t, nodes[0].AddContainer(container))
|
||||
|
||||
// nodes[1] has c2
|
||||
container = &cluster.Container{Container: dockerclient.Container{Id: "c2"}}
|
||||
assert.NoError(t, nodes[1].AddContainer(container))
|
||||
|
||||
// nodes[2] has nothing
|
||||
|
||||
// Different dependencies on c0 and c1
|
||||
config = &dockerclient.ContainerConfig{
|
||||
HostConfig: dockerclient.HostConfig{
|
||||
VolumesFrom: []string{"c0"},
|
||||
Links: []string{"c1"},
|
||||
NetworkMode: "container:c1",
|
||||
},
|
||||
}
|
||||
result, err = f.Filter(config, nodes)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, result, 1)
|
||||
assert.Equal(t, result[0], nodes[0])
|
||||
|
||||
// Different dependencies on c0 and c2
|
||||
config = &dockerclient.ContainerConfig{
|
||||
HostConfig: dockerclient.HostConfig{
|
||||
VolumesFrom: []string{"c0"},
|
||||
Links: []string{"c2"},
|
||||
NetworkMode: "container:c1",
|
||||
},
|
||||
}
|
||||
result, err = f.Filter(config, nodes)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ func init() {
|
|||
"health": &HealthFilter{},
|
||||
"constraint": &ConstraintFilter{},
|
||||
"port": &PortFilter{},
|
||||
"dependency": &DependencyFilter{},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue