Skip to content
This repository was archived by the owner on Feb 1, 2021. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ func postContainersCreate(c *context, w http.ResponseWriter, r *http.Request) {
return
}

container, err := c.cluster.CreateContainer(&config, name)
container, err := c.cluster.CreateContainer(cluster.BuildContainerConfig(&config), name)
if err != nil {
httpError(w, err.Error(), http.StatusInternalServerError)
return
Expand Down
2 changes: 1 addition & 1 deletion cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
// Cluster is exported
type Cluster interface {
// Create a container
CreateContainer(config *dockerclient.ContainerConfig, name string) (*Container, error)
CreateContainer(config *ContainerConfig, name string) (*Container, error)

// Remove a container
RemoveContainer(container *Container, force bool) error
Expand Down
98 changes: 98 additions & 0 deletions cluster/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package cluster

import (
"encoding/json"
"strings"

"github.com/samalba/dockerclient"
)

const namespace = "com.docker.swarm"

// ContainerConfig is exported
// TODO store affinities and constraints in their own fields
type ContainerConfig struct {
dockerclient.ContainerConfig
}

func parseEnv(e string) (bool, string, string) {
parts := strings.SplitN(e, ":", 2)
if len(parts) == 2 {
return true, parts[0], parts[1]
}
return false, "", ""
}

// BuildContainerConfig creates a cluster.ContainerConfig from a dockerclient.ContainerConfig
func BuildContainerConfig(c *dockerclient.ContainerConfig) *ContainerConfig {
var (
affinities []string
constraints []string
env []string
)

// only for tests
if c.Labels == nil {
c.Labels = make(map[string]string)
}

// parse affinities from labels (ex. docker run --label 'com.docker.swarm.affinities=["container==redis","image==nginx"]')
if labels, ok := c.Labels[namespace+".affinities"]; ok {
json.Unmarshal([]byte(labels), &affinities)
}

// parse contraints from labels (ex. docker run --label 'com.docker.swarm.constraints=["region==us-east","storage==ssd"]')
if labels, ok := c.Labels[namespace+".constraints"]; ok {
json.Unmarshal([]byte(labels), &constraints)
}

// parse affinities/contraints from env (ex. docker run -e affinity:container==redis -e affinity:image==nginx -e constraint:region==us-east -e constraint:storage==ssd)
for _, e := range c.Env {
if ok, key, value := parseEnv(e); ok && key == "affinity" {
affinities = append(affinities, value)
} else if ok && key == "constraint" {
constraints = append(constraints, value)
} else {
env = append(env, e)
}
}

// remove affinities/contraints from env
c.Env = env

// store affinities in labels
if len(affinities) > 0 {
if labels, err := json.Marshal(affinities); err == nil {
c.Labels[namespace+".affinities"] = string(labels)
}
}

// store contraints in labels
if len(constraints) > 0 {
if labels, err := json.Marshal(constraints); err == nil {
c.Labels[namespace+".constraints"] = string(labels)
}
}

return &ContainerConfig{*c}
}

func (c *ContainerConfig) extractExprs(key string) []string {
var exprs []string

if labels, ok := c.Labels[namespace+"."+key]; ok {
json.Unmarshal([]byte(labels), &exprs)
}

return exprs
}

// Affinities returns all the affinities from the ContainerConfig
func (c *ContainerConfig) Affinities() []string {
return c.extractExprs("affinities")
}

// Constraints returns all the constraints from the ContainerConfig
func (c *ContainerConfig) Constraints() []string {
return c.extractExprs("constraints")
}
52 changes: 52 additions & 0 deletions cluster/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package cluster

import (
"testing"

"github.com/samalba/dockerclient"
"github.com/stretchr/testify/assert"
)

func TestBuildContainerConfig(t *testing.T) {
config := BuildContainerConfig(&dockerclient.ContainerConfig{})
assert.Equal(t, len(config.Env), 0)
assert.Equal(t, len(config.Labels), 0)

config = BuildContainerConfig(&dockerclient.ContainerConfig{Env: []string{"test=true"}})
assert.Equal(t, len(config.Env), 1)
assert.Equal(t, len(config.Labels), 0)

config = BuildContainerConfig(&dockerclient.ContainerConfig{Env: []string{"constraint:test==true"}})
assert.Equal(t, len(config.Env), 0)
assert.Equal(t, len(config.Labels), 1)

config = BuildContainerConfig(&dockerclient.ContainerConfig{Env: []string{"affinity:container==test"}})
assert.Equal(t, len(config.Env), 0)
assert.Equal(t, len(config.Labels), 1)

config = BuildContainerConfig(&dockerclient.ContainerConfig{Env: []string{"test=true", "constraint:test==true", "affinity:container==test"}})
assert.Equal(t, len(config.Env), 1)
assert.Equal(t, len(config.Labels), 2)
}

func TestConstraints(t *testing.T) {
config := BuildContainerConfig(&dockerclient.ContainerConfig{})
assert.Equal(t, len(config.Constraints()), 0)

config = BuildContainerConfig(&dockerclient.ContainerConfig{Env: []string{"constraint:test==true"}})
assert.Equal(t, len(config.Constraints()), 1)

config = BuildContainerConfig(&dockerclient.ContainerConfig{Env: []string{"test=true", "constraint:test==true", "affinity:container==test"}})
assert.Equal(t, len(config.Constraints()), 1)
}

func TestAffinities(t *testing.T) {
config := BuildContainerConfig(&dockerclient.ContainerConfig{})
assert.Equal(t, len(config.Affinities()), 0)

config = BuildContainerConfig(&dockerclient.ContainerConfig{Env: []string{"affinity:container==test"}})
assert.Equal(t, len(config.Affinities()), 1)

config = BuildContainerConfig(&dockerclient.ContainerConfig{Env: []string{"test=true", "constraint:test==true", "affinity:container==test"}})
assert.Equal(t, len(config.Affinities()), 1)
}
6 changes: 3 additions & 3 deletions cluster/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ func (e *Engine) TotalCpus() int64 {
}

// Create a new container
func (e *Engine) Create(config *dockerclient.ContainerConfig, name string, pullImage bool) (*Container, error) {
func (e *Engine) Create(config *ContainerConfig, name string, pullImage bool) (*Container, error) {
var (
err error
id string
Expand All @@ -361,7 +361,7 @@ func (e *Engine) Create(config *dockerclient.ContainerConfig, name string, pullI
// nb of CPUs -> real CpuShares
newConfig.CpuShares = config.CpuShares * 1024 / e.Cpus

if id, err = client.CreateContainer(&newConfig, name); err != nil {
if id, err = client.CreateContainer(&newConfig.ContainerConfig, name); err != nil {
// If the error is other than not found, abort immediately.
if err != dockerclient.ErrNotFound || !pullImage {
return nil, err
Expand All @@ -371,7 +371,7 @@ func (e *Engine) Create(config *dockerclient.ContainerConfig, name string, pullI
return nil, err
}
// ...And try agaie.
if id, err = client.CreateContainer(&newConfig, name); err != nil {
if id, err = client.CreateContainer(&newConfig.ContainerConfig, name); err != nil {
return nil, err
}
}
Expand Down
10 changes: 5 additions & 5 deletions cluster/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,12 +169,12 @@ func TestEngineContainerLookup(t *testing.T) {

func TestCreateContainer(t *testing.T) {
var (
config = &dockerclient.ContainerConfig{
config = &ContainerConfig{dockerclient.ContainerConfig{
Image: "busybox",
CpuShares: 1,
Cmd: []string{"date"},
Tty: false,
}
}}
engine = NewEngine("test", 0)
client = mockclient.NewMockClient()
)
Expand All @@ -186,7 +186,7 @@ func TestCreateContainer(t *testing.T) {
assert.NoError(t, engine.connectClient(client))
assert.True(t, engine.isConnected())

mockConfig := *config
mockConfig := config.ContainerConfig
mockConfig.CpuShares = config.CpuShares * 1024 / mockInfo.NCPU

// Everything is ok
Expand All @@ -195,7 +195,7 @@ func TestCreateContainer(t *testing.T) {
client.On("CreateContainer", &mockConfig, name).Return(id, nil).Once()
client.On("ListContainers", true, false, fmt.Sprintf(`{"id":[%q]}`, id)).Return([]dockerclient.Container{{Id: id}}, nil).Once()
client.On("ListImages").Return([]*dockerclient.Image{}, nil).Once()
client.On("InspectContainer", id).Return(&dockerclient.ContainerInfo{Config: config}, nil).Once()
client.On("InspectContainer", id).Return(&dockerclient.ContainerInfo{Config: &config.ContainerConfig}, nil).Once()
container, err := engine.Create(config, name, false)
assert.Nil(t, err)
assert.Equal(t, container.Id, id)
Expand All @@ -218,7 +218,7 @@ func TestCreateContainer(t *testing.T) {
client.On("CreateContainer", &mockConfig, name).Return(id, nil).Once()
client.On("ListContainers", true, false, fmt.Sprintf(`{"id":[%q]}`, id)).Return([]dockerclient.Container{{Id: id}}, nil).Once()
client.On("ListImages").Return([]*dockerclient.Image{}, nil).Once()
client.On("InspectContainer", id).Return(&dockerclient.ContainerInfo{Config: config}, nil).Once()
client.On("InspectContainer", id).Return(&dockerclient.ContainerInfo{Config: &config.ContainerConfig}, nil).Once()
container, err = engine.Create(config, name, true)
assert.Nil(t, err)
assert.Equal(t, container.Id, id)
Expand Down
4 changes: 2 additions & 2 deletions cluster/swarm/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func (c *Cluster) RegisterEventHandler(h cluster.EventHandler) error {
}

// CreateContainer aka schedule a brand new container into the cluster.
func (c *Cluster) CreateContainer(config *dockerclient.ContainerConfig, name string) (*cluster.Container, error) {
func (c *Cluster) CreateContainer(config *cluster.ContainerConfig, name string) (*cluster.Container, error) {
c.scheduler.Lock()
defer c.scheduler.Unlock()

Expand Down Expand Up @@ -376,7 +376,7 @@ func (c *Cluster) Info() [][2]string {

// RANDOMENGINE returns a random engine.
func (c *Cluster) RANDOMENGINE() (*cluster.Engine, error) {
n, err := c.scheduler.SelectNodeForContainer(c.listNodes(), &dockerclient.ContainerConfig{})
n, err := c.scheduler.SelectNodeForContainer(c.listNodes(), &cluster.ContainerConfig{})
if err != nil {
return nil, err
}
Expand Down
6 changes: 3 additions & 3 deletions scheduler/filter/affinity.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import (
"strings"

log "github.com/Sirupsen/logrus"
"github.com/docker/swarm/cluster"
"github.com/docker/swarm/scheduler/node"
"github.com/samalba/dockerclient"
)

// AffinityFilter selects only nodes based on other containers on the node.
Expand All @@ -19,8 +19,8 @@ func (f *AffinityFilter) Name() string {
}

// Filter is exported
func (f *AffinityFilter) Filter(config *dockerclient.ContainerConfig, nodes []*node.Node) ([]*node.Node, error) {
affinities, err := parseExprs("affinity", config.Env)
func (f *AffinityFilter) Filter(config *cluster.ContainerConfig, nodes []*node.Node) ([]*node.Node, error) {
affinities, err := parseExprs(config.Affinities())
if err != nil {
return nil, err
}
Expand Down
Loading