From ac3a8b6ac9208bed348b33ca47c6629a7dd0866e Mon Sep 17 00:00:00 2001 From: Nishant Totla Date: Wed, 10 Feb 2016 15:53:53 -0800 Subject: [PATCH 1/2] Adding functions to list filter constraints of all types Signed-off-by: Nishant Totla --- scheduler/filter/affinity.go | 13 +++++++++++++ scheduler/filter/constraint.go | 13 +++++++++++++ scheduler/filter/dependency.go | 10 ++++++++-- scheduler/filter/filter.go | 3 +++ scheduler/filter/health.go | 5 +++++ scheduler/filter/port.go | 18 ++++++++++++++++++ 6 files changed, 60 insertions(+), 2 deletions(-) diff --git a/scheduler/filter/affinity.go b/scheduler/filter/affinity.go index 6bc458d586..6727819e5a 100644 --- a/scheduler/filter/affinity.go +++ b/scheduler/filter/affinity.go @@ -76,3 +76,16 @@ func (f *AffinityFilter) Filter(config *cluster.ContainerConfig, nodes []*node.N return nodes, nil } + +// Get a list of the affinities found in the container config. +func (f *AffinityFilter) GetAllFilters(config *cluster.ContainerConfig) ([]string, error) { + allAffinities := []string{} + affinities, err := parseExprs(config.Affinities()) + if err != nil { + return nil, err + } + for _, affinity := range affinities { + allAffinities = append(allAffinities, fmt.Sprintf("%s%s%s (soft=%t)", affinity.key, OPERATORS[affinity.operator], affinity.value, affinity.isSoft)) + } + return allAffinities, nil +} diff --git a/scheduler/filter/constraint.go b/scheduler/filter/constraint.go index 841d5feb3b..dcf454be93 100644 --- a/scheduler/filter/constraint.go +++ b/scheduler/filter/constraint.go @@ -51,3 +51,16 @@ func (f *ConstraintFilter) Filter(config *cluster.ContainerConfig, nodes []*node } return nodes, nil } + +// Get a list of the constraints found in the container config. +func (f *ConstraintFilter) GetAllFilters(config *cluster.ContainerConfig) ([]string, error) { + allConstraints := []string{} + constraints, err := parseExprs(config.Constraints()) + if err != nil { + return nil, err + } + for _, constraint := range constraints { + allConstraints = append(allConstraints, fmt.Sprintf("%s%s%s", constraint.key, OPERATORS[constraint.operator], constraint.value)) + } + return allConstraints, nil +} diff --git a/scheduler/filter/dependency.go b/scheduler/filter/dependency.go index 4a0ea9fdf0..0e8c078462 100644 --- a/scheduler/filter/dependency.go +++ b/scheduler/filter/dependency.go @@ -56,8 +56,8 @@ func (f *DependencyFilter) Filter(config *cluster.ContainerConfig, nodes []*node return candidates, nil } -// Get a string representation of the dependencies found in the container config. -func (f *DependencyFilter) String(config *cluster.ContainerConfig) string { +// Get a list of the dependencies found in the container config. +func (f *DependencyFilter) GetAllFilters(config *cluster.ContainerConfig) ([]string, error) { dependencies := []string{} for _, volume := range config.HostConfig.VolumesFrom { dependencies = append(dependencies, fmt.Sprintf("--volumes-from=%s", volume)) @@ -68,6 +68,12 @@ func (f *DependencyFilter) String(config *cluster.ContainerConfig) string { if strings.HasPrefix(config.HostConfig.NetworkMode, "container:") { dependencies = append(dependencies, fmt.Sprintf("--net=%s", config.HostConfig.NetworkMode)) } + return dependencies, nil +} + +// Get a string representation of the dependencies found in the container config. +func (f *DependencyFilter) String(config *cluster.ContainerConfig) string { + dependencies, _ := f.GetAllFilters(config) return strings.Join(dependencies, " ") } diff --git a/scheduler/filter/filter.go b/scheduler/filter/filter.go index c17419acc4..b414c24f72 100644 --- a/scheduler/filter/filter.go +++ b/scheduler/filter/filter.go @@ -14,6 +14,9 @@ type Filter interface { // Return a subset of nodes that were accepted by the filtering policy. Filter(*cluster.ContainerConfig, []*node.Node, bool) ([]*node.Node, error) + + // Return a list of constraints/filters provided + GetAllFilters(*cluster.ContainerConfig) ([]string, error) } var ( diff --git a/scheduler/filter/health.go b/scheduler/filter/health.go index d6a27b54af..12f805c710 100644 --- a/scheduler/filter/health.go +++ b/scheduler/filter/health.go @@ -36,3 +36,8 @@ func (f *HealthFilter) Filter(_ *cluster.ContainerConfig, nodes []*node.Node, _ return result, nil } + +// Implements interface function, but currently redundant since we don't have node constraints +func (f *HealthFilter) GetAllFilters(config *cluster.ContainerConfig) ([]string, error) { + return nil, nil +} diff --git a/scheduler/filter/port.go b/scheduler/filter/port.go index 757a6c6b6a..2145ab44fd 100644 --- a/scheduler/filter/port.go +++ b/scheduler/filter/port.go @@ -119,6 +119,24 @@ func (p *PortFilter) compare(requested dockerclient.PortBinding, bindings map[st return false } +// Get a list of the port constraints found in the container config. +func (p *PortFilter) GetAllFilters(config *cluster.ContainerConfig) ([]string, error) { + allPortConstraints := []string{} + if config.HostConfig.NetworkMode == "host" { + for port := range config.ExposedPorts { + allPortConstraints = append(allPortConstraints, fmt.Sprintf("port %s (Host mode)", port)) + } + return allPortConstraints, nil + } + + for _, port := range config.HostConfig.PortBindings { + for _, binding := range port { + allPortConstraints = append(allPortConstraints, fmt.Sprintf("port %s", binding.HostPort)) + } + } + return allPortConstraints, nil +} + func bindsAllInterfaces(binding dockerclient.PortBinding) bool { return binding.HostIp == "0.0.0.0" || binding.HostIp == "" } From 5075e95612d41b0a467b4e23899180919ba8e7f1 Mon Sep 17 00:00:00 2001 From: Nishant Totla Date: Wed, 10 Feb 2016 18:22:34 -0800 Subject: [PATCH 2/2] Printing entire set of unsatisfiable constraints for container scheduling Signed-off-by: Nishant Totla --- scheduler/filter/affinity.go | 4 ++-- scheduler/filter/constraint.go | 4 ++-- scheduler/filter/dependency.go | 6 +++--- scheduler/filter/filter.go | 24 ++++++++++++++++++++++-- scheduler/filter/health.go | 4 ++-- scheduler/filter/port.go | 6 +++--- test/integration/affinities.bats | 3 ++- test/integration/build_with_filters.bats | 3 ++- test/integration/port-filters.bats | 6 ++++-- 9 files changed, 42 insertions(+), 18 deletions(-) diff --git a/scheduler/filter/affinity.go b/scheduler/filter/affinity.go index 6727819e5a..d3903aff6a 100644 --- a/scheduler/filter/affinity.go +++ b/scheduler/filter/affinity.go @@ -77,8 +77,8 @@ func (f *AffinityFilter) Filter(config *cluster.ContainerConfig, nodes []*node.N return nodes, nil } -// Get a list of the affinities found in the container config. -func (f *AffinityFilter) GetAllFilters(config *cluster.ContainerConfig) ([]string, error) { +// GetFilters returns a list of the affinities found in the container config. +func (f *AffinityFilter) GetFilters(config *cluster.ContainerConfig) ([]string, error) { allAffinities := []string{} affinities, err := parseExprs(config.Affinities()) if err != nil { diff --git a/scheduler/filter/constraint.go b/scheduler/filter/constraint.go index dcf454be93..91ab8b0c3d 100644 --- a/scheduler/filter/constraint.go +++ b/scheduler/filter/constraint.go @@ -52,8 +52,8 @@ func (f *ConstraintFilter) Filter(config *cluster.ContainerConfig, nodes []*node return nodes, nil } -// Get a list of the constraints found in the container config. -func (f *ConstraintFilter) GetAllFilters(config *cluster.ContainerConfig) ([]string, error) { +// GetFilters returns a list of the constraints found in the container config. +func (f *ConstraintFilter) GetFilters(config *cluster.ContainerConfig) ([]string, error) { allConstraints := []string{} constraints, err := parseExprs(config.Constraints()) if err != nil { diff --git a/scheduler/filter/dependency.go b/scheduler/filter/dependency.go index 0e8c078462..83a3c363fb 100644 --- a/scheduler/filter/dependency.go +++ b/scheduler/filter/dependency.go @@ -56,8 +56,8 @@ func (f *DependencyFilter) Filter(config *cluster.ContainerConfig, nodes []*node return candidates, nil } -// Get a list of the dependencies found in the container config. -func (f *DependencyFilter) GetAllFilters(config *cluster.ContainerConfig) ([]string, error) { +// GetFilters returns a list of the dependencies found in the container config. +func (f *DependencyFilter) GetFilters(config *cluster.ContainerConfig) ([]string, error) { dependencies := []string{} for _, volume := range config.HostConfig.VolumesFrom { dependencies = append(dependencies, fmt.Sprintf("--volumes-from=%s", volume)) @@ -73,7 +73,7 @@ func (f *DependencyFilter) GetAllFilters(config *cluster.ContainerConfig) ([]str // Get a string representation of the dependencies found in the container config. func (f *DependencyFilter) String(config *cluster.ContainerConfig) string { - dependencies, _ := f.GetAllFilters(config) + dependencies, _ := f.GetFilters(config) return strings.Join(dependencies, " ") } diff --git a/scheduler/filter/filter.go b/scheduler/filter/filter.go index b414c24f72..e33a2f5832 100644 --- a/scheduler/filter/filter.go +++ b/scheduler/filter/filter.go @@ -2,6 +2,7 @@ package filter import ( "errors" + "fmt" log "github.com/Sirupsen/logrus" "github.com/docker/swarm/cluster" @@ -16,7 +17,7 @@ type Filter interface { Filter(*cluster.ContainerConfig, []*node.Node, bool) ([]*node.Node, error) // Return a list of constraints/filters provided - GetAllFilters(*cluster.ContainerConfig) ([]string, error) + GetFilters(*cluster.ContainerConfig) ([]string, error) } var ( @@ -66,12 +67,31 @@ func ApplyFilters(filters []Filter, config *cluster.ContainerConfig, nodes []*no for _, filter := range filters { candidates, err = filter.Filter(config, candidates, soft) if err != nil { - return nil, err + // special case for when no healthy nodes are found + if filter.Name() == "health" { + return nil, err + } + return nil, fmt.Errorf("Unable to find a node that satisfies the following conditions %s", listAllFilters(filters, config, filter.Name())) } } return candidates, nil } +// listAllFilters creates a string containing all applied filters +func listAllFilters(filters []Filter, config *cluster.ContainerConfig, lastFilter string) string { + allFilters := "" + for _, filter := range filters { + list, err := filter.GetFilters(config) + if err == nil && len(list) > 0 { + allFilters = fmt.Sprintf("%s\n%v", allFilters, list) + } + if filter.Name() == lastFilter { + return allFilters + } + } + return allFilters +} + // List returns the names of all the available filters func List() []string { names := []string{} diff --git a/scheduler/filter/health.go b/scheduler/filter/health.go index 12f805c710..04ee2c54f9 100644 --- a/scheduler/filter/health.go +++ b/scheduler/filter/health.go @@ -37,7 +37,7 @@ func (f *HealthFilter) Filter(_ *cluster.ContainerConfig, nodes []*node.Node, _ return result, nil } -// Implements interface function, but currently redundant since we don't have node constraints -func (f *HealthFilter) GetAllFilters(config *cluster.ContainerConfig) ([]string, error) { +// GetFilters returns +func (f *HealthFilter) GetFilters(config *cluster.ContainerConfig) ([]string, error) { return nil, nil } diff --git a/scheduler/filter/port.go b/scheduler/filter/port.go index 2145ab44fd..3579b43456 100644 --- a/scheduler/filter/port.go +++ b/scheduler/filter/port.go @@ -119,8 +119,8 @@ func (p *PortFilter) compare(requested dockerclient.PortBinding, bindings map[st return false } -// Get a list of the port constraints found in the container config. -func (p *PortFilter) GetAllFilters(config *cluster.ContainerConfig) ([]string, error) { +// GetFilters returns a list of the port constraints found in the container config. +func (p *PortFilter) GetFilters(config *cluster.ContainerConfig) ([]string, error) { allPortConstraints := []string{} if config.HostConfig.NetworkMode == "host" { for port := range config.ExposedPorts { @@ -131,7 +131,7 @@ func (p *PortFilter) GetAllFilters(config *cluster.ContainerConfig) ([]string, e for _, port := range config.HostConfig.PortBindings { for _, binding := range port { - allPortConstraints = append(allPortConstraints, fmt.Sprintf("port %s", binding.HostPort)) + allPortConstraints = append(allPortConstraints, fmt.Sprintf("port %s (Bridge mode)", binding.HostPort)) } } return allPortConstraints, nil diff --git a/test/integration/affinities.bats b/test/integration/affinities.bats index 274dc95a52..78fdc4dacd 100644 --- a/test/integration/affinities.bats +++ b/test/integration/affinities.bats @@ -151,7 +151,8 @@ function teardown() { # Run 3 tests in parallel. One of them must fail. run parallel docker -H "${SWARM_HOSTS[0]}" run --label test.label=true -e affinity:test.label!=true -d busybox:latest ::: top top top [ "$status" -ne 0 ] - [[ "${output}" == *'unable to find a node that satisfies test.label!=true'* ]] + [[ "${output}" == *"Unable to find a node that satisfies the following conditions"* ]] + [[ "${output}" == *"[test.label!=true (soft=false)]"* ]] # Only 2 containers should have succeeded. run docker_swarm ps -q diff --git a/test/integration/build_with_filters.bats b/test/integration/build_with_filters.bats index fb7d92b649..90ade594ef 100644 --- a/test/integration/build_with_filters.bats +++ b/test/integration/build_with_filters.bats @@ -17,7 +17,8 @@ function teardown() { run docker_swarm build --build-arg="constraint:node==node-9" $TESTDATA/build [ "$status" -eq 1 ] - [[ "$output" == *"Error response from daemon: unable to find a node that satisfies node==node-9"* ]] + [[ "${lines[1]}" == *"Unable to find a node that satisfies the following conditions"* ]] + [[ "${lines[2]}" == *"[node==node-9]"* ]] run docker_swarm images -q [ "$status" -eq 0 ] diff --git a/test/integration/port-filters.bats b/test/integration/port-filters.bats index 8044ba6730..b93f246d59 100644 --- a/test/integration/port-filters.bats +++ b/test/integration/port-filters.bats @@ -21,7 +21,8 @@ function teardown() { # When trying to start the 3rd one, it should be error finding port 80. run docker_swarm run -d --expose=80 --net=host busybox sh [ "$status" -ne 0 ] - [[ "${lines[0]}" == *"unable to find a node with port 80/tcp available in the Host mode"* ]] + [[ "${lines[0]}" == *"Unable to find a node that satisfies the following conditions"* ]] + [[ "${lines[1]}" == *"[port 80/tcp (Host mode)]"* ]] # And the number of running containers should be still 2. run docker_swarm ps -n 2 @@ -40,7 +41,8 @@ function teardown() { # When trying to start the 3rd one, it should be error finding port 80. run docker_swarm run --expose=80 -p 80:80 busybox echo 3 [ "$status" -ne 0 ] - [[ "${lines[0]}" == *"unable to find a node with port 80 available"* ]] + [[ "${lines[0]}" == *"Unable to find a node that satisfies the following conditions"* ]] + [[ "${lines[1]}" == *"[port 80 (Bridge mode)]"* ]] # And the number of running containers should be still 2. run docker_swarm ps -n 2