diff --git a/cluster/node.go b/cluster/node.go index 0c02e5547c..974d048b66 100644 --- a/cluster/node.go +++ b/cluster/node.go @@ -99,7 +99,7 @@ func (n *Node) updateSpecs() error { n.Cpus = info.NCPU n.Memory = info.MemTotal n.Labels = map[string]string{ - "graphdriver": info.Driver, + "storagedriver": info.Driver, "executiondriver": info.ExecutionDriver, "kernelversion": info.KernelVersion, "operatingsystem": info.OperatingSystem, @@ -207,7 +207,6 @@ func (n *Node) Create(config *dockerclient.ContainerConfig, name string, pullIma // Register the container immediately while waiting for a state refresh. // Force a state refresh to pick up the newly created container. - log.Debug("Updating containers after create") n.updateContainers() return n.containers[id], nil diff --git a/manage.go b/manage.go index f3fbc8ec6e..eb15ee4555 100644 --- a/manage.go +++ b/manage.go @@ -57,7 +57,7 @@ func manage(c *cli.Context) { } } - s := scheduler.NewScheduler(cluster, &strategy.BinPackingPlacementStrategy{}, []filter.Filter{}) + s := scheduler.NewScheduler(cluster, &strategy.BinPackingPlacementStrategy{}, []filter.Filter{&filter.AttributeFilter{}, &filter.PortFilter{}}) log.Fatal(api.ListenAndServe(cluster, s, c.String("addr"))) } diff --git a/scheduler/filter/attributes.go b/scheduler/filter/attributes.go new file mode 100644 index 0000000000..248834345e --- /dev/null +++ b/scheduler/filter/attributes.go @@ -0,0 +1,45 @@ +package filter + +import ( + "fmt" + "strings" + + "github.com/docker/swarm/cluster" + "github.com/samalba/dockerclient" +) + +// AttributeFilter selects only nodes that match certain attributes. Attributes +// include storagedriver, executiondriver and so on. +type AttributeFilter struct { +} + +func (f *AttributeFilter) extractConstraints(env []string) map[string]string { + constraints := make(map[string]string) + for _, e := range env { + if strings.HasPrefix(e, "constraint:") { + constraint := strings.TrimPrefix(e, "constraint:") + parts := strings.SplitN(constraint, "=", 2) + constraints[strings.ToLower(parts[0])] = strings.ToLower(parts[1]) + } + } + return constraints +} + +func (f *AttributeFilter) Filter(config *dockerclient.ContainerConfig, nodes []*cluster.Node) ([]*cluster.Node, error) { + constraints := f.extractConstraints(config.Env) + for k, v := range constraints { + candidates := []*cluster.Node{} + for _, node := range nodes { + if label, ok := node.Labels[k]; ok { + if strings.Contains(strings.ToLower(label), v) { + candidates = append(candidates, node) + } + } + } + if len(candidates) == 0 { + return nil, fmt.Errorf("unable to find a node that satisfies %s == %s", k, v) + } + nodes = candidates + } + return nodes, nil +} diff --git a/scheduler/filter/port.go b/scheduler/filter/port.go new file mode 100644 index 0000000000..9bd2a04f6c --- /dev/null +++ b/scheduler/filter/port.go @@ -0,0 +1,55 @@ +package filter + +import ( + "fmt" + + "github.com/docker/swarm/cluster" + "github.com/samalba/dockerclient" +) + +// PortFilter guarantees that, when scheduling a container binding a public +// port, only nodes that have not already allocated that same port will be +// considered. +type PortFilter struct { +} + +func (p *PortFilter) Filter(config *dockerclient.ContainerConfig, nodes []*cluster.Node) ([]*cluster.Node, error) { + for _, port := range config.HostConfig.PortBindings { + for _, binding := range port { + candidates := []*cluster.Node{} + for _, node := range nodes { + if !p.portAlreadyInUse(node, binding) { + candidates = append(candidates, node) + } + } + if len(candidates) == 0 { + return nil, fmt.Errorf("unable to find a node with port %s available", binding.HostPort) + } + nodes = candidates + } + } + return nodes, nil +} + +func (p *PortFilter) portAlreadyInUse(node *cluster.Node, requested dockerclient.PortBinding) bool { + for _, c := range node.Containers() { + for _, port := range c.Info.NetworkSettings.Ports { + for _, binding := range port { + if binding.HostPort == requested.HostPort { + // Another container on the same host is binding on the same + // port/protocol. Verify if they are requesting the same + // binding IP, or if the other container is already binding on + // every interface. + if requested.HostIp == binding.HostIp || bindsAllInterfaces(requested) || bindsAllInterfaces(binding) { + return true + } + } + } + } + } + return false +} + +func bindsAllInterfaces(binding dockerclient.PortBinding) bool { + return binding.HostIp == "0.0.0.0" || binding.HostIp == "" +}