Skip to content
This repository was archived by the owner on Feb 1, 2021. It is now read-only.

Commit b39e023

Browse files
committed
Merge pull request #673 from vieux/labels_storage
store constraints and affinities as labels
2 parents 5872d4c + f53b5df commit b39e023

29 files changed

+394
-253
lines changed

api/handlers.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ func postContainersCreate(c *context, w http.ResponseWriter, r *http.Request) {
209209
return
210210
}
211211

212-
container, err := c.cluster.CreateContainer(&config, name)
212+
container, err := c.cluster.CreateContainer(cluster.BuildContainerConfig(&config), name)
213213
if err != nil {
214214
httpError(w, err.Error(), http.StatusInternalServerError)
215215
return

cluster/cluster.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
// Cluster is exported
1010
type Cluster interface {
1111
// Create a container
12-
CreateContainer(config *dockerclient.ContainerConfig, name string) (*Container, error)
12+
CreateContainer(config *ContainerConfig, name string) (*Container, error)
1313

1414
// Remove a container
1515
RemoveContainer(container *Container, force bool) error

cluster/config.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package cluster
2+
3+
import (
4+
"encoding/json"
5+
"strings"
6+
7+
"github.com/samalba/dockerclient"
8+
)
9+
10+
const namespace = "com.docker.swarm"
11+
12+
// ContainerConfig is exported
13+
// TODO store affinities and constraints in their own fields
14+
type ContainerConfig struct {
15+
dockerclient.ContainerConfig
16+
}
17+
18+
func parseEnv(e string) (bool, string, string) {
19+
parts := strings.SplitN(e, ":", 2)
20+
if len(parts) == 2 {
21+
return true, parts[0], parts[1]
22+
}
23+
return false, "", ""
24+
}
25+
26+
// BuildContainerConfig creates a cluster.ContainerConfig from a dockerclient.ContainerConfig
27+
func BuildContainerConfig(c *dockerclient.ContainerConfig) *ContainerConfig {
28+
var (
29+
affinities []string
30+
constraints []string
31+
env []string
32+
)
33+
34+
// only for tests
35+
if c.Labels == nil {
36+
c.Labels = make(map[string]string)
37+
}
38+
39+
// parse affinities from labels (ex. docker run --label 'com.docker.swarm.affinities=["container==redis","image==nginx"]')
40+
if labels, ok := c.Labels[namespace+".affinities"]; ok {
41+
json.Unmarshal([]byte(labels), &affinities)
42+
}
43+
44+
// parse contraints from labels (ex. docker run --label 'com.docker.swarm.constraints=["region==us-east","storage==ssd"]')
45+
if labels, ok := c.Labels[namespace+".constraints"]; ok {
46+
json.Unmarshal([]byte(labels), &constraints)
47+
}
48+
49+
// 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)
50+
for _, e := range c.Env {
51+
if ok, key, value := parseEnv(e); ok && key == "affinity" {
52+
affinities = append(affinities, value)
53+
} else if ok && key == "constraint" {
54+
constraints = append(constraints, value)
55+
} else {
56+
env = append(env, e)
57+
}
58+
}
59+
60+
// remove affinities/contraints from env
61+
c.Env = env
62+
63+
// store affinities in labels
64+
if len(affinities) > 0 {
65+
if labels, err := json.Marshal(affinities); err == nil {
66+
c.Labels[namespace+".affinities"] = string(labels)
67+
}
68+
}
69+
70+
// store contraints in labels
71+
if len(constraints) > 0 {
72+
if labels, err := json.Marshal(constraints); err == nil {
73+
c.Labels[namespace+".constraints"] = string(labels)
74+
}
75+
}
76+
77+
return &ContainerConfig{*c}
78+
}
79+
80+
func (c *ContainerConfig) extractExprs(key string) []string {
81+
var exprs []string
82+
83+
if labels, ok := c.Labels[namespace+"."+key]; ok {
84+
json.Unmarshal([]byte(labels), &exprs)
85+
}
86+
87+
return exprs
88+
}
89+
90+
// Affinities returns all the affinities from the ContainerConfig
91+
func (c *ContainerConfig) Affinities() []string {
92+
return c.extractExprs("affinities")
93+
}
94+
95+
// Constraints returns all the constraints from the ContainerConfig
96+
func (c *ContainerConfig) Constraints() []string {
97+
return c.extractExprs("constraints")
98+
}

cluster/config_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package cluster
2+
3+
import (
4+
"testing"
5+
6+
"github.com/samalba/dockerclient"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestBuildContainerConfig(t *testing.T) {
11+
config := BuildContainerConfig(&dockerclient.ContainerConfig{})
12+
assert.Equal(t, len(config.Env), 0)
13+
assert.Equal(t, len(config.Labels), 0)
14+
15+
config = BuildContainerConfig(&dockerclient.ContainerConfig{Env: []string{"test=true"}})
16+
assert.Equal(t, len(config.Env), 1)
17+
assert.Equal(t, len(config.Labels), 0)
18+
19+
config = BuildContainerConfig(&dockerclient.ContainerConfig{Env: []string{"constraint:test==true"}})
20+
assert.Equal(t, len(config.Env), 0)
21+
assert.Equal(t, len(config.Labels), 1)
22+
23+
config = BuildContainerConfig(&dockerclient.ContainerConfig{Env: []string{"affinity:container==test"}})
24+
assert.Equal(t, len(config.Env), 0)
25+
assert.Equal(t, len(config.Labels), 1)
26+
27+
config = BuildContainerConfig(&dockerclient.ContainerConfig{Env: []string{"test=true", "constraint:test==true", "affinity:container==test"}})
28+
assert.Equal(t, len(config.Env), 1)
29+
assert.Equal(t, len(config.Labels), 2)
30+
}
31+
32+
func TestConstraints(t *testing.T) {
33+
config := BuildContainerConfig(&dockerclient.ContainerConfig{})
34+
assert.Equal(t, len(config.Constraints()), 0)
35+
36+
config = BuildContainerConfig(&dockerclient.ContainerConfig{Env: []string{"constraint:test==true"}})
37+
assert.Equal(t, len(config.Constraints()), 1)
38+
39+
config = BuildContainerConfig(&dockerclient.ContainerConfig{Env: []string{"test=true", "constraint:test==true", "affinity:container==test"}})
40+
assert.Equal(t, len(config.Constraints()), 1)
41+
}
42+
43+
func TestAffinities(t *testing.T) {
44+
config := BuildContainerConfig(&dockerclient.ContainerConfig{})
45+
assert.Equal(t, len(config.Affinities()), 0)
46+
47+
config = BuildContainerConfig(&dockerclient.ContainerConfig{Env: []string{"affinity:container==test"}})
48+
assert.Equal(t, len(config.Affinities()), 1)
49+
50+
config = BuildContainerConfig(&dockerclient.ContainerConfig{Env: []string{"test=true", "constraint:test==true", "affinity:container==test"}})
51+
assert.Equal(t, len(config.Affinities()), 1)
52+
}

cluster/engine.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ func (e *Engine) TotalCpus() int64 {
349349
}
350350

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

364-
if id, err = client.CreateContainer(&newConfig, name); err != nil {
364+
if id, err = client.CreateContainer(&newConfig.ContainerConfig, name); err != nil {
365365
// If the error is other than not found, abort immediately.
366366
if err != dockerclient.ErrNotFound || !pullImage {
367367
return nil, err
@@ -371,7 +371,7 @@ func (e *Engine) Create(config *dockerclient.ContainerConfig, name string, pullI
371371
return nil, err
372372
}
373373
// ...And try agaie.
374-
if id, err = client.CreateContainer(&newConfig, name); err != nil {
374+
if id, err = client.CreateContainer(&newConfig.ContainerConfig, name); err != nil {
375375
return nil, err
376376
}
377377
}

cluster/engine_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -169,12 +169,12 @@ func TestEngineContainerLookup(t *testing.T) {
169169

170170
func TestCreateContainer(t *testing.T) {
171171
var (
172-
config = &dockerclient.ContainerConfig{
172+
config = &ContainerConfig{dockerclient.ContainerConfig{
173173
Image: "busybox",
174174
CpuShares: 1,
175175
Cmd: []string{"date"},
176176
Tty: false,
177-
}
177+
}}
178178
engine = NewEngine("test", 0)
179179
client = mockclient.NewMockClient()
180180
)
@@ -186,7 +186,7 @@ func TestCreateContainer(t *testing.T) {
186186
assert.NoError(t, engine.connectClient(client))
187187
assert.True(t, engine.isConnected())
188188

189-
mockConfig := *config
189+
mockConfig := config.ContainerConfig
190190
mockConfig.CpuShares = config.CpuShares * 1024 / mockInfo.NCPU
191191

192192
// Everything is ok
@@ -195,7 +195,7 @@ func TestCreateContainer(t *testing.T) {
195195
client.On("CreateContainer", &mockConfig, name).Return(id, nil).Once()
196196
client.On("ListContainers", true, false, fmt.Sprintf(`{"id":[%q]}`, id)).Return([]dockerclient.Container{{Id: id}}, nil).Once()
197197
client.On("ListImages").Return([]*dockerclient.Image{}, nil).Once()
198-
client.On("InspectContainer", id).Return(&dockerclient.ContainerInfo{Config: config}, nil).Once()
198+
client.On("InspectContainer", id).Return(&dockerclient.ContainerInfo{Config: &config.ContainerConfig}, nil).Once()
199199
container, err := engine.Create(config, name, false)
200200
assert.Nil(t, err)
201201
assert.Equal(t, container.Id, id)
@@ -218,7 +218,7 @@ func TestCreateContainer(t *testing.T) {
218218
client.On("CreateContainer", &mockConfig, name).Return(id, nil).Once()
219219
client.On("ListContainers", true, false, fmt.Sprintf(`{"id":[%q]}`, id)).Return([]dockerclient.Container{{Id: id}}, nil).Once()
220220
client.On("ListImages").Return([]*dockerclient.Image{}, nil).Once()
221-
client.On("InspectContainer", id).Return(&dockerclient.ContainerInfo{Config: config}, nil).Once()
221+
client.On("InspectContainer", id).Return(&dockerclient.ContainerInfo{Config: &config.ContainerConfig}, nil).Once()
222222
container, err = engine.Create(config, name, true)
223223
assert.Nil(t, err)
224224
assert.Equal(t, container.Id, id)

cluster/swarm/cluster.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func (c *Cluster) RegisterEventHandler(h cluster.EventHandler) error {
8181
}
8282

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

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

377377
// RANDOMENGINE returns a random engine.
378378
func (c *Cluster) RANDOMENGINE() (*cluster.Engine, error) {
379-
n, err := c.scheduler.SelectNodeForContainer(c.listNodes(), &dockerclient.ContainerConfig{})
379+
n, err := c.scheduler.SelectNodeForContainer(c.listNodes(), &cluster.ContainerConfig{})
380380
if err != nil {
381381
return nil, err
382382
}

scheduler/filter/affinity.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import (
55
"strings"
66

77
log "github.com/Sirupsen/logrus"
8+
"github.com/docker/swarm/cluster"
89
"github.com/docker/swarm/scheduler/node"
9-
"github.com/samalba/dockerclient"
1010
)
1111

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

2121
// Filter is exported
22-
func (f *AffinityFilter) Filter(config *dockerclient.ContainerConfig, nodes []*node.Node) ([]*node.Node, error) {
23-
affinities, err := parseExprs("affinity", config.Env)
22+
func (f *AffinityFilter) Filter(config *cluster.ContainerConfig, nodes []*node.Node) ([]*node.Node, error) {
23+
affinities, err := parseExprs(config.Affinities())
2424
if err != nil {
2525
return nil, err
2626
}

0 commit comments

Comments
 (0)