Merge lp:~dimitern/juju-core/460-network-constraints into lp:~go-bot/juju-core/trunk
- 460-network-constraints
- Merge into trunk
Status: | Rejected |
---|---|
Rejected by: | Dimiter Naydenov |
Proposed branch: | lp:~dimitern/juju-core/460-network-constraints |
Merge into: | lp:~go-bot/juju-core/trunk |
Diff against target: |
765 lines (+269/-131) 7 files modified
cmd/juju/deploy.go (+42/-15) cmd/juju/deploy_test.go (+8/-5) cmd/juju/help_topics.go (+7/-0) constraints/constraints.go (+68/-10) constraints/constraints_test.go (+96/-63) state/constraints.go (+3/-0) state/constraintsvalidation_test.go (+45/-38) |
To merge this branch: | bzr merge lp:~dimitern/juju-core/460-network-constraints |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Engineering | Pending | ||
Review via email: mp+221390@code.launchpad.net |
Commit message
Description of the change
various: Introduce networks constraints
Following the removal of --exclude-networks argument
from juju deploy, now we're introducting a complete
way to specify networks with constraints:
--constraints networks=
Means "find a machine with net1 and net2 networks,
but not with net3 or net4 networks".
Changes are in 3 places: constraints package, state
(for saving/loading) and cmd/juju/deploy for the CLI.
Now, to specify networks to include/exclude on deploy:
juju deploy svc --networks=
Means: "deploy svc on machines with logging, db, and storage
networks, but not with dmz network".
Only the those, specified with --networks will be enabled
on the machine, once the networker worker is done. The ones
specified with constraints are used as a filter for instance
selection.
Dimiter Naydenov (dimitern) wrote : | # |
William Reade (fwereade) wrote : | # |
If you dropped the changes to cmd/juju I'd approve this (with just a
little bit of extra checking of the values when you parse network
constraints).
https:/
File cmd/juju/deploy.go (right):
https:/
cmd/juju/
--constraints ^logging,db
--constraints networks=
https:/
cmd/juju/
mergeNetworks(
c.Constraints.
This is definitely wrong. This'd put the ones in constraints into the
set that got configured on the machine, instead of the ones used *only*
for machine *selection*, as they should be.
https:/
cmd/juju/
c.Constraints.
And I'm pretty sure this is wrong too. The information's already in
constraints and doesn't need to be duplicated.
https:/
cmd/juju/
I'm a bit concerned about this. I know we currently need the environ to
talk to the charm store, but I think this will change as we move the
charm store into the environment; I'd prefer not to add more
dependencies on it at this layer (apart from everything else, we can't
assume that the average user has permission to look at the env config).
Just do the checking in the API server (which you have to do anyway, you
might get any request from the GUI/other client).
https:/
cmd/juju/
includeNetworks could stay, if it had the right information in it, but
I'm certain we don't want this. If we haven't released a stable version
with --include/
are dev versions for, if not for making mistakes? ;p
https:/
cmd/juju/
cmd.StringsValue
https:/
cmd/juju/
We should *always* turn the user's strings into tags. If they try to
specify network-foo, send network-network-foo over the API.
https:/
File constraints/
https:/
constraints/
I'm wondering whether we should be restricting the values here, at least
in the networks case. Remember these are meant to refer to juju network
names, not provider ones (where we can't control validity).
Tags may be a bit different; not sure if we can sanely restrict them,
even if we'll get surprising results if they ever contain ",", but I
think we can and should keep network names locked down.
ht...
Dimiter Naydenov (dimitern) wrote : | # |
Sorry, what I meant by "follow-up" is actually a prerequisite:
https:/
What I've said below is done, it's there.
I'll repropose this later with the changes to cmd/juju mostly.
https:/
File cmd/juju/deploy.go (right):
https:/
cmd/juju/
--constraints ^logging,db
On 2014/05/29 18:22:36, fwereade wrote:
> --constraints networks=
Done in a follow-up.
https:/
cmd/juju/
mergeNetworks(
c.Constraints.
On 2014/05/29 18:22:36, fwereade wrote:
> This is definitely wrong. This'd put the ones in constraints into the
set that
> got configured on the machine, instead of the ones used *only* for
machine
> *selection*, as they should be.
Taking your suggestion, I'm dropping cmd/juju changes from this branch
and will propose it as a follow-up, fixing these issues.
https:/
cmd/juju/
c.Constraints.
On 2014/05/29 18:22:36, fwereade wrote:
> And I'm pretty sure this is wrong too. The information's already in
constraints
> and doesn't need to be duplicated.
Done in a follow-up.
https:/
cmd/juju/
On 2014/05/29 18:22:36, fwereade wrote:
> I'm a bit concerned about this. I know we currently need the environ
to talk to
> the charm store, but I think this will change as we move the charm
store into
> the environment; I'd prefer not to add more dependencies on it at this
layer
> (apart from everything else, we can't assume that the average user has
> permission to look at the env config). Just do the checking in the API
server
> (which you have to do anyway, you might get any request from the
GUI/other
> client).
Check the follow-up. Doing this check here, in addition to a check in
the API server (sorry, I thought I had that there already - will add it
there as well) will allow us to bail out early, before the API call with
a nicer CLI-specific message, like "cannot use --networks: not supported
by the environmen" instead of "cannot deploy "wordpress" with networks:
not supported by the environment". I'm with having only the latter, less
specific error message.
https:/
cmd/juju/
On 2014/05/29 18:22:36, fwereade wrote:
> includeNetworks could stay, if it had the right information in it, but
I'm
> certain we don't want this. If we haven't released a stable version
with
> --include/
dev
> versions for, if not for making mistakes? ;p
Good point. In the follow-up, I'll drop that and modify
service.Networks() to return include/
taking both requested networks and...
Dimiter Naydenov (dimitern) wrote : | # |
This got migrated to github:
https:/
Lots of changes done, suggestions applied.
Preview Diff
1 | === modified file 'cmd/juju/deploy.go' |
2 | --- cmd/juju/deploy.go 2014-05-28 15:07:03 +0000 |
3 | +++ cmd/juju/deploy.go 2014-05-29 14:17:10 +0000 |
4 | @@ -22,6 +22,7 @@ |
5 | "launchpad.net/juju-core/names" |
6 | "launchpad.net/juju-core/state/api" |
7 | "launchpad.net/juju-core/state/api/params" |
8 | + "launchpad.net/juju-core/utils/set" |
9 | ) |
10 | |
11 | type DeployCommand struct { |
12 | @@ -88,16 +89,20 @@ |
13 | juju deploy mysql --to 24/lxc/3 (deploy to lxc container 3 on host machine 24) |
14 | juju deploy mysql --to lxc:25 (deploy to a new lxc container on host machine 25) |
15 | |
16 | - juju deploy mysql -n 5 --constraints mem=8G (deploy 5 instances of mysql with at least 8 GB of RAM each) |
17 | + juju deploy mysql -n 5 --constraints mem=8G |
18 | + (deploy 5 instances of mysql with at least 8 GB of RAM each) |
19 | |
20 | - juju deploy mysql --networks=storage,mynet |
21 | + juju deploy mysql --networks=storage,mynet --constraints ^logging,db |
22 | + (deploy mysql on machines with "storage", "mynet" and "db" networks, |
23 | + but not on machines with "logging" network) |
24 | |
25 | Like constraints, service-specific network requirements can be |
26 | specified with the --networks argument, which takes a comma-delimited |
27 | -list of provider-specific network names/labels. |
28 | -These instruct juju to ensure to add all the networks specified with |
29 | ---networks to all new machines deployed to host units of the service. |
30 | -Not supported on all providers. |
31 | +list of juju-specific network names. Networks can also be specified with |
32 | +constraints, but they only define what machine to pick, not what networks |
33 | +to configure on it. The --networks argument instructs juju to add all the |
34 | +networks specified with it to all new machines deployed to host units of |
35 | +the service. Not supported on all providers. |
36 | |
37 | See Also: |
38 | juju help constraints |
39 | @@ -182,16 +187,17 @@ |
40 | if c.BumpRevision { |
41 | ctx.Infof("--upgrade (or -u) is deprecated and ignored; charms are always deployed with a unique revision.") |
42 | } |
43 | - var includeNetworks []string |
44 | - if c.Networks != "" { |
45 | - includeNetworks = parseNetworks(c.Networks) |
46 | |
47 | + includeNetworks := mergeNetworks(parseNetworks(c.Networks), c.Constraints.IncludeNetworks()) |
48 | + excludeNetworks := mergeNetworks(nil, c.Constraints.ExcludeNetworks()) |
49 | + haveNetworks := len(includeNetworks) > 0 || len(excludeNetworks) > 0 |
50 | + if haveNetworks { |
51 | env, err := environs.New(conf) |
52 | if err != nil { |
53 | return err |
54 | } |
55 | if !env.SupportNetworks() { |
56 | - return errors.New("cannot use --networks: not supported by the environment") |
57 | + return errors.New("cannot use --networks/--constraints networks=...: not supported by the environment") |
58 | } |
59 | } |
60 | |
61 | @@ -231,11 +237,11 @@ |
62 | c.Constraints, |
63 | c.ToMachineSpec, |
64 | includeNetworks, |
65 | - nil, |
66 | + excludeNetworks, |
67 | ) |
68 | if params.IsCodeNotImplemented(err) { |
69 | - if len(includeNetworks) > 0 { |
70 | - return errors.New("cannot use --networks: not supported by the API server") |
71 | + if haveNetworks { |
72 | + return errors.New("cannot use --networks/--constraints networks=...: not supported by the API server") |
73 | } |
74 | err = client.ServiceDeploy( |
75 | curl.String(), |
76 | @@ -282,15 +288,36 @@ |
77 | return curl, nil |
78 | } |
79 | |
80 | -// parseNetworks returns a list of network tags by parsing the |
81 | +// parseNetworks returns a list of networks by parsing the |
82 | // comma-delimited string value of --networks argument. |
83 | func parseNetworks(networksValue string) (networks []string) { |
84 | + if networksValue == "" { |
85 | + return networks |
86 | + } |
87 | parts := strings.Split(networksValue, ",") |
88 | for _, part := range parts { |
89 | network := strings.TrimSpace(part) |
90 | if network != "" { |
91 | - networks = append(networks, names.NetworkTag(network)) |
92 | + networks = append(networks, network) |
93 | } |
94 | } |
95 | return networks |
96 | } |
97 | + |
98 | +// mergeNetworks returns a sorted list of unique network tags, merging |
99 | +// the given slices. |
100 | +func mergeNetworks(fromNetworks, fromConstraints []string) []string { |
101 | + result := set.NewStrings() |
102 | + mergeFrom := func(networks []string) { |
103 | + for _, netName := range networks { |
104 | + if _, _, err := names.ParseTag(netName, names.NetworkTagKind); err != nil { |
105 | + // It's not a tag, convert it. |
106 | + netName = names.NetworkTag(netName) |
107 | + } |
108 | + result.Add(netName) |
109 | + } |
110 | + } |
111 | + mergeFrom(fromNetworks) |
112 | + mergeFrom(fromConstraints) |
113 | + return result.SortedValues() |
114 | +} |
115 | |
116 | === modified file 'cmd/juju/deploy_test.go' |
117 | --- cmd/juju/deploy_test.go 2014-05-28 15:07:03 +0000 |
118 | +++ cmd/juju/deploy_test.go 2014-05-29 14:17:10 +0000 |
119 | @@ -161,25 +161,28 @@ |
120 | |
121 | func (s *DeploySuite) TestConstraints(c *gc.C) { |
122 | coretesting.Charms.BundlePath(s.SeriesPath, "dummy") |
123 | - err := runDeploy(c, "local:dummy", "--constraints", "mem=2G cpu-cores=2") |
124 | + err := runDeploy(c, "local:dummy", "--constraints", "mem=2G cpu-cores=2 networks=net1,^net2") |
125 | c.Assert(err, gc.IsNil) |
126 | curl := charm.MustParseURL("local:precise/dummy-1") |
127 | service, _ := s.AssertService(c, "dummy", curl, 1, 0) |
128 | cons, err := service.Constraints() |
129 | c.Assert(err, gc.IsNil) |
130 | - c.Assert(cons, gc.DeepEquals, constraints.MustParse("mem=2G cpu-cores=2")) |
131 | + c.Assert(cons, jc.DeepEquals, constraints.MustParse("mem=2G cpu-cores=2 networks=net1,^net2")) |
132 | } |
133 | |
134 | func (s *DeploySuite) TestNetworks(c *gc.C) { |
135 | coretesting.Charms.BundlePath(s.SeriesPath, "dummy") |
136 | - err := runDeploy(c, "local:dummy", "--networks", ", net1, net2 , ") |
137 | + err := runDeploy(c, "local:dummy", "--networks", ", net1, net2 , ", "--constraints", "mem=2G cpu-cores=2 networks=net1,net0,^net3,^net4") |
138 | c.Assert(err, gc.IsNil) |
139 | curl := charm.MustParseURL("local:precise/dummy-1") |
140 | service, _ := s.AssertService(c, "dummy", curl, 1, 0) |
141 | includeNetworks, excludeNetworks, err := service.Networks() |
142 | c.Assert(err, gc.IsNil) |
143 | - c.Assert(includeNetworks, gc.DeepEquals, []string{"net1", "net2"}) |
144 | - c.Assert(excludeNetworks, gc.HasLen, 0) |
145 | + c.Assert(includeNetworks, jc.DeepEquals, []string{"net0", "net1", "net2"}) |
146 | + c.Assert(excludeNetworks, jc.DeepEquals, []string{"net3", "net4"}) |
147 | + cons, err := service.Constraints() |
148 | + c.Assert(err, gc.IsNil) |
149 | + c.Assert(cons, jc.DeepEquals, constraints.MustParse("mem=2G cpu-cores=2 networks=net1,net0,^net3,^net4")) |
150 | } |
151 | |
152 | func (s *DeploySuite) TestSubordinateConstraints(c *gc.C) { |
153 | |
154 | === modified file 'cmd/juju/help_topics.go' |
155 | --- cmd/juju/help_topics.go 2014-05-15 10:12:36 +0000 |
156 | +++ cmd/juju/help_topics.go 2014-05-29 14:17:10 +0000 |
157 | @@ -324,6 +324,13 @@ |
158 | Multiple tags must be delimited by a comma. Tags are currently only supported |
159 | by the MaaS environment. |
160 | |
161 | +networks |
162 | + Networks defines the list of networks to ensure are available or not on the |
163 | + machine. Both positive and negative network constraints can be specified, the |
164 | + later have a "^" prefix to the name. Multiple netwokrs must be delimited by a |
165 | + comma. Not supported on all providers. Example: networks=storage,db,^logging |
166 | + specifies to enable "storage" and "db" networks but not "logging" on the machine. |
167 | + |
168 | Example: |
169 | |
170 | juju add-machine --constraints "arch=amd64 mem=8G tags=foo,bar" |
171 | |
172 | === modified file 'constraints/constraints.go' |
173 | --- constraints/constraints.go 2014-05-13 04:50:10 +0000 |
174 | +++ constraints/constraints.go 2014-05-29 14:17:10 +0000 |
175 | @@ -27,6 +27,7 @@ |
176 | RootDisk = "root-disk" |
177 | Tags = "tags" |
178 | InstanceType = "instance-type" |
179 | + Networks = "networks" |
180 | ) |
181 | |
182 | // Value describes a user's requirements of the hardware on which units |
183 | @@ -70,6 +71,12 @@ |
184 | // InstanceType, if not nil, indicates that the specified cloud instance type |
185 | // be used. Only valid for clouds which support instance types. |
186 | InstanceType *string `json:"instance-type,omitempty" yaml:"instance-type,omitempty"` |
187 | + |
188 | + // Networks, if not nil, holds a list of juju network names that |
189 | + // should be available (or not) on the machine. Positive and |
190 | + // negative values are accepted, and the difference is the latter |
191 | + // have a "^" prefix to the name. |
192 | + Networks *[]string `json:"networks,omitempty" yaml:"networks,omitempty"` |
193 | } |
194 | |
195 | // fieldNames records a mapping from the constraint tag to struct field name. |
196 | @@ -107,6 +114,40 @@ |
197 | return v.InstanceType != nil && *v.InstanceType != "" |
198 | } |
199 | |
200 | +// extractNetworks returns a list of positive (toInclude=true), |
201 | +// negative (toExclude=true), or both from the networks constraint (if |
202 | +// set). |
203 | +func (v *Value) extractNetworks(toInclude, toExclude bool) []string { |
204 | + var nets []string |
205 | + if v.Networks != nil { |
206 | + for _, name := range *v.Networks { |
207 | + if strings.HasPrefix(name, "^") { |
208 | + if toExclude { |
209 | + nets = append(nets, strings.TrimPrefix(name, "^")) |
210 | + } |
211 | + } else { |
212 | + if toInclude { |
213 | + nets = append(nets, name) |
214 | + } |
215 | + } |
216 | + } |
217 | + } |
218 | + return nets |
219 | +} |
220 | + |
221 | +// IncludeNetworks returns a list of networks to include when starting |
222 | +// a machine, if specified. |
223 | +func (v *Value) IncludeNetworks() []string { |
224 | + return v.extractNetworks(true, false) |
225 | +} |
226 | + |
227 | +// ExcludeNetworks returns a list of networks to exclude when starting |
228 | +// a machine, if specified. They are given in the networks constraint |
229 | +// with a "^" prefix to the name, which is stripped before returning. |
230 | +func (v *Value) ExcludeNetworks() []string { |
231 | + return v.extractNetworks(false, true) |
232 | +} |
233 | + |
234 | // String expresses a constraints.Value in the language in which it was specified. |
235 | func (v Value) String() string { |
236 | var strs []string |
237 | @@ -143,6 +184,10 @@ |
238 | s := strings.Join(*v.Tags, ",") |
239 | strs = append(strs, "tags="+s) |
240 | } |
241 | + if v.Networks != nil { |
242 | + s := strings.Join(*v.Networks, ",") |
243 | + strs = append(strs, "networks="+s) |
244 | + } |
245 | return strings.Join(strs, " ") |
246 | } |
247 | |
248 | @@ -270,6 +315,8 @@ |
249 | err = v.setTags(str) |
250 | case InstanceType: |
251 | err = v.setInstanceType(str) |
252 | + case Networks: |
253 | + err = v.setNetworks(str) |
254 | default: |
255 | return fmt.Errorf("unknown constraint %q", name) |
256 | } |
257 | @@ -309,7 +356,9 @@ |
258 | case RootDisk: |
259 | v.RootDisk, err = parseUint64(vstr) |
260 | case Tags: |
261 | - v.Tags, err = parseYamlTags(val) |
262 | + v.Tags, err = parseYamlStrings("tags", val) |
263 | + case Networks: |
264 | + v.Networks, err = parseYamlStrings("networks", val) |
265 | default: |
266 | return false |
267 | } |
268 | @@ -397,7 +446,15 @@ |
269 | if v.Tags != nil { |
270 | return fmt.Errorf("already set") |
271 | } |
272 | - v.Tags = parseTags(str) |
273 | + v.Tags = parseCommaDelimited(str) |
274 | + return nil |
275 | +} |
276 | + |
277 | +func (v *Value) setNetworks(str string) error { |
278 | + if v.Networks != nil { |
279 | + return fmt.Errorf("already set") |
280 | + } |
281 | + v.Networks = parseCommaDelimited(str) |
282 | return nil |
283 | } |
284 | |
285 | @@ -431,8 +488,9 @@ |
286 | return &value, nil |
287 | } |
288 | |
289 | -// parseTags returns the tags in the value s. We expect the tags to be comma delimited strings. |
290 | -func parseTags(s string) *[]string { |
291 | +// parseCommaDelimited returns the items in the value s. We expect the |
292 | +// tags to be comma delimited strings. It is used for tags and networks. |
293 | +func parseCommaDelimited(s string) *[]string { |
294 | if s == "" { |
295 | return &[]string{} |
296 | } |
297 | @@ -440,20 +498,20 @@ |
298 | return &t |
299 | } |
300 | |
301 | -func parseYamlTags(val interface{}) (*[]string, error) { |
302 | +func parseYamlStrings(entityName string, val interface{}) (*[]string, error) { |
303 | ifcs, ok := val.([]interface{}) |
304 | if !ok { |
305 | - return nil, fmt.Errorf("unexpected type passed to tags: %T", val) |
306 | + return nil, fmt.Errorf("unexpected type passed to %s: %T", entityName, val) |
307 | } |
308 | - tags := make([]string, len(ifcs)) |
309 | + items := make([]string, len(ifcs)) |
310 | for n, ifc := range ifcs { |
311 | s, ok := ifc.(string) |
312 | if !ok { |
313 | - return nil, fmt.Errorf("unexpected type passed as a tag: %T", ifc) |
314 | + return nil, fmt.Errorf("unexpected type passed as in %s: %T", entityName, ifc) |
315 | } |
316 | - tags[n] = s |
317 | + items[n] = s |
318 | } |
319 | - return &tags, nil |
320 | + return &items, nil |
321 | } |
322 | |
323 | var mbSuffixes = map[string]float64{ |
324 | |
325 | === modified file 'constraints/constraints_test.go' |
326 | --- constraints/constraints_test.go 2014-04-24 00:00:36 +0000 |
327 | +++ constraints/constraints_test.go 2014-05-29 14:17:10 +0000 |
328 | @@ -263,6 +263,21 @@ |
329 | args: []string{"tags="}, |
330 | }, |
331 | |
332 | + // networks |
333 | + { |
334 | + summary: "single network", |
335 | + args: []string{"networks=net1"}, |
336 | + }, { |
337 | + summary: "multiple networks - positive", |
338 | + args: []string{"networks=net1,net2"}, |
339 | + }, { |
340 | + summary: "multiple networks - positive and negative", |
341 | + args: []string{"networks=net1,^net2,net3,^net4"}, |
342 | + }, { |
343 | + summary: "no networks", |
344 | + args: []string{"networks="}, |
345 | + }, |
346 | + |
347 | // instance type |
348 | { |
349 | summary: "set instance type", |
350 | @@ -277,12 +292,12 @@ |
351 | summary: "kitchen sink together", |
352 | args: []string{ |
353 | "root-disk=8G mem=2T arch=i386 cpu-cores=4096 cpu-power=9001 container=lxc " + |
354 | - "tags=foo,bar instance-type=foo"}, |
355 | + "tags=foo,bar networks=net1,^net2 instance-type=foo"}, |
356 | }, { |
357 | summary: "kitchen sink separately", |
358 | args: []string{ |
359 | "root-disk=8G", "mem=2T", "cpu-cores=4096", "cpu-power=9001", "arch=armhf", |
360 | - "container=lxc", "tags=foo,bar", "instance-type=foo"}, |
361 | + "container=lxc", "tags=foo,bar", "networks=net1,^net2", "instance-type=foo"}, |
362 | }, |
363 | } |
364 | |
365 | @@ -302,15 +317,26 @@ |
366 | } |
367 | } |
368 | |
369 | -func (s *ConstraintsSuite) TestParseMissingTags(c *gc.C) { |
370 | +func (s *ConstraintsSuite) TestParseMissingTagsAndNetworks(c *gc.C) { |
371 | con := constraints.MustParse("arch=amd64 mem=4G cpu-cores=1 root-disk=8G") |
372 | c.Check(con.Tags, gc.IsNil) |
373 | + c.Check(con.Networks, gc.IsNil) |
374 | } |
375 | |
376 | -func (s *ConstraintsSuite) TestParseNoTags(c *gc.C) { |
377 | - con := constraints.MustParse("arch=amd64 mem=4G cpu-cores=1 root-disk=8G tags=") |
378 | +func (s *ConstraintsSuite) TestParseNoTagsNoNetworks(c *gc.C) { |
379 | + con := constraints.MustParse("arch=amd64 mem=4G cpu-cores=1 root-disk=8G tags= networks=") |
380 | c.Assert(con.Tags, gc.Not(gc.IsNil)) |
381 | + c.Assert(con.Networks, gc.Not(gc.IsNil)) |
382 | c.Check(*con.Tags, gc.HasLen, 0) |
383 | + c.Check(*con.Networks, gc.HasLen, 0) |
384 | +} |
385 | + |
386 | +func (s *ConstraintsSuite) TestIncludeExcludeNetworks(c *gc.C) { |
387 | + con := constraints.MustParse("networks=net1,^net2,net3,^net4") |
388 | + c.Assert(con.Networks, gc.Not(gc.IsNil)) |
389 | + c.Check(*con.Networks, gc.HasLen, 4) |
390 | + c.Check(con.IncludeNetworks(), jc.SameContents, []string{"net1", "net3"}) |
391 | + c.Check(con.ExcludeNetworks(), jc.SameContents, []string{"net2", "net4"}) |
392 | } |
393 | |
394 | func (s *ConstraintsSuite) TestIsEmpty(c *gc.C) { |
395 | @@ -322,6 +348,8 @@ |
396 | c.Check(&con, jc.Satisfies, constraints.IsEmpty) |
397 | con = constraints.MustParse("tags=") |
398 | c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) |
399 | + con = constraints.MustParse("networks=") |
400 | + c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) |
401 | con = constraints.MustParse("mem=") |
402 | c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty) |
403 | con = constraints.MustParse("arch=") |
404 | @@ -378,6 +406,9 @@ |
405 | {"Tags1", constraints.Value{Tags: nil}}, |
406 | {"Tags2", constraints.Value{Tags: &[]string{}}}, |
407 | {"Tags3", constraints.Value{Tags: &[]string{"foo", "bar"}}}, |
408 | + {"Networks1", constraints.Value{Networks: nil}}, |
409 | + {"Networks2", constraints.Value{Networks: &[]string{}}}, |
410 | + {"Networks3", constraints.Value{Networks: &[]string{"net1", "^net2"}}}, |
411 | {"InstanceType1", constraints.Value{InstanceType: strp("")}}, |
412 | {"InstanceType2", constraints.Value{InstanceType: strp("foo")}}, |
413 | {"All", constraints.Value{ |
414 | @@ -388,6 +419,7 @@ |
415 | Mem: uint64p(18000000000), |
416 | RootDisk: uint64p(24000000000), |
417 | Tags: &[]string{"foo", "bar"}, |
418 | + Networks: &[]string{"net1", "^net2"}, |
419 | InstanceType: strp("foo"), |
420 | }}, |
421 | } |
422 | @@ -399,7 +431,7 @@ |
423 | val := constraints.ConstraintsValue{&cons} |
424 | err := val.Set(t.Value.String()) |
425 | c.Check(err, gc.IsNil) |
426 | - c.Check(cons, gc.DeepEquals, t.Value) |
427 | + c.Check(cons, jc.DeepEquals, t.Value) |
428 | } |
429 | } |
430 | |
431 | @@ -408,7 +440,7 @@ |
432 | c.Logf("test %s", t.Name) |
433 | cons, err := constraints.Parse(t.Value.String()) |
434 | c.Check(err, gc.IsNil) |
435 | - c.Check(cons, gc.DeepEquals, t.Value) |
436 | + c.Check(cons, jc.DeepEquals, t.Value) |
437 | } |
438 | } |
439 | |
440 | @@ -420,7 +452,7 @@ |
441 | var cons constraints.Value |
442 | err = json.Unmarshal(data, &cons) |
443 | c.Check(err, gc.IsNil) |
444 | - c.Check(cons, gc.DeepEquals, t.Value) |
445 | + c.Check(cons, jc.DeepEquals, t.Value) |
446 | } |
447 | } |
448 | |
449 | @@ -432,7 +464,7 @@ |
450 | var cons constraints.Value |
451 | err = goyaml.Unmarshal(data, &cons) |
452 | c.Check(err, gc.IsNil) |
453 | - c.Check(cons, gc.DeepEquals, t.Value) |
454 | + c.Check(cons, jc.DeepEquals, t.Value) |
455 | } |
456 | } |
457 | |
458 | @@ -466,57 +498,53 @@ |
459 | c.Check(cons.HasInstanceType(), jc.IsTrue) |
460 | } |
461 | |
462 | +const initialWithoutCons = "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 networks=net1,^net2 tags=foo container=lxc instance-type=bar" |
463 | + |
464 | var withoutTests = []struct { |
465 | initial string |
466 | without []string |
467 | final string |
468 | -}{ |
469 | - { |
470 | - initial: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo container=lxc instance-type=bar", |
471 | - without: []string{"root-disk"}, |
472 | - final: "mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo container=lxc instance-type=bar", |
473 | - }, |
474 | - { |
475 | - initial: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo container=lxc instance-type=bar", |
476 | - without: []string{"mem"}, |
477 | - final: "root-disk=8G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo container=lxc instance-type=bar", |
478 | - }, |
479 | - { |
480 | - initial: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo container=lxc instance-type=bar", |
481 | - without: []string{"arch"}, |
482 | - final: "root-disk=8G mem=4G cpu-power=1000 cpu-cores=4 tags=foo container=lxc instance-type=bar", |
483 | - }, |
484 | - { |
485 | - initial: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo container=lxc instance-type=bar", |
486 | - without: []string{"cpu-power"}, |
487 | - final: "root-disk=8G mem=4G arch=amd64 cpu-cores=4 tags=foo container=lxc instance-type=bar", |
488 | - }, |
489 | - { |
490 | - initial: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo container=lxc instance-type=bar", |
491 | - without: []string{"cpu-cores"}, |
492 | - final: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 tags=foo container=lxc instance-type=bar", |
493 | - }, |
494 | - { |
495 | - initial: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo container=lxc instance-type=bar", |
496 | - without: []string{"tags"}, |
497 | - final: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 container=lxc instance-type=bar", |
498 | - }, |
499 | - { |
500 | - initial: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo container=lxc instance-type=bar", |
501 | - without: []string{"container"}, |
502 | - final: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo instance-type=bar", |
503 | - }, |
504 | - { |
505 | - initial: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo container=lxc instance-type=bar", |
506 | - without: []string{"instance-type"}, |
507 | - final: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo container=lxc", |
508 | - }, |
509 | - { |
510 | - initial: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo container=lxc instance-type=bar", |
511 | - without: []string{"root-disk", "mem", "arch"}, |
512 | - final: "cpu-power=1000 cpu-cores=4 tags=foo container=lxc instance-type=bar", |
513 | - }, |
514 | -} |
515 | +}{{ |
516 | + initial: initialWithoutCons, |
517 | + without: []string{"root-disk"}, |
518 | + final: "mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo networks=net1,^net2 container=lxc instance-type=bar", |
519 | +}, { |
520 | + initial: initialWithoutCons, |
521 | + without: []string{"mem"}, |
522 | + final: "root-disk=8G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo networks=net1,^net2 container=lxc instance-type=bar", |
523 | +}, { |
524 | + initial: initialWithoutCons, |
525 | + without: []string{"arch"}, |
526 | + final: "root-disk=8G mem=4G cpu-power=1000 cpu-cores=4 tags=foo networks=net1,^net2 container=lxc instance-type=bar", |
527 | +}, { |
528 | + initial: initialWithoutCons, |
529 | + without: []string{"cpu-power"}, |
530 | + final: "root-disk=8G mem=4G arch=amd64 cpu-cores=4 tags=foo networks=net1,^net2 container=lxc instance-type=bar", |
531 | +}, { |
532 | + initial: initialWithoutCons, |
533 | + without: []string{"cpu-cores"}, |
534 | + final: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 tags=foo networks=net1,^net2 container=lxc instance-type=bar", |
535 | +}, { |
536 | + initial: initialWithoutCons, |
537 | + without: []string{"tags"}, |
538 | + final: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 networks=net1,^net2 container=lxc instance-type=bar", |
539 | +}, { |
540 | + initial: initialWithoutCons, |
541 | + without: []string{"networks"}, |
542 | + final: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo container=lxc instance-type=bar", |
543 | +}, { |
544 | + initial: initialWithoutCons, |
545 | + without: []string{"container"}, |
546 | + final: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo networks=net1,^net2 instance-type=bar", |
547 | +}, { |
548 | + initial: initialWithoutCons, |
549 | + without: []string{"instance-type"}, |
550 | + final: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 tags=foo networks=net1,^net2 container=lxc", |
551 | +}, { |
552 | + initial: initialWithoutCons, |
553 | + without: []string{"root-disk", "mem", "arch"}, |
554 | + final: "cpu-power=1000 cpu-cores=4 tags=foo networks=net1,^net2 container=lxc instance-type=bar", |
555 | +}} |
556 | |
557 | func (s *ConstraintsSuite) TestWithout(c *gc.C) { |
558 | for i, t := range withoutTests { |
559 | @@ -524,7 +552,7 @@ |
560 | initial := constraints.MustParse(t.initial) |
561 | final, err := constraints.Without(initial, t.without...) |
562 | c.Assert(err, gc.IsNil) |
563 | - c.Check(final, gc.DeepEquals, constraints.MustParse(t.final)) |
564 | + c.Check(final, jc.DeepEquals, constraints.MustParse(t.final)) |
565 | } |
566 | } |
567 | |
568 | @@ -537,7 +565,7 @@ |
569 | func (s *ConstraintsSuite) TestAttributesWithValues(c *gc.C) { |
570 | for i, consStr := range []string{ |
571 | "", |
572 | - "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 instance-type=foo tags=foo,bar", |
573 | + "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4 instance-type=foo tags=foo,bar networks=net1,^net2", |
574 | } { |
575 | c.Logf("test %d", i) |
576 | cons := constraints.MustParse(consStr) |
577 | @@ -576,6 +604,11 @@ |
578 | } else { |
579 | assertMissing("tags") |
580 | } |
581 | + if cons.Networks != nil { |
582 | + c.Check(obtained["networks"], gc.DeepEquals, *cons.Networks) |
583 | + } else { |
584 | + assertMissing("networks") |
585 | + } |
586 | if cons.InstanceType != nil { |
587 | c.Check(obtained["instance-type"], gc.Equals, *cons.InstanceType) |
588 | } else { |
589 | @@ -590,13 +623,13 @@ |
590 | expected []string |
591 | }{ |
592 | { |
593 | - cons: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4", |
594 | - attrs: []string{"root-disk", "tags", "mem"}, |
595 | - expected: []string{"root-disk", "mem"}, |
596 | + cons: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 networks=net1,^net2 cpu-cores=4", |
597 | + attrs: []string{"root-disk", "tags", "mem", "networks"}, |
598 | + expected: []string{"root-disk", "mem", "networks"}, |
599 | }, |
600 | { |
601 | cons: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4", |
602 | - attrs: []string{"tags"}, |
603 | + attrs: []string{"tags", "networks"}, |
604 | expected: []string{}, |
605 | }, |
606 | } |
607 | @@ -606,6 +639,6 @@ |
608 | c.Logf("test %d", i) |
609 | cons := constraints.MustParse(t.cons) |
610 | obtained := constraints.HasAny(cons, t.attrs...) |
611 | - c.Check(obtained, gc.DeepEquals, t.expected) |
612 | + c.Check(obtained, jc.DeepEquals, t.expected) |
613 | } |
614 | } |
615 | |
616 | === modified file 'state/constraints.go' |
617 | --- state/constraints.go 2014-05-13 04:30:48 +0000 |
618 | +++ state/constraints.go 2014-05-29 14:17:10 +0000 |
619 | @@ -25,6 +25,7 @@ |
620 | InstanceType *string |
621 | Container *instance.ContainerType |
622 | Tags *[]string `bson:",omitempty"` |
623 | + Networks *[]string `bson:",omitempty"` |
624 | } |
625 | |
626 | func (doc constraintsDoc) value() constraints.Value { |
627 | @@ -36,6 +37,7 @@ |
628 | RootDisk: doc.RootDisk, |
629 | Container: doc.Container, |
630 | Tags: doc.Tags, |
631 | + Networks: doc.Networks, |
632 | InstanceType: doc.InstanceType, |
633 | } |
634 | } |
635 | @@ -49,6 +51,7 @@ |
636 | RootDisk: cons.RootDisk, |
637 | Container: cons.Container, |
638 | Tags: cons.Tags, |
639 | + Networks: cons.Networks, |
640 | InstanceType: cons.InstanceType, |
641 | } |
642 | } |
643 | |
644 | === modified file 'state/constraintsvalidation_test.go' |
645 | --- state/constraintsvalidation_test.go 2014-04-18 13:49:33 +0000 |
646 | +++ state/constraintsvalidation_test.go 2014-05-29 14:17:10 +0000 |
647 | @@ -4,6 +4,7 @@ |
648 | package state_test |
649 | |
650 | import ( |
651 | + jc "github.com/juju/testing/checkers" |
652 | gc "launchpad.net/gocheck" |
653 | |
654 | "launchpad.net/juju-core/constraints" |
655 | @@ -39,50 +40,56 @@ |
656 | consFallback string |
657 | cons string |
658 | expected string |
659 | -}{ |
660 | - { |
661 | - cons: "root-disk=8G mem=4G arch=amd64", |
662 | - consFallback: "cpu-power=1000 cpu-cores=4", |
663 | - expected: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4", |
664 | - }, { |
665 | - cons: "root-disk=8G mem=4G arch=amd64", |
666 | - consFallback: "cpu-power=1000 cpu-cores=4 mem=8G", |
667 | - expected: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4", |
668 | - }, { |
669 | - consFallback: "root-disk=8G cpu-cores=4 instance-type=foo", |
670 | - expected: "root-disk=8G cpu-cores=4 instance-type=foo", |
671 | - }, { |
672 | - cons: "root-disk=8G cpu-cores=4 instance-type=foo", |
673 | - expected: "root-disk=8G cpu-cores=4 instance-type=foo", |
674 | - }, { |
675 | - consFallback: "root-disk=8G instance-type=foo", |
676 | - cons: "root-disk=8G cpu-cores=4 instance-type=bar", |
677 | - expected: "root-disk=8G cpu-cores=4 instance-type=bar", |
678 | - }, { |
679 | - consFallback: "root-disk=8G mem=4G", |
680 | - cons: "root-disk=8G cpu-cores=4 instance-type=bar", |
681 | - expected: "root-disk=8G cpu-cores=4 instance-type=bar", |
682 | - }, { |
683 | - consFallback: "root-disk=8G cpu-cores=4 instance-type=bar", |
684 | - cons: "root-disk=8G mem=4G", |
685 | - expected: "root-disk=8G cpu-cores=4 mem=4G", |
686 | - }, { |
687 | - consFallback: "root-disk=8G cpu-cores=4 instance-type=bar", |
688 | - cons: "root-disk=8G arch=amd64 mem=4G", |
689 | - expected: "root-disk=8G cpu-cores=4 arch=amd64 mem=4G", |
690 | - }, |
691 | -} |
692 | +}{{ |
693 | + cons: "root-disk=8G mem=4G arch=amd64", |
694 | + consFallback: "cpu-power=1000 cpu-cores=4", |
695 | + expected: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4", |
696 | +}, { |
697 | + cons: "root-disk=8G mem=4G arch=amd64", |
698 | + consFallback: "cpu-power=1000 cpu-cores=4 mem=8G", |
699 | + expected: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cpu-cores=4", |
700 | +}, { |
701 | + consFallback: "root-disk=8G cpu-cores=4 instance-type=foo", |
702 | + expected: "root-disk=8G cpu-cores=4 instance-type=foo", |
703 | +}, { |
704 | + cons: "root-disk=8G cpu-cores=4 instance-type=foo", |
705 | + expected: "root-disk=8G cpu-cores=4 instance-type=foo", |
706 | +}, { |
707 | + consFallback: "root-disk=8G instance-type=foo", |
708 | + cons: "root-disk=8G cpu-cores=4 instance-type=bar", |
709 | + expected: "root-disk=8G cpu-cores=4 instance-type=bar", |
710 | +}, { |
711 | + consFallback: "root-disk=8G mem=4G", |
712 | + cons: "root-disk=8G cpu-cores=4 instance-type=bar", |
713 | + expected: "root-disk=8G cpu-cores=4 instance-type=bar", |
714 | +}, { |
715 | + consFallback: "root-disk=8G cpu-cores=4 instance-type=bar", |
716 | + cons: "root-disk=8G mem=4G", |
717 | + expected: "root-disk=8G cpu-cores=4 mem=4G", |
718 | +}, { |
719 | + consFallback: "root-disk=8G cpu-cores=4 instance-type=bar", |
720 | + cons: "root-disk=8G arch=amd64 mem=4G", |
721 | + expected: "root-disk=8G cpu-cores=4 arch=amd64 mem=4G", |
722 | +}, { |
723 | + consFallback: "cpu-cores=4 mem=4G networks=net1,^net3", |
724 | + cons: "networks=net2,^net4 mem=4G", |
725 | + expected: "cpu-cores=4 mem=4G networks=net2,^net4", |
726 | +}, { |
727 | + consFallback: "cpu-cores=4 mem=2G networks=", |
728 | + cons: "mem=4G networks=net1,^net3", |
729 | + expected: "cpu-cores=4 mem=4G networks=net1,^net3", |
730 | +}} |
731 | |
732 | func (s *constraintsValidationSuite) TestMachineConstraints(c *gc.C) { |
733 | for i, t := range setConstraintsTests { |
734 | - c.Logf("test %d", i) |
735 | + c.Logf("test %d: fallback: %q, cons: %q", i, t.consFallback, t.cons) |
736 | err := s.State.SetEnvironConstraints(constraints.MustParse(t.consFallback)) |
737 | c.Check(err, gc.IsNil) |
738 | m, err := s.addOneMachine(c, constraints.MustParse(t.cons)) |
739 | c.Check(err, gc.IsNil) |
740 | cons, err := m.Constraints() |
741 | c.Check(err, gc.IsNil) |
742 | - c.Check(cons, gc.DeepEquals, constraints.MustParse(t.expected)) |
743 | + c.Check(cons, jc.DeepEquals, constraints.MustParse(t.expected)) |
744 | } |
745 | } |
746 | |
747 | @@ -90,15 +97,15 @@ |
748 | charm := s.AddTestingCharm(c, "wordpress") |
749 | service := s.AddTestingService(c, "wordpress", charm) |
750 | for i, t := range setConstraintsTests { |
751 | - c.Logf("test %d", i) |
752 | + c.Logf("test %d: fallback: %q, cons: %q", i, t.consFallback, t.cons) |
753 | err := s.State.SetEnvironConstraints(constraints.MustParse(t.consFallback)) |
754 | c.Check(err, gc.IsNil) |
755 | err = service.SetConstraints(constraints.MustParse(t.cons)) |
756 | c.Check(err, gc.IsNil) |
757 | u, err := service.AddUnit() |
758 | c.Check(err, gc.IsNil) |
759 | - cons, err := state.UnitConstraints(u) |
760 | + ucons, err := state.UnitConstraints(u) |
761 | c.Check(err, gc.IsNil) |
762 | - c.Check(*cons, gc.DeepEquals, constraints.MustParse(t.expected)) |
763 | + c.Check(*ucons, jc.DeepEquals, constraints.MustParse(t.expected)) |
764 | } |
765 | } |
Reviewers: mp+221390_ code.launchpad. net,
Message:
Please take a look.
Description:
various: Introduce networks constraints
Following the removal of --exclude-networks argument
from juju deploy, now we're introducting a complete
way to specify networks with constraints:
--constraints networks= net1,net2, ^net3,^ net4
Means "find a machine with net1 and net2 networks,
but not with net3 or net4 networks".
Changes are in 3 places: constraints package, state
(for saving/loading) and cmd/juju/deploy for the CLI.
Now, to specify networks to include/exclude on deploy: logging, db --constraints ^dmz,storage
juju deploy svc --networks=
networks=
Means: "deploy svc on machines with logging, db, and storage
networks, but not with dmz network".
Only the those, specified with --networks will be enabled
on the machine, once the networker worker is done. The ones
specified with constraints are used as a filter for instance
selection.
https:/ /code.launchpad .net/~dimitern/ juju-core/ 460-network- constraints/ +merge/ 221390
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/102830044/
Affected files (+271, -131 lines): deploy_ test.go help_topics. go constraints. go constraints_ test.go ts.go tsvalidation_ test.go
A [revision details]
M cmd/juju/deploy.go
M cmd/juju/
M cmd/juju/
M constraints/
M constraints/
M state/constrain
M state/constrain