Merge pull request #228 from vieux/overcommit

Move overcommit outside of binpacking
This commit is contained in:
Andrea Luzzardi 2015-01-14 14:33:30 +01:00
commit 61081ec6d2
13 changed files with 92 additions and 71 deletions

View File

@ -20,12 +20,14 @@ type Cluster struct {
tlsConfig *tls.Config tlsConfig *tls.Config
eventHandlers []EventHandler eventHandlers []EventHandler
nodes map[string]*Node nodes map[string]*Node
overcommitRatio float64
} }
func NewCluster(tlsConfig *tls.Config) *Cluster { func NewCluster(tlsConfig *tls.Config, overcommitRatio float64) *Cluster {
return &Cluster{ return &Cluster{
tlsConfig: tlsConfig, tlsConfig: tlsConfig,
nodes: make(map[string]*Node), nodes: make(map[string]*Node),
overcommitRatio: overcommitRatio,
} }
} }
@ -63,7 +65,7 @@ func (c *Cluster) UpdateNodes(nodes []*discovery.Node) {
for _, addr := range nodes { for _, addr := range nodes {
go func(node *discovery.Node) { go func(node *discovery.Node) {
if c.Node(node.String()) == nil { if c.Node(node.String()) == nil {
n := NewNode(node.String()) n := NewNode(node.String(), c.overcommitRatio)
if err := n.Connect(c.tlsConfig); err != nil { if err := n.Connect(c.tlsConfig); err != nil {
log.Error(err) log.Error(err)
return return

View File

@ -10,7 +10,7 @@ import (
) )
func createNode(t *testing.T, ID string, containers ...dockerclient.Container) *Node { func createNode(t *testing.T, ID string, containers ...dockerclient.Container) *Node {
node := NewNode(ID) node := NewNode(ID, 0)
node.Name = ID node.Name = ID
assert.False(t, node.IsConnected()) assert.False(t, node.IsConnected())
@ -32,7 +32,7 @@ func createNode(t *testing.T, ID string, containers ...dockerclient.Container) *
} }
func TestAddNode(t *testing.T) { func TestAddNode(t *testing.T) {
c := NewCluster(nil) c := NewCluster(nil, 0)
assert.Equal(t, len(c.Nodes()), 0) assert.Equal(t, len(c.Nodes()), 0)
assert.Nil(t, c.Node("test")) assert.Nil(t, c.Node("test"))
@ -52,7 +52,7 @@ func TestAddNode(t *testing.T) {
} }
func TestLookupContainer(t *testing.T) { func TestLookupContainer(t *testing.T) {
c := NewCluster(nil) c := NewCluster(nil, 0)
container := dockerclient.Container{ container := dockerclient.Container{
Id: "container-id", Id: "container-id",
Names: []string{"/container-name1", "/container-name2"}, Names: []string{"/container-name1", "/container-name2"},

View File

@ -21,13 +21,14 @@ const (
requestTimeout = 10 * time.Second requestTimeout = 10 * time.Second
) )
func NewNode(addr string) *Node { func NewNode(addr string, overcommitRatio float64) *Node {
e := &Node{ e := &Node{
Addr: addr, Addr: addr,
Labels: make(map[string]string), Labels: make(map[string]string),
ch: make(chan bool), ch: make(chan bool),
containers: make(map[string]*Container), containers: make(map[string]*Container),
healthy: true, healthy: true,
overcommitRatio: int64(overcommitRatio * 100),
} }
return e return e
} }
@ -48,6 +49,7 @@ type Node struct {
client dockerclient.Client client dockerclient.Client
eventHandler EventHandler eventHandler EventHandler
healthy bool healthy bool
overcommitRatio int64
} }
// Connect will initialize a connection to the Docker daemon running on the // Connect will initialize a connection to the Docker daemon running on the
@ -268,6 +270,14 @@ func (n *Node) ReservedCpus() int64 {
return r return r
} }
func (n *Node) UsableMemory() int64 {
return n.Memory + (n.Memory * n.overcommitRatio / 100)
}
func (n *Node) UsableCpus() int64 {
return n.Cpus + (n.Cpus * n.overcommitRatio / 100)
}
func (n *Node) Create(config *dockerclient.ContainerConfig, name string, pullImage bool) (*Container, error) { func (n *Node) Create(config *dockerclient.ContainerConfig, name string, pullImage bool) (*Container, error) {
var ( var (
err error err error

View File

@ -26,7 +26,7 @@ var (
) )
func TestNodeConnectionFailure(t *testing.T) { func TestNodeConnectionFailure(t *testing.T) {
node := NewNode("test") node := NewNode("test", 0)
assert.False(t, node.IsConnected()) assert.False(t, node.IsConnected())
// Always fail. // Always fail.
@ -41,7 +41,7 @@ func TestNodeConnectionFailure(t *testing.T) {
} }
func TestOutdatedNode(t *testing.T) { func TestOutdatedNode(t *testing.T) {
node := NewNode("test") node := NewNode("test", 0)
client := mockclient.NewMockClient() client := mockclient.NewMockClient()
client.On("Info").Return(&dockerclient.Info{}, nil) client.On("Info").Return(&dockerclient.Info{}, nil)
@ -52,7 +52,7 @@ func TestOutdatedNode(t *testing.T) {
} }
func TestNodeCpusMemory(t *testing.T) { func TestNodeCpusMemory(t *testing.T) {
node := NewNode("test") node := NewNode("test", 0)
assert.False(t, node.IsConnected()) assert.False(t, node.IsConnected())
client := mockclient.NewMockClient() client := mockclient.NewMockClient()
@ -71,7 +71,7 @@ func TestNodeCpusMemory(t *testing.T) {
} }
func TestNodeSpecs(t *testing.T) { func TestNodeSpecs(t *testing.T) {
node := NewNode("test") node := NewNode("test", 0)
assert.False(t, node.IsConnected()) assert.False(t, node.IsConnected())
client := mockclient.NewMockClient() client := mockclient.NewMockClient()
@ -95,7 +95,7 @@ func TestNodeSpecs(t *testing.T) {
} }
func TestNodeState(t *testing.T) { func TestNodeState(t *testing.T) {
node := NewNode("test") node := NewNode("test", 0)
assert.False(t, node.IsConnected()) assert.False(t, node.IsConnected())
client := mockclient.NewMockClient() client := mockclient.NewMockClient()
@ -140,7 +140,7 @@ func TestCreateContainer(t *testing.T) {
Cmd: []string{"date"}, Cmd: []string{"date"},
Tty: false, Tty: false,
} }
node = NewNode("test") node = NewNode("test", 0)
client = mockclient.NewMockClient() client = mockclient.NewMockClient()
) )
@ -186,3 +186,23 @@ func TestCreateContainer(t *testing.T) {
assert.Equal(t, container.Id, id) assert.Equal(t, container.Id, id)
assert.Len(t, node.Containers(), 2) assert.Len(t, node.Containers(), 2)
} }
func TestUsableMemory(t *testing.T) {
node := NewNode("test", 0.05)
node.Memory = 1024
assert.Equal(t, node.UsableMemory(), 1024+1024*5/100)
node = NewNode("test", 0)
node.Memory = 1024
assert.Equal(t, node.UsableMemory(), 1024)
}
func TestUsableCpus(t *testing.T) {
node := NewNode("test", 0.05)
node.Cpus = 2
assert.Equal(t, node.UsableCpus(), 2+2*5/100)
node = NewNode("test", 0)
node.Cpus = 2
assert.Equal(t, node.UsableCpus(), 2)
}

View File

@ -50,10 +50,15 @@ var (
Name: "tlsverify", Name: "tlsverify",
Usage: "use TLS and verify the remote", Usage: "use TLS and verify the remote",
} }
flOverCommit = cli.Float64Flag{
Name: "overcommit, oc",
Usage: "overcommit to apply on resources",
Value: 0.05,
}
flStrategy = cli.StringFlag{ flStrategy = cli.StringFlag{
Name: "strategy", Name: "strategy",
Usage: "placement strategy to use [binpacking, random]", Usage: "placement strategy to use [binpacking, random]",
Value: "binpacking:0.05", Value: "binpacking",
} }
flFilter = cli.StringSliceFlag{ flFilter = cli.StringSliceFlag{
Name: "filter, f", Name: "filter, f",

View File

@ -87,7 +87,7 @@ func main() {
Flags: []cli.Flag{ Flags: []cli.Flag{
flDiscovery, flDiscovery,
flStrategy, flFilter, flStrategy, flFilter,
flHosts, flHeartBeat, flHosts, flHeartBeat, flOverCommit,
flTls, flTlsCaCert, flTlsCert, flTlsKey, flTlsVerify, flTls, flTlsCaCert, flTlsCert, flTlsKey, flTlsVerify,
flEnableCors}, flEnableCors},
Action: manage, Action: manage,

View File

@ -71,7 +71,7 @@ func manage(c *cli.Context) {
} }
} }
cluster := cluster.NewCluster(tlsConfig) cluster := cluster.NewCluster(tlsConfig, c.Float64("overcommit"))
cluster.Events(&logHandler{}) cluster.Events(&logHandler{})
if !c.IsSet("discovery") { if !c.IsSet("discovery") {

View File

@ -12,9 +12,9 @@ func TestConstrainteFilter(t *testing.T) {
var ( var (
f = ConstraintFilter{} f = ConstraintFilter{}
nodes = []*cluster.Node{ nodes = []*cluster.Node{
cluster.NewNode("node-0"), cluster.NewNode("node-0", 0),
cluster.NewNode("node-1"), cluster.NewNode("node-1", 0),
cluster.NewNode("node-2"), cluster.NewNode("node-2", 0),
} }
result []*cluster.Node result []*cluster.Node
err error err error

View File

@ -24,9 +24,9 @@ func TestPortFilterNoConflicts(t *testing.T) {
var ( var (
p = PortFilter{} p = PortFilter{}
nodes = []*cluster.Node{ nodes = []*cluster.Node{
cluster.NewNode("node-1"), cluster.NewNode("node-1", 0),
cluster.NewNode("node-2"), cluster.NewNode("node-2", 0),
cluster.NewNode("node-3"), cluster.NewNode("node-3", 0),
} }
result []*cluster.Node result []*cluster.Node
err error err error
@ -70,9 +70,9 @@ func TestPortFilterSimple(t *testing.T) {
var ( var (
p = PortFilter{} p = PortFilter{}
nodes = []*cluster.Node{ nodes = []*cluster.Node{
cluster.NewNode("node-1"), cluster.NewNode("node-1", 0),
cluster.NewNode("node-2"), cluster.NewNode("node-2", 0),
cluster.NewNode("node-3"), cluster.NewNode("node-3", 0),
} }
result []*cluster.Node result []*cluster.Node
err error err error
@ -99,9 +99,9 @@ func TestPortFilterDifferentInterfaces(t *testing.T) {
var ( var (
p = PortFilter{} p = PortFilter{}
nodes = []*cluster.Node{ nodes = []*cluster.Node{
cluster.NewNode("node-1"), cluster.NewNode("node-1", 0),
cluster.NewNode("node-2"), cluster.NewNode("node-2", 0),
cluster.NewNode("node-3"), cluster.NewNode("node-3", 0),
} }
result []*cluster.Node result []*cluster.Node
err error err error

View File

@ -3,7 +3,6 @@ package strategy
import ( import (
"errors" "errors"
"sort" "sort"
"strconv"
"github.com/docker/swarm/cluster" "github.com/docker/swarm/cluster"
"github.com/samalba/dockerclient" "github.com/samalba/dockerclient"
@ -13,22 +12,18 @@ var (
ErrNoResourcesAvailable = errors.New("no resources available to schedule container") ErrNoResourcesAvailable = errors.New("no resources available to schedule container")
) )
type BinPackingPlacementStrategy struct { type BinPackingPlacementStrategy struct{}
ratio int64
}
func (p *BinPackingPlacementStrategy) Initialize(opts string) error { func (p *BinPackingPlacementStrategy) Initialize() error {
overcommitRatio, err := strconv.ParseFloat(opts, 64) return nil
p.ratio = int64(overcommitRatio * 100)
return err
} }
func (p *BinPackingPlacementStrategy) PlaceContainer(config *dockerclient.ContainerConfig, nodes []*cluster.Node) (*cluster.Node, error) { func (p *BinPackingPlacementStrategy) PlaceContainer(config *dockerclient.ContainerConfig, nodes []*cluster.Node) (*cluster.Node, error) {
scores := scores{} scores := scores{}
for _, node := range nodes { for _, node := range nodes {
nodeMemory := node.Memory + (node.Memory * p.ratio / 100) nodeMemory := node.UsableMemory()
nodeCpus := node.Cpus + (node.Cpus * p.ratio / 100) nodeCpus := node.UsableCpus()
// Skip nodes that are smaller than the requested resources. // Skip nodes that are smaller than the requested resources.
if nodeMemory < int64(config.Memory) || nodeCpus < config.CpuShares { if nodeMemory < int64(config.Memory) || nodeCpus < config.CpuShares {

View File

@ -10,7 +10,7 @@ import (
) )
func createNode(ID string, memory int64, cpus int64) *cluster.Node { func createNode(ID string, memory int64, cpus int64) *cluster.Node {
node := cluster.NewNode(ID) node := cluster.NewNode(ID, 0.05)
node.ID = ID node.ID = ID
node.Memory = memory * 1024 * 1024 * 1024 node.Memory = memory * 1024 * 1024 * 1024
node.Cpus = cpus node.Cpus = cpus
@ -114,7 +114,7 @@ func TestPlaceContainerHuge(t *testing.T) {
} }
func TestPlaceContainerOvercommit(t *testing.T) { func TestPlaceContainerOvercommit(t *testing.T) {
s, err := New("binpacking:0.05") s, err := New("binpacking")
assert.NoError(t, err) assert.NoError(t, err)
nodes := []*cluster.Node{createNode("node-1", 0, 1)} nodes := []*cluster.Node{createNode("node-1", 0, 1)}

View File

@ -10,10 +10,9 @@ import (
) )
// Randomly place the container into the cluster. // Randomly place the container into the cluster.
type RandomPlacementStrategy struct { type RandomPlacementStrategy struct{}
}
func (p *RandomPlacementStrategy) Initialize(_ string) error { func (p *RandomPlacementStrategy) Initialize() error {
rand.Seed(time.Now().UTC().UnixNano()) rand.Seed(time.Now().UTC().UnixNano())
return nil return nil
} }

View File

@ -2,7 +2,6 @@ package strategy
import ( import (
"errors" "errors"
"strings"
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/docker/swarm/cluster" "github.com/docker/swarm/cluster"
@ -10,7 +9,7 @@ import (
) )
type PlacementStrategy interface { type PlacementStrategy interface {
Initialize(string) error Initialize() error
// Given a container configuration and a set of nodes, select the target // Given a container configuration and a set of nodes, select the target
// node where the container should be scheduled. // node where the container should be scheduled.
PlaceContainer(config *dockerclient.ContainerConfig, nodes []*cluster.Node) (*cluster.Node, error) PlaceContainer(config *dockerclient.ContainerConfig, nodes []*cluster.Node) (*cluster.Node, error)
@ -28,19 +27,10 @@ func init() {
} }
} }
func New(nameAndOpts string) (PlacementStrategy, error) { func New(name string) (PlacementStrategy, error) {
var (
parts = strings.SplitN(nameAndOpts, ":", 2)
name = parts[0]
opts string
)
if len(parts) == 2 {
opts = parts[1]
}
if strategy, exists := strategies[name]; exists { if strategy, exists := strategies[name]; exists {
log.Debugf("Initializing %q strategy with %q", name, opts) log.Debugf("Initializing %q strategy", name)
err := strategy.Initialize(opts) err := strategy.Initialize()
return strategy, err return strategy, err
} }