Filter for containerslots-label

Signed-off-by: michael.freund <michael.freund@shopgate.com>
This commit is contained in:
michael.freund 2016-04-18 09:58:33 +02:00 committed by Dong Chen
parent e8cb44fe81
commit 0ccc958b50
6 changed files with 311 additions and 1 deletions

View File

@ -25,6 +25,7 @@ Each filter has a name that identifies it. The node filters are:
* `constraint` * `constraint`
* `health` * `health`
* `containerslots`
The container configuration filters are: The container configuration filters are:
@ -48,6 +49,8 @@ $ swarm manage --filter=health --filter=dependency
When creating a container or building an image, you use a `constraint` or When creating a container or building an image, you use a `constraint` or
`health` filter to select a subset of nodes to consider for scheduling. `health` filter to select a subset of nodes to consider for scheduling.
If there are nodes in the swarm that have a label with key as `containerslots`
and a number-value, Swarm will not launch more containers than the given number.
### Use a constraint filter ### Use a constraint filter
@ -175,6 +178,16 @@ The node `health` filter prevents the scheduler form running containers
on unhealthy nodes. A node is considered unhealthy if the node is down or it on unhealthy nodes. A node is considered unhealthy if the node is down or it
can't communicate with the cluster store. can't communicate with the cluster store.
### Use the containerslots filter
You may give your Docker nodes the containerslots label
```bash
$ docker daemon --label containerslots=3
```
Swarm will prevent running more than three containers at this node, if
all nodes are "full", an error is thrown. If the value is not castable
to an integer number or is not present, there will be no limit.
## Container filters ## Container filters
When creating a container, you can use three types of container filters: When creating a container, you can use three types of container filters:

View File

@ -30,6 +30,7 @@ func init() {
filters = []Filter{ filters = []Filter{
&HealthFilter{}, &HealthFilter{},
&PortFilter{}, &PortFilter{},
&SlotsFilter{},
&DependencyFilter{}, &DependencyFilter{},
&AffinityFilter{}, &AffinityFilter{},
&ConstraintFilter{}, &ConstraintFilter{},

51
scheduler/filter/slots.go Normal file
View File

@ -0,0 +1,51 @@
package filter
import (
"errors"
"github.com/docker/swarm/cluster"
"github.com/docker/swarm/scheduler/node"
"strconv"
)
var (
// ErrNoNodeWithFreeSlotsAvailable is exported
ErrNoNodeWithFreeSlotsAvailable = errors.New("No node with enough open slots available in the cluster")
)
//SlotsFilter only schedules containers with open slots.
type SlotsFilter struct {
}
// Name returns the name of the filter
func (f *SlotsFilter) Name() string {
return "containerslots"
}
// Filter is exported
func (f *SlotsFilter) Filter(_ *cluster.ContainerConfig, nodes []*node.Node, _ bool) ([]*node.Node, error) {
result := []*node.Node{}
for _, node := range nodes {
if slotsString, ok := node.Labels["containerslots"]; ok {
slots, err := strconv.Atoi(slotsString) //if err => cannot cast to int, so ignore the label
if err != nil || len(node.Containers) < slots {
result = append(result, node)
}
} else {
//no limit if label is missing
result = append(result, node)
}
}
if len(result) == 0 {
return nil, ErrNoNodeWithFreeSlotsAvailable
}
return result, nil
}
// GetFilters returns just the info that this node failed, because there where no free slots
func (f *SlotsFilter) GetFilters(config *cluster.ContainerConfig) ([]string, error) {
return []string{"free slots"}, nil
}

View File

@ -0,0 +1,167 @@
package filter
import (
"testing"
"github.com/docker/engine-api/types"
"github.com/docker/swarm/cluster"
"github.com/docker/swarm/scheduler/node"
"github.com/stretchr/testify/assert"
)
var labelsWithSlots = make(map[string]string)
var labelsWithoutSlots = make(map[string]string)
var labelsWithStringSlot = make(map[string]string)
func testFixturesAllFreeNode() []*node.Node {
return []*node.Node{
{
ID: "node-0-id",
Name: "node-0-name",
Labels: labelsWithSlots,
Containers: []*cluster.Container{
{Container: types.Container{}},
},
},
{
ID: "node-1-id",
Name: "node-1-name",
Labels: labelsWithSlots,
Containers: []*cluster.Container{},
},
}
}
func testFixturesPartlyFreeNode() []*node.Node {
return []*node.Node{
{
ID: "node-0-id",
Name: "node-0-name",
Labels: labelsWithSlots,
Containers: []*cluster.Container{
{Container: types.Container{}},
{Container: types.Container{}},
{Container: types.Container{}},
},
},
{
ID: "node-1-id",
Name: "node-1-name",
Labels: labelsWithSlots,
Containers: []*cluster.Container{},
},
}
}
func testFixturesAllNoLabelNode() []*node.Node {
return []*node.Node{
{
ID: "node-0-id",
Name: "node-0-name",
Labels: labelsWithoutSlots,
Containers: []*cluster.Container{
{Container: types.Container{}},
{Container: types.Container{}},
{Container: types.Container{}},
},
},
{
ID: "node-1-id",
Name: "node-1-name",
Labels: labelsWithoutSlots,
Containers: []*cluster.Container{},
},
}
}
func testFixturesNoFreeNode() []*node.Node {
return []*node.Node{
{
ID: "node-0-id",
Name: "node-0-name",
Labels: labelsWithSlots,
Containers: []*cluster.Container{
{Container: types.Container{}},
{Container: types.Container{}},
{Container: types.Container{}},
},
},
{
ID: "node-1-id",
Name: "node-1-name",
Labels: labelsWithSlots,
Containers: []*cluster.Container{
{Container: types.Container{}},
{Container: types.Container{}},
{Container: types.Container{}},
},
},
}
}
func testFixturesNoFreeNodeButStringLabel() []*node.Node {
return []*node.Node{
{
ID: "node-0-id",
Name: "node-0-name",
Labels: labelsWithSlots,
Containers: []*cluster.Container{
{Container: types.Container{}},
{Container: types.Container{}},
{Container: types.Container{}},
},
},
{
ID: "node-1-id",
Name: "node-1-name",
Labels: labelsWithStringSlot,
Containers: []*cluster.Container{
{Container: types.Container{}},
{Container: types.Container{}},
{Container: types.Container{}},
},
},
}
}
func TestSlotsFilter(t *testing.T) {
labelsWithSlots["containerslots"] = "3"
labelsWithStringSlot["containerslots"] = "foo"
var (
f = SlotsFilter{}
nodesAllFree = testFixturesAllFreeNode()
nodesPartlyFree = testFixturesPartlyFreeNode()
nodesAllNoLabel = testFixturesAllNoLabelNode()
nodesNoFree = testFixturesNoFreeNode()
nodesNoFreeButStringLabel = testFixturesNoFreeNodeButStringLabel()
result []*node.Node
err error
)
result, err = f.Filter(&cluster.ContainerConfig{}, nodesAllFree, true)
assert.NoError(t, err)
assert.Equal(t, result, nodesAllFree)
result, err = f.Filter(&cluster.ContainerConfig{}, nodesPartlyFree, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodesPartlyFree[1])
result, err = f.Filter(&cluster.ContainerConfig{}, nodesAllNoLabel, true)
assert.NoError(t, err)
assert.Equal(t, result, nodesAllNoLabel)
result, err = f.Filter(&cluster.ContainerConfig{}, nodesNoFree, true)
assert.Equal(t, err, ErrNoNodeWithFreeSlotsAvailable)
assert.Nil(t, result)
result, err = f.Filter(&cluster.ContainerConfig{}, nodesNoFreeButStringLabel, true)
assert.NoError(t, err)
assert.Len(t, result, 1)
assert.Equal(t, result[0], nodesNoFreeButStringLabel[1])
}

View File

@ -18,7 +18,7 @@ function teardown() {
run docker_swarm build --build-arg="constraint:node==node-9" $TESTDATA/build run docker_swarm build --build-arg="constraint:node==node-9" $TESTDATA/build
[ "$status" -eq 1 ] [ "$status" -eq 1 ]
[[ "${lines[1]}" == *"Unable to find a node that satisfies the following conditions"* ]] [[ "${lines[1]}" == *"Unable to find a node that satisfies the following conditions"* ]]
[[ "${lines[2]}" == *"[node==node-9]"* ]] [[ "${lines[3]}" == *"[node==node-9]"* ]]
run docker_swarm images -q run docker_swarm images -q
[ "$status" -eq 0 ] [ "$status" -eq 0 ]

View File

@ -0,0 +1,78 @@
#!/usr/bin/env bats
load helpers
function teardown() {
swarm_manage_cleanup
stop_docker
}
@test "containerslots filter" {
start_docker_with_busybox 2 --label containerslots=2
swarm_manage
# Use busybox to save image pulling time for integration test.
# Running the first 4 containers, it should be fine.
run docker_swarm run -d -t busybox sh
[ "$status" -eq 0 ]
run docker_swarm run -d -t busybox sh
[ "$status" -eq 0 ]
run docker_swarm run -d -t busybox sh
[ "$status" -eq 0 ]
run docker_swarm run -d -t busybox sh
[ "$status" -eq 0 ]
# When trying to start the 5th one, it should be error finding a node with free slots.
run docker_swarm run -d -t busybox sh
[ "$status" -ne 0 ]
[[ "${lines[0]}" == *"Unable to find a node that satisfies the following conditions"* ]]
[[ "${lines[1]}" == *"free slots"* ]]
# And the number of running containers should be still 4.
run docker_swarm ps
[ "${#lines[@]}" -eq 5 ]
}
@test "containerslots without existing label" {
start_docker_with_busybox 2
swarm_manage
# Use busybox to save image pulling time for integration test.
# Running more than 5 containers, it should be fine.
run docker_swarm run -d -t busybox sh
[ "$status" -eq 0 ]
run docker_swarm run -d -t busybox sh
[ "$status" -eq 0 ]
run docker_swarm run -d -t busybox sh
[ "$status" -eq 0 ]
run docker_swarm run -d -t busybox sh
[ "$status" -eq 0 ]
run docker_swarm run -d -t busybox sh
[ "$status" -eq 0 ]
# And the number of running containers should be 5.
run docker_swarm ps
[ "${#lines[@]}" -eq 6 ]
}
@test "containerslots with invalid label" {
start_docker_with_busybox 2 --label containerslots="foo"
swarm_manage
# Use busybox to save image pulling time for integration test.
# Running more than 5 containers, it should be fine.
run docker_swarm run -d -t busybox sh
[ "$status" -eq 0 ]
run docker_swarm run -d -t busybox sh
[ "$status" -eq 0 ]
run docker_swarm run -d -t busybox sh
[ "$status" -eq 0 ]
run docker_swarm run -d -t busybox sh
[ "$status" -eq 0 ]
run docker_swarm run -d -t busybox sh
[ "$status" -eq 0 ]
# And the number of running containers should be 5.
run docker_swarm ps
[ "${#lines[@]}" -eq 6 ]
}