func testFixtures() (nodes []*cluster.Node) { nodes = []*cluster.Node{ cluster.NewNode("node-0", 0), cluster.NewNode("node-1", 0), cluster.NewNode("node-2", 0), cluster.NewNode("node-3", 0), } nodes[0].ID = "node-0-id" nodes[0].Name = "node-0-name" nodes[0].Labels = map[string]string{ "name": "node0", "group": "1", "region": "us-west", } nodes[1].ID = "node-1-id" nodes[1].Name = "node-1-name" nodes[1].Labels = map[string]string{ "name": "node1", "group": "1", "region": "us-east", } nodes[2].ID = "node-2-id" nodes[2].Name = "node-2-name" nodes[2].Labels = map[string]string{ "name": "node2", "group": "2", "region": "eu", } nodes[3].ID = "node-3-id" nodes[3].Name = "node-3-name" return }
func TestPortFilterSimple(t *testing.T) { var ( p = PortFilter{} nodes = []*cluster.Node{ cluster.NewNode("node-1", 0), cluster.NewNode("node-2", 0), cluster.NewNode("node-3", 0), } result []*cluster.Node err error ) // Add a container taking away port 80 to nodes[0]. container := &cluster.Container{Container: dockerclient.Container{Id: "c1"}, Info: dockerclient.ContainerInfo{HostConfig: &dockerclient.HostConfig{PortBindings: makeBinding("", "80")}}} assert.NoError(t, nodes[0].AddContainer(container)) // Request port 80. config := &dockerclient.ContainerConfig{ HostConfig: dockerclient.HostConfig{ PortBindings: makeBinding("", "80"), }, } // nodes[0] should be excluded since port 80 is taken away. result, err = p.Filter(config, nodes) assert.NoError(t, err) assert.NotContains(t, result, nodes[0]) }
func createNode(ID string, memory int64, cpus int64) *cluster.Node { node := cluster.NewNode(ID, 0.05) node.ID = ID node.Memory = memory * 1024 * 1024 * 1024 node.Cpus = cpus return node }
func TestPortFilterNoConflicts(t *testing.T) { var ( p = PortFilter{} nodes = []*cluster.Node{ cluster.NewNode("node-1", 0), cluster.NewNode("node-2", 0), cluster.NewNode("node-3", 0), } result []*cluster.Node err error ) // Request no ports. config := &dockerclient.ContainerConfig{ HostConfig: dockerclient.HostConfig{ PortBindings: map[string][]dockerclient.PortBinding{}, }, } // Make sure we don't filter anything out. result, err = p.Filter(config, nodes) assert.NoError(t, err) assert.Equal(t, result, nodes) // Request port 80. config = &dockerclient.ContainerConfig{ HostConfig: dockerclient.HostConfig{ PortBindings: makeBinding("", "80"), }, } // Since there are no other containers in the cluster, this shouldn't // filter anything either. result, err = p.Filter(config, nodes) assert.NoError(t, err) assert.Equal(t, result, nodes) // Add a container taking a different (4242) port. container := &cluster.Container{Container: dockerclient.Container{Id: "c1"}, Info: dockerclient.ContainerInfo{HostConfig: &dockerclient.HostConfig{PortBindings: makeBinding("", "4242")}}} assert.NoError(t, nodes[0].AddContainer(container)) // Since no node is using port 80, there should be no filter result, err = p.Filter(config, nodes) assert.NoError(t, err) assert.Equal(t, result, nodes) }
func TestFilterWithRelativeComparisons(t *testing.T) { t.Skip() var ( f = ConstraintFilter{} nodes = testFixtures() result []*cluster.Node err error ) // Prepare node with a strange name node3 := cluster.NewNode("node-3", 0) node3.ID = "node-3-id" node3.Name = "node-3-name" node3.Labels = map[string]string{ "name": "aBcDeF", "group": "4", "kernel": "3.1", "region": "eu", } nodes = append(nodes, node3) // Check with less than or equal result, err = f.Filter(&dockerclient.ContainerConfig{ Env: []string{`constraint:group<=3`}, }, nodes) assert.NoError(t, err) assert.Len(t, result, 3) // Check with greater than or equal result, err = f.Filter(&dockerclient.ContainerConfig{ Env: []string{`constraint:group>=4`}, }, nodes) assert.NoError(t, err) assert.Len(t, result, 1) // Another gte check with a complex string result, err = f.Filter(&dockerclient.ContainerConfig{ Env: []string{`constraint:kernel>=3.0`}, }, nodes) assert.NoError(t, err) assert.Len(t, result, 1) assert.Equal(t, result[0], nodes[3]) assert.Equal(t, result[0].Labels["kernel"], "3.1") // Check with greater than or equal. This should match node-3-id. result, err = f.Filter(&dockerclient.ContainerConfig{ Env: []string{`constraint:node>=node-3`}, }, nodes) assert.NoError(t, err) assert.Len(t, result, 1) }
func TestFilterRegExpCaseInsensitive(t *testing.T) { var ( f = ConstraintFilter{} nodes = testFixtures() result []*cluster.Node err error ) // Prepare node with a strange name node3 := cluster.NewNode("node-3", 0) node3.ID = "node-3-id" node3.Name = "node-3-name" node3.Labels = map[string]string{ "name": "aBcDeF", "group": "2", "region": "eu", } nodes[3] = node3 // Case-sensitive, so not match result, err = f.Filter(&dockerclient.ContainerConfig{ Env: []string{`constraint:name==/abcdef/`}, }, nodes) assert.Error(t, err) assert.Len(t, result, 0) // Match with case-insensitive result, err = f.Filter(&dockerclient.ContainerConfig{ Env: []string{`constraint:name==/(?i)abcdef/`}, }, nodes) assert.NoError(t, err) assert.Len(t, result, 1) assert.Equal(t, result[0], nodes[3]) assert.Equal(t, result[0].Labels["name"], "aBcDeF") // Test ! filter combined with case insensitive result, err = f.Filter(&dockerclient.ContainerConfig{ Env: []string{`constraint:name!=/(?i)abc*/`}, }, nodes) assert.NoError(t, err) assert.Len(t, result, 3) }
func manage(c *cli.Context) { var ( tlsConfig *tls.Config = nil err error ) // If either --tls or --tlsverify are specified, load the certificates. if c.Bool("tls") || c.Bool("tlsverify") { tlsConfig, err = loadTlsConfig( c.String("tlscacert"), c.String("tlscert"), c.String("tlskey"), c.Bool("tlsverify")) if err != nil { log.Fatal(err) } } refresh := func(c *cluster.Cluster, nodes []string) { for _, addr := range nodes { go func(addr string) { if !strings.Contains(addr, "://") { addr = "http://" + addr } if c.Node(addr) == nil { n := cluster.NewNode(addr) if err := n.Connect(tlsConfig); err != nil { log.Error(err) return } if err := c.AddNode(n); err != nil { log.Error(err) return } } }(addr) } } cluster := cluster.NewCluster() cluster.Events(&logHandler{}) go func() { if c.String("token") != "" { nodes, err := discovery.FetchSlaves(c.String("token")) if err != nil { log.Fatal(err) } refresh(cluster, nodes) hb := time.Duration(c.Int("heartbeat")) go func() { for { time.Sleep(hb * time.Second) nodes, err = discovery.FetchSlaves(c.String("token")) if err == nil { refresh(cluster, nodes) } } }() } else { refresh(cluster, c.Args()) } }() s := scheduler.NewScheduler( cluster, &strategy.BinPackingPlacementStrategy{OvercommitRatio: 0.05}, []filter.Filter{ &filter.HealthFilter{}, &filter.LabelFilter{}, &filter.PortFilter{}, }, ) log.Fatal(api.ListenAndServe(cluster, s, c.String("addr"), c.App.Version, c.Bool("cors"), tlsConfig)) }
func TestPortFilterDifferentInterfaces(t *testing.T) { var ( p = PortFilter{} nodes = []*cluster.Node{ cluster.NewNode("node-1", 0), cluster.NewNode("node-2", 0), cluster.NewNode("node-3", 0), } result []*cluster.Node err error ) // Add a container taking away port 80 on every interface to nodes[0]. container := &cluster.Container{Container: dockerclient.Container{Id: "c1"}, Info: dockerclient.ContainerInfo{HostConfig: &dockerclient.HostConfig{PortBindings: makeBinding("", "80")}}} assert.NoError(t, nodes[0].AddContainer(container)) // Request port 80 for the local interface. config := &dockerclient.ContainerConfig{ HostConfig: dockerclient.HostConfig{ PortBindings: makeBinding("127.0.0.1", "80"), }, } // nodes[0] should be excluded since port 80 is taken away for every // interface. result, err = p.Filter(config, nodes) assert.NoError(t, err) assert.NotContains(t, result, nodes[0]) // Add a container taking away port 4242 on the local interface of // nodes[1]. container = &cluster.Container{Container: dockerclient.Container{Id: "c1"}, Info: dockerclient.ContainerInfo{HostConfig: &dockerclient.HostConfig{PortBindings: makeBinding("127.0.0.1", "4242")}}} assert.NoError(t, nodes[1].AddContainer(container)) // Request port 4242 on the same interface. config = &dockerclient.ContainerConfig{ HostConfig: dockerclient.HostConfig{ PortBindings: makeBinding("127.0.0.1", "4242"), }, } // nodes[1] should be excluded since port 4242 is already taken on that // interface. result, err = p.Filter(config, nodes) assert.NoError(t, err) assert.NotContains(t, result, nodes[1]) // Request port 4242 on every interface. config = &dockerclient.ContainerConfig{ HostConfig: dockerclient.HostConfig{ PortBindings: makeBinding("0.0.0.0", "4242"), }, } // nodes[1] should still be excluded since the port is not available on the same interface. result, err = p.Filter(config, nodes) assert.NoError(t, err) assert.NotContains(t, result, nodes[1]) // Request port 4242 on every interface using an alternative syntax. config = &dockerclient.ContainerConfig{ HostConfig: dockerclient.HostConfig{ PortBindings: makeBinding("", "4242"), }, } // nodes[1] should still be excluded since the port is not available on the same interface. result, err = p.Filter(config, nodes) assert.NoError(t, err) assert.NotContains(t, result, nodes[1]) // Finally, request port 4242 on a different interface. config = &dockerclient.ContainerConfig{ HostConfig: dockerclient.HostConfig{ PortBindings: makeBinding("192.168.1.1", "4242"), }, } // nodes[1] should be included this time since the port is available on the // other interface. result, err = p.Filter(config, nodes) assert.NoError(t, err) assert.Contains(t, result, nodes[1]) }
func TestPortFilterRandomAssignment(t *testing.T) { var ( p = PortFilter{} nodes = []*cluster.Node{ cluster.NewNode("node-1", 0), cluster.NewNode("node-2", 0), cluster.NewNode("node-3", 0), } result []*cluster.Node err error ) // Simulate a container that requested to map 80 to a random port. // In this case, HostConfig.PortBindings should contain a binding with no // HostPort defined and NetworkSettings.Ports should contain the actual // mapped port. container := &cluster.Container{ Container: dockerclient.Container{Id: "c1"}, Info: dockerclient.ContainerInfo{ HostConfig: &dockerclient.HostConfig{ PortBindings: map[string][]dockerclient.PortBinding{ "80/tcp": { { HostIp: "", HostPort: "", }, }, }, }, NetworkSettings: struct { IpAddress string IpPrefixLen int Gateway string Bridge string Ports map[string][]dockerclient.PortBinding }{ Ports: map[string][]dockerclient.PortBinding{ "80/tcp": { { HostIp: "127.0.0.1", HostPort: "1234", }, }, }, }, }, } assert.NoError(t, nodes[0].AddContainer(container)) // Request port 80. config := &dockerclient.ContainerConfig{ HostConfig: dockerclient.HostConfig{ PortBindings: makeBinding("", "80"), }, } // Since port "80" has been mapped to "1234", we should be able to request "80". result, err = p.Filter(config, nodes) assert.NoError(t, err) assert.Equal(t, result, nodes) // However, we should not be able to request "1234" since it has been used for a random assignment. config = &dockerclient.ContainerConfig{ HostConfig: dockerclient.HostConfig{ PortBindings: makeBinding("", "1234"), }, } result, err = p.Filter(config, nodes) assert.NoError(t, err) assert.NotContains(t, result, nodes[0]) }
func TestAffinityFilter(t *testing.T) { var ( f = AffinityFilter{} nodes = []*cluster.Node{ cluster.NewNode("node-0", 0), cluster.NewNode("node-1", 0), cluster.NewNode("node-2", 0), } result []*cluster.Node err error ) nodes[0].ID = "node-0-id" nodes[0].Name = "node-0-name" nodes[0].AddContainer(&cluster.Container{ Container: dockerclient.Container{ Id: "container-n0-0-id", Names: []string{"/container-n0-0-name"}, }, }) nodes[0].AddContainer(&cluster.Container{ Container: dockerclient.Container{ Id: "container-n0-1-id", Names: []string{"/container-n0-1-name"}, }, }) nodes[0].AddImage(&dockerclient.Image{ Id: "image-0-id", RepoTags: []string{"image-0:tag1", "image-0:tag2"}, }) nodes[1].ID = "node-1-id" nodes[1].Name = "node-1-name" nodes[1].AddContainer(&cluster.Container{ Container: dockerclient.Container{ Id: "container-n1-0-id", Names: []string{"/container-n1-0-name"}, }, }) nodes[1].AddContainer(&cluster.Container{ Container: dockerclient.Container{ Id: "container-n1-1-id", Names: []string{"/container-n1-1-name"}, }, }) nodes[1].AddImage(&dockerclient.Image{ Id: "image-1-id", RepoTags: []string{"image-1:tag1", "image-0:tag3", "image-1:tag2"}, }) nodes[2].ID = "node-2-id" nodes[2].Name = "node-2-name" // Without constraints we should get the unfiltered list of nodes back. result, err = f.Filter(&dockerclient.ContainerConfig{}, nodes) assert.NoError(t, err) assert.Equal(t, result, nodes) // Set a constraint that cannot be fullfilled and expect an error back. result, err = f.Filter(&dockerclient.ContainerConfig{ Env: []string{"affinity:container==does_not_exsits"}, }, nodes) assert.Error(t, err) // Set a contraint that can only be filled by a single node. result, err = f.Filter(&dockerclient.ContainerConfig{ Env: []string{"affinity:container==container-n0*"}, }, nodes) assert.NoError(t, err) assert.Len(t, result, 1) assert.Equal(t, result[0], nodes[0]) // This constraint can only be fullfilled by a subset of nodes. result, err = f.Filter(&dockerclient.ContainerConfig{ Env: []string{"affinity:container==container-*"}, }, nodes) assert.NoError(t, err) assert.Len(t, result, 2) assert.NotContains(t, result, nodes[2]) // Validate by id. result, err = f.Filter(&dockerclient.ContainerConfig{ Env: []string{"affinity:container==container-n0-0-id"}, }, nodes) assert.NoError(t, err) assert.Len(t, result, 1) assert.Equal(t, result[0], nodes[0]) // Validate by id. result, err = f.Filter(&dockerclient.ContainerConfig{ Env: []string{"affinity:container!=container-n0-0-id"}, }, nodes) assert.NoError(t, err) assert.Len(t, result, 2) assert.NotContains(t, result, nodes[0]) // Validate by id. result, err = f.Filter(&dockerclient.ContainerConfig{ Env: []string{"affinity:container!=container-n0-1-id"}, }, nodes) assert.NoError(t, err) assert.Len(t, result, 2) assert.NotContains(t, result, nodes[0]) // Validate by name. result, err = f.Filter(&dockerclient.ContainerConfig{ Env: []string{"affinity:container==container-n1-0-name"}, }, nodes) assert.NoError(t, err) assert.Len(t, result, 1) assert.Equal(t, result[0], nodes[1]) // Validate by name. result, err = f.Filter(&dockerclient.ContainerConfig{ Env: []string{"affinity:container!=container-n1-0-name"}, }, nodes) assert.NoError(t, err) assert.Len(t, result, 2) assert.NotContains(t, result, nodes[1]) // Validate by name. result, err = f.Filter(&dockerclient.ContainerConfig{ Env: []string{"affinity:container!=container-n1-1-name"}, }, nodes) assert.NoError(t, err) assert.Len(t, result, 2) assert.NotContains(t, result, nodes[1]) // Validate images by id result, err = f.Filter(&dockerclient.ContainerConfig{ Env: []string{"affinity:image==image-0-id"}, }, nodes) assert.NoError(t, err) assert.Len(t, result, 1) assert.Equal(t, result[0], nodes[0]) // Validate images by name result, err = f.Filter(&dockerclient.ContainerConfig{ Env: []string{"affinity:image==image-0:tag3"}, }, nodes) assert.NoError(t, err) assert.Len(t, result, 1) assert.Equal(t, result[0], nodes[1]) // Validate images by name result, err = f.Filter(&dockerclient.ContainerConfig{ Env: []string{"affinity:image!=image-0:tag3"}, }, nodes) assert.NoError(t, err) assert.Len(t, result, 2) // Validate images by name result, err = f.Filter(&dockerclient.ContainerConfig{ Env: []string{"affinity:image==image-1"}, }, nodes) assert.NoError(t, err) assert.Len(t, result, 1) assert.Equal(t, result[0], nodes[1]) // Validate images by name result, err = f.Filter(&dockerclient.ContainerConfig{ Env: []string{"affinity:image!=image-1"}, }, nodes) assert.NoError(t, err) assert.Len(t, result, 2) // Ensure that constraints can be chained. result, err = f.Filter(&dockerclient.ContainerConfig{ Env: []string{ "affinity:container!=container-n0-1-id", "affinity:container!=container-n1-1-id", }, }, nodes) assert.NoError(t, err) assert.Len(t, result, 1) assert.Equal(t, result[0], nodes[2]) // Ensure that constraints can be chained. result, err = f.Filter(&dockerclient.ContainerConfig{ Env: []string{ "affinity:container==container-n0-1-id", "affinity:container==container-n1-1-id", }, }, nodes) assert.Error(t, err) // Not support = any more result, err = f.Filter(&dockerclient.ContainerConfig{ Env: []string{"affinity:image=image-0:tag3"}, }, nodes) assert.Error(t, err) assert.Len(t, result, 0) // Not support =! any more result, err = f.Filter(&dockerclient.ContainerConfig{ Env: []string{"affinity:image=!image-0:tag3"}, }, nodes) assert.Error(t, err) assert.Len(t, result, 0) }