Merge lp:~axwalk/juju-core/addmachine-placement into lp:~go-bot/juju-core/trunk
- addmachine-placement
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Andrew Wilkins |
Approved revision: | no longer in the source branch. |
Merged at revision: | 2666 |
Proposed branch: | lp:~axwalk/juju-core/addmachine-placement |
Merge into: | lp:~go-bot/juju-core/trunk |
Diff against target: |
1626 lines (+683/-219) 34 files modified
cmd/juju/addmachine.go (+50/-36) cmd/juju/addmachine_test.go (+9/-5) environs/broker.go (+5/-0) environs/jujutest/livetests.go (+2/-1) instance/placement.go (+85/-0) instance/placement_test.go (+76/-0) names/machine.go (+5/-0) names/machine_test.go (+6/-4) provider/azure/environ.go (+4/-1) provider/azure/instancetype_test.go (+4/-2) provider/common/policies.go (+0/-15) provider/dummy/environs.go (+8/-1) provider/ec2/ec2.go (+4/-1) provider/ec2/local_test.go (+6/-3) provider/joyent/environ.go (+4/-1) provider/local/environ.go (+7/-1) provider/maas/environ.go (+9/-1) provider/manual/environ.go (+1/-2) provider/openstack/local_test.go (+4/-2) provider/openstack/provider.go (+4/-1) state/addmachine.go (+9/-4) state/api/client.go (+14/-1) state/api/params/internal.go (+20/-0) state/api/params/params.go (+6/-1) state/api/provisioner/machine.go (+8/-32) state/api/provisioner/provisioner_test.go (+29/-43) state/apiserver/client/client.go (+42/-9) state/apiserver/client/client_test.go (+62/-16) state/apiserver/provisioner/provisioner.go (+43/-0) state/apiserver/provisioner/provisioner_test.go (+81/-0) state/machine.go (+9/-0) state/policy.go (+3/-3) state/prechecker_test.go (+22/-13) worker/provisioner/provisioner_task.go (+42/-20) |
To merge this branch: | bzr merge lp:~axwalk/juju-core/addmachine-placement |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Engineering | Pending | ||
Review via email: mp+214761@code.launchpad.net |
Commit message
Introduce instance.Placement
This change introduces a new Placement
structure in the instance package that
encapsulates machine-id, container, and
(new) environment-
directives.
The add-machine subcommand is modified
to pass this new Placement structure
through the AddMachine client API, which
calls a new state method AddMachineWithP
Machine-id and container placement do the
same thing as before, while new environment-
specific placement structures are stored in
state. A new state policy is added to allow
an Environ to validate placement.
The provisioner API and worker are updated
to pass Placement through to StartInstance.
Description of the change
Introduce instance.Placement
This change introduces a new Placement
structure in the instance package that
encapsulates machine-id, container, and
(new) environment-
directives.
The add-machine subcommand is modified
to pass this new Placement structure
through the AddMachine client API, which
calls a new state method AddMachineWithP
Machine-id and container placement do the
same thing as before, while new environment-
specific placement structures are stored in
state. A new state policy is added to allow
an Environ to validate placement.
The provisioner API and worker are updated
to pass Placement through to StartInstance.
Follow-ups to come:
- update MAAS provider to pick maas-name out
the Placement.
- update ec2, OpenStack (and MAAS?) to pick
out availbility-zone from Placement.
- update add-unit and deploy subcommands to
use Placement; this will involve modifications
to state unit assignment to choose existing
matching machines (e.g. for maas-name).
Andrew Wilkins (axwalk) wrote : | # |
Ian Booth (wallyworld) wrote : | # |
Some initial thoughts added. I assume William is ok with the design?
Seems ok to me. Maybe we can ultimately remove container constraints? My
main concern is about backwards compatibility of clients invoking add
machine. Do we support 1.18 add-machine commands?
https:/
File cmd/juju/
https:/
cmd/juju/
client.
What about backwards compatibility?
https:/
cmd/juju/
whether it's a container?
Can't we inspect the scope to see if it's a valid container type?
https:/
File instance/
https:/
instance/
instance.
Perhaps to reduce the verbosity, the test struct can have expectScope
and expectDirective and the test would use those individual values to
construct a Placement to compare with?
https:/
File provider/
https:/
provider/
*instance.
I know we check for nil elsewhere, but maybe we should have it here as
well to be defensive and avoid a npe?
https:/
File state/addmachine.go (right):
https:/
state/addmachin
AddMachineWithP
*instance.
We need a test for this method? I can see it's tested elsewhere outside
this package so maybe that's sufficient, but in general it's nice to
have at least one local test to cover all the corner cases, and the
external tests can be a little more general to test how things are wired
up.
https:/
File state/api/
https:/
state/api/
So we are retaining ContainerType and ParentId but add-machine does not
set them anymore?
https:/
File state/api/
https:/
state/api/
gc.ErrorMatches, "machine 1 not found")
We have started returning perm denied if a machine is not found.
We should have tests for genuine perm denied cases as well?
Andrew Wilkins (axwalk) wrote : | # |
> I assume William is ok with the design?
I ran the basic idea by William, but not all the details. I asked for a
review last night, but I think he was busy reviewing other things.
> Maybe we can ultimately remove container constraints? My main concern
is about backwards compatibility of clients invoking add machine. Do we
support 1.18 add-machine commands?
Yeah I think we need to keep it for backwards compatibility. The client
code should continue to pass ContainerType and ParentId to the API
server; when we get to 1.22, then we can use Placement exclusively.
https:/
File cmd/juju/
https:/
cmd/juju/
client.
On 2014/04/09 04:52:18, wallyworld wrote:
> What about backwards compatibility?
Yeah, I suppose we will need to continue setting ContainerType and
ParentId for backwards compatibility.
https:/
cmd/juju/
whether it's a container?
On 2014/04/09 04:52:18, wallyworld wrote:
> Can't we inspect the scope to see if it's a valid container type?
Yes indeed. I had previously implemented things differently, but with
the current implementation it's trivial to do so.
https:/
File instance/
https:/
instance/
instance.
On 2014/04/09 04:52:18, wallyworld wrote:
> Perhaps to reduce the verbosity, the test struct can have expectScope
and
> expectDirective and the test would use those individual values to
construct a
> Placement to compare with?
Sure. There's a special case: empty scope and value == nil placement.
https:/
File provider/
https:/
provider/
*instance.
On 2014/04/09 04:52:18, wallyworld wrote:
> I know we check for nil elsewhere, but maybe we should have it here as
well to
> be defensive and avoid a npe?
I suppose so. Alternatively, make it a non-pointer.
https:/
File state/api/
https:/
state/api/
On 2014/04/09 04:52:18, wallyworld wrote:
> So we are retaining ContainerType and ParentId but add-machine does
not set them
> anymore?
Yes, to support old clients. This deserves a DEPRECATED comment, due for
removal in 1.22.
https:/
File state/apiserver
John A Meinel (jameinel) wrote : | # |
I'm a little concerned that we're changing an API.
How will we handle trying to send Placement to and older server?
(Remember that newer client should play nice when connecting to older
servers, just as newer servers should play nice when older clients talk
to them.)
Andrew Wilkins (axwalk) wrote : | # |
On 2014/04/09 09:58:21, jameinel wrote:
> I'm a little concerned that we're changing an API.
> How will we handle trying to send Placement to and older server?
(Remember that
> newer client should play nice when connecting to older servers, just
as newer
> servers should play nice when older clients talk to them.)
Yes, we will have to continue passing ContainerType/
Actually we can't really reuse the API, because if we connect to an old
API server it'll just ignore the Placement field. I'll create a new API
method.
William Reade (fwereade) wrote : | # |
Coming along nicely -- let me know what you think about the following.
https:/
File cmd/juju/
https:/
cmd/juju/
whether it's a container?
On 2014/04/09 05:20:22, axw wrote:
> On 2014/04/09 04:52:18, wallyworld wrote:
> > Can't we inspect the scope to see if it's a valid container type?
> Yes indeed. I had previously implemented things differently, but with
the
> current implementation it's trivial to do so.
And the machine id encodes this info anyway, doesn't it
https:/
File environs/
https:/
environs/
I'm not clear on how this is distinct from Prechecker -- isn't placement
just another StartInstance parameter?
Food for thought and maybe future implementation: should the prechecker
perhaps return something that implements InstanceBroker? that'd keep
what-we-ask-for and what-we-check in sync...
https:/
File instance/
https:/
instance/
isContainerType
Hmm. Problem coming: environments named after a container type will act
in a surprising way. Can you think of any way around this?
https:/
instance/
I'm not following this case. `add-machine --to kvm`? I think we need a
target machine for this to make sense.
https:/
instance/
I think I'd prefer an explict ErrScopeUnclear, and explicit handling
thereof in the client.
https:/
instance/
specified string and create a
s/Parse/MustParse/
https:/
File provider/
https:/
provider/
directive: %s", p)
Do we want a whole Placement here, or should we maybe just handle the
directive alone?
https:/
File provider/
https:/
provider/
"valid" scope would surely not be valid, unless the dummy environ's name
were itself "valid"? And I'm not sure that it's worth passing down a
value that always has to be a particular value... this STM to indicate
that it's just ValidatePla...
Andrew Wilkins (axwalk) wrote : | # |
Please take a look.
https:/
File cmd/juju/
https:/
cmd/juju/
client.
On 2014/04/09 05:20:22, axw wrote:
> On 2014/04/09 04:52:18, wallyworld wrote:
> > What about backwards compatibility?
> Yeah, I suppose we will need to continue setting ContainerType and
ParentId for
> backwards compatibility.
Done.
https:/
cmd/juju/
whether it's a container?
On 2014/04/09 05:20:22, axw wrote:
> On 2014/04/09 04:52:18, wallyworld wrote:
> > Can't we inspect the scope to see if it's a valid container type?
> Yes indeed. I had previously implemented things differently, but with
the
> current implementation it's trivial to do so.
Done.
https:/
File environs/
https:/
environs/
On 2014/04/11 12:27:11, fwereade wrote:
> I'm not clear on how this is distinct from Prechecker -- isn't
placement just
> another StartInstance parameter?
My intention was for PlacementValidator to be used for validating
add-unit/deploy placement too. I suppose those can just be handled by a
unit-placement policy.
> Food for thought and maybe future implementation: should the
prechecker perhaps
> return something that implements InstanceBroker? that'd keep
what-we-ask-for and
> what-we-check in sync...
Sounds like a decent idea, but I'd rather keep this CL to a minimum.
https:/
File instance/
https:/
instance/
isContainerType
On 2014/04/11 12:27:11, fwereade wrote:
> Hmm. Problem coming: environments named after a container type will
act in a
> surprising way. Can you think of any way around this?
Our current placement directives currently define the lxc, kvm, ssh and
machine-id special cases. Can't do anything about this without breaking
that existing format.
In the initial implementation we won't be able to handle environment
placement directives for an environment named "lxc" or "kvm". We *could*
add special casing so that it looks for "env:lxc:" or something, but
really I don't think it's worth it.
https:/
instance/
On 2014/04/11 12:27:11, fwereade wrote:
> I'm not following this case. `add-machine --to kvm`? I think we need a
target
> machine for this to make sense.
It means "add a kvm container on a new machine"; see the bottom of
state/apiserver
https:/
ins...
William Reade (fwereade) wrote : | # |
Looking very nice, only a couple of details that maybe demand
thought/discussion.
https:/
File instance/
https:/
instance/
isContainerType
On 2014/04/17 06:13:46, axw wrote:
> On 2014/04/11 12:27:11, fwereade wrote:
> > Hmm. Problem coming: environments named after a container type will
act in a
> > surprising way. Can you think of any way around this?
> Our current placement directives currently define the lxc, kvm, ssh
and
> machine-id special cases. Can't do anything about this without
breaking that
> existing format.
> In the initial implementation we won't be able to handle environment
placement
> directives for an environment named "lxc" or "kvm". We *could* add
special
> casing so that it looks for "env:lxc:" or something, but really I
don't think
> it's worth it.
Yeah, I was more thinking that we ought to disallow "lxc" and "kvm" as
env names, but can't see how we can get there from here.
https:/
instance/
On 2014/04/17 06:13:46, axw wrote:
> On 2014/04/11 12:27:11, fwereade wrote:
> > I'm not following this case. `add-machine --to kvm`? I think we need
a target
> > machine for this to make sense.
> It means "add a kvm container on a new machine"; see the bottom of
> state/apiserver
Huh. I guess it was always like this, and I completely missed it.
Still not quite sure I like it, but let's not change things completely
arbitrarily :).
https:/
File cmd/juju/
https:/
cmd/juju/
This feels a little bit off -- if we're not incompatible, we should
surely send the info in placement form? I think we should do this
tweaking *after* getting a NotImplemented...
https:/
File environs/
https:/
environs/
prechecker.
<3
https:/
File instance/
https:/
instance/
FWIW, I really like it when test tables are defined inside the funcs
that use them, instead of cluttering up the package globals ;). It's a
bit surprising at first, but the structure is actually quite clean:
for i, test := range []struct{
// fields
}{{
// test
}, {
// test
}} {
// code
}
https:/
File provider/
Andrew Wilkins (axwalk) wrote : | # |
Please take a look.
https:/
File cmd/juju/
https:/
cmd/juju/
On 2014/04/18 09:15:52, fwereade wrote:
> This feels a little bit off -- if we're not incompatible, we should
surely send
> the info in placement form? I think we should do this tweaking *after*
getting a
> NotImplemented...
Done. Sorry, misunderstood a previous comment about maintaining use of
ContainerType/
templates.
https:/
File instance/
https:/
instance/
On 2014/04/18 09:15:52, fwereade wrote:
> FWIW, I really like it when test tables are defined inside the funcs
that use
> them, instead of cluttering up the package globals ;). It's a bit
surprising at
> first, but the structure is actually quite clean:
> for i, test := range []struct{
> // fields
> }{{
> // test
> }, {
> // test
> }} {
> // code
> }
Sure, I just copied the structure from instance_test.go.
Updated. I don't like munging the slice literal into the range, as it
puts the assigned variables too far away from the loop body.
https:/
File provider/
https:/
provider/
directive
On 2014/04/18 09:15:52, fwereade wrote:
> deserves a bug#
Done.
https:/
File state/api/client.go (right):
https:/
state/api/
remove in 1.21 (client only).
On 2014/04/18 09:15:52, fwereade wrote:
> are we sure we can drop this? I fear we'll have to deal with the
possibility of
> a 1.18 client *or* server pretty-much-forever
I'm talking about the client code only, and then only from 1.21 when we
no longer require new-client-
https:/
state/api/
On 2014/04/18 09:15:52, fwereade wrote:
> grump, rage. jam, *please* get me some API versioning.
I didn't want to do this either.
https:/
File state/api/
https:/
state/api/
On 2014/04/18 09:15:52, fwereade wrote:
> This feels like it should be all together in a single call that
extracts all the
> info we need for a given machine -- ie we should be able to grab
> series+
Andrew Wilkins (axwalk) wrote : | # |
Please take a look.
Andrew Wilkins (axwalk) wrote : | # |
Please take a look.
Ian Booth (wallyworld) wrote : | # |
LGTM with the error tweak as discussed
Andrew Wilkins (axwalk) wrote : | # |
Please take a look.
Andrew Wilkins (axwalk) wrote : | # |
Please take a look.
Preview Diff
1 | === modified file 'cmd/juju/addmachine.go' |
2 | --- cmd/juju/addmachine.go 2014-04-09 22:01:03 +0000 |
3 | +++ cmd/juju/addmachine.go 2014-04-23 06:25:18 +0000 |
4 | @@ -5,7 +5,6 @@ |
5 | |
6 | import ( |
7 | "fmt" |
8 | - "strings" |
9 | |
10 | "launchpad.net/gnuflag" |
11 | |
12 | @@ -60,10 +59,9 @@ |
13 | // If specified, use this series, else use the environment default-series |
14 | Series string |
15 | // If specified, these constraints are merged with those already in the environment. |
16 | - Constraints constraints.Value |
17 | - MachineId string |
18 | - ContainerType instance.ContainerType |
19 | - SSHHost string |
20 | + Constraints constraints.Value |
21 | + // Placement is passed verbatim to the API, to be parsed and evaluated server-side. |
22 | + Placement *instance.Placement |
23 | } |
24 | |
25 | func (c *AddMachineCommand) Info() *cmd.Info { |
26 | @@ -89,33 +87,25 @@ |
27 | if c.Constraints.Container != nil { |
28 | return fmt.Errorf("container constraint %q not allowed when adding a machine", *c.Constraints.Container) |
29 | } |
30 | - containerSpec, err := cmd.ZeroOrOneArgs(args) |
31 | - if err != nil { |
32 | - return err |
33 | - } |
34 | - if containerSpec == "" { |
35 | - return nil |
36 | - } |
37 | - if strings.HasPrefix(containerSpec, sshHostPrefix) { |
38 | - c.SSHHost = containerSpec[len(sshHostPrefix):] |
39 | - } else { |
40 | - // container arg can either be 'type:machine' or 'type' |
41 | - if c.ContainerType, err = instance.ParseContainerType(containerSpec); err != nil { |
42 | - if names.IsMachine(containerSpec) || !cmd.IsMachineOrNewContainer(containerSpec) { |
43 | - return fmt.Errorf("malformed container argument %q", containerSpec) |
44 | - } |
45 | - sep := strings.Index(containerSpec, ":") |
46 | - c.MachineId = containerSpec[sep+1:] |
47 | - c.ContainerType, err = instance.ParseContainerType(containerSpec[:sep]) |
48 | - } |
49 | - } |
50 | - return err |
51 | + placement, err := cmd.ZeroOrOneArgs(args) |
52 | + if err != nil { |
53 | + return err |
54 | + } |
55 | + c.Placement, err = instance.ParsePlacement(placement) |
56 | + if err == instance.ErrPlacementScopeMissing { |
57 | + placement = c.EnvironName() + ":" + placement |
58 | + c.Placement, err = instance.ParsePlacement(placement) |
59 | + } |
60 | + if err != nil { |
61 | + return err |
62 | + } |
63 | + return nil |
64 | } |
65 | |
66 | func (c *AddMachineCommand) Run(ctx *cmd.Context) error { |
67 | - if c.SSHHost != "" { |
68 | + if c.Placement != nil && c.Placement.Scope == "ssh" { |
69 | args := manual.ProvisionMachineArgs{ |
70 | - Host: c.SSHHost, |
71 | + Host: c.Placement.Directive, |
72 | EnvName: c.EnvName, |
73 | Stdin: ctx.Stdin, |
74 | Stdout: ctx.Stdout, |
75 | @@ -131,27 +121,51 @@ |
76 | } |
77 | defer client.Close() |
78 | |
79 | + if c.Placement != nil && c.Placement.Scope == instance.MachineScope { |
80 | + // It does not make sense to add-machine <id>. |
81 | + return fmt.Errorf("machine-id cannot be specified when adding machines") |
82 | + } |
83 | + |
84 | machineParams := params.AddMachineParams{ |
85 | - ParentId: c.MachineId, |
86 | - ContainerType: c.ContainerType, |
87 | - Series: c.Series, |
88 | - Constraints: c.Constraints, |
89 | - Jobs: []params.MachineJob{params.JobHostUnits}, |
90 | + Placement: c.Placement, |
91 | + Series: c.Series, |
92 | + Constraints: c.Constraints, |
93 | + Jobs: []params.MachineJob{params.JobHostUnits}, |
94 | } |
95 | results, err := client.AddMachines([]params.AddMachineParams{machineParams}) |
96 | + if params.IsCodeNotImplemented(err) { |
97 | + if c.Placement != nil { |
98 | + containerType, parseErr := instance.ParseContainerType(c.Placement.Scope) |
99 | + if parseErr != nil { |
100 | + // The user specified a non-container placement directive: |
101 | + // return original API not implemented error. |
102 | + return err |
103 | + } |
104 | + machineParams.ContainerType = containerType |
105 | + machineParams.ParentId = c.Placement.Directive |
106 | + machineParams.Placement = nil |
107 | + } |
108 | + logger.Infof( |
109 | + "AddMachinesWithPlacement not supported by the API server, " + |
110 | + "falling back to 1.18 compatibility mode", |
111 | + ) |
112 | + results, err = client.AddMachines1dot18([]params.AddMachineParams{machineParams}) |
113 | + } |
114 | if err != nil { |
115 | return err |
116 | } |
117 | + |
118 | // Currently, only one machine is added, but in future there may be several added in one call. |
119 | machineInfo := results[0] |
120 | if machineInfo.Error != nil { |
121 | return machineInfo.Error |
122 | } |
123 | machineId := machineInfo.Machine |
124 | - if c.ContainerType == "" { |
125 | - logger.Infof("created machine %v", machineId) |
126 | + |
127 | + if names.IsContainerMachine(machineId) { |
128 | + ctx.Infof("created container %v", machineId) |
129 | } else { |
130 | - logger.Infof("created %q container on machine %v", c.ContainerType, machineId) |
131 | + ctx.Infof("created machine %v", machineId) |
132 | } |
133 | return nil |
134 | } |
135 | |
136 | === modified file 'cmd/juju/addmachine_test.go' |
137 | --- cmd/juju/addmachine_test.go 2014-03-13 07:54:56 +0000 |
138 | +++ cmd/juju/addmachine_test.go 2014-04-23 06:25:18 +0000 |
139 | @@ -107,13 +107,17 @@ |
140 | |
141 | func (s *AddMachineSuite) TestAddMachineErrors(c *gc.C) { |
142 | err := runAddMachine(c, ":lxc") |
143 | - c.Assert(err, gc.ErrorMatches, `malformed container argument ":lxc"`) |
144 | + c.Check(err, gc.ErrorMatches, `cannot add a new machine: :lxc placement is invalid`) |
145 | err = runAddMachine(c, "lxc:") |
146 | - c.Assert(err, gc.ErrorMatches, `malformed container argument "lxc:"`) |
147 | + c.Check(err, gc.ErrorMatches, `invalid value "" for "lxc" scope: expected machine-id`) |
148 | err = runAddMachine(c, "2") |
149 | - c.Assert(err, gc.ErrorMatches, `malformed container argument "2"`) |
150 | + c.Check(err, gc.ErrorMatches, `machine-id cannot be specified when adding machines`) |
151 | err = runAddMachine(c, "foo") |
152 | - c.Assert(err, gc.ErrorMatches, `malformed container argument "foo"`) |
153 | + c.Check(err, gc.ErrorMatches, `cannot add a new machine: foo placement is invalid`) |
154 | + err = runAddMachine(c, "foo:bar") |
155 | + c.Check(err, gc.ErrorMatches, `invalid environment name "foo"`) |
156 | + err = runAddMachine(c, "dummyenv:invalid") |
157 | + c.Check(err, gc.ErrorMatches, `cannot add a new machine: invalid placement is invalid`) |
158 | err = runAddMachine(c, "lxc", "--constraints", "container=lxc") |
159 | - c.Assert(err, gc.ErrorMatches, `container constraint "lxc" not allowed when adding a machine`) |
160 | + c.Check(err, gc.ErrorMatches, `container constraint "lxc" not allowed when adding a machine`) |
161 | } |
162 | |
163 | === modified file 'environs/broker.go' |
164 | --- environs/broker.go 2014-04-18 16:37:28 +0000 |
165 | +++ environs/broker.go 2014-04-23 06:25:18 +0000 |
166 | @@ -25,6 +25,11 @@ |
167 | // MachineConfig describes the machine's configuration. |
168 | MachineConfig *cloudinit.MachineConfig |
169 | |
170 | + // Placement, if non-empty, contains an environment-specific |
171 | + // placement directive that may be used to decide how the |
172 | + // instance should be started. |
173 | + Placement string |
174 | + |
175 | // DistributionGroup, if non-nil, is a function |
176 | // that returns a slice of instance.Ids that belong |
177 | // to the same distribution group as the machine |
178 | |
179 | === modified file 'environs/jujutest/livetests.go' |
180 | --- environs/jujutest/livetests.go 2014-04-21 23:10:05 +0000 |
181 | +++ environs/jujutest/livetests.go 2014-04-23 06:25:18 +0000 |
182 | @@ -162,7 +162,8 @@ |
183 | if !ok { |
184 | return |
185 | } |
186 | - err := prechecker.PrecheckInstance("precise", constraints.Value{}) |
187 | + const placement = "" |
188 | + err := prechecker.PrecheckInstance("precise", constraints.Value{}, placement) |
189 | c.Assert(err, gc.IsNil) |
190 | } |
191 | |
192 | |
193 | === added file 'instance/placement.go' |
194 | --- instance/placement.go 1970-01-01 00:00:00 +0000 |
195 | +++ instance/placement.go 2014-04-23 06:25:18 +0000 |
196 | @@ -0,0 +1,85 @@ |
197 | +// Copyright 2014 Canonical Ltd. |
198 | +// Licensed under the AGPLv3, see LICENCE file for details. |
199 | + |
200 | +package instance |
201 | + |
202 | +import ( |
203 | + "fmt" |
204 | + "strings" |
205 | + |
206 | + "launchpad.net/juju-core/names" |
207 | +) |
208 | + |
209 | +const ( |
210 | + // MachineScope is a special scope name that is used |
211 | + // for machine placement directives (e.g. --to 0). |
212 | + MachineScope = "#" |
213 | +) |
214 | + |
215 | +var ErrPlacementScopeMissing = fmt.Errorf("placement scope missing") |
216 | + |
217 | +// Placement defines a placement directive, which has a scope |
218 | +// and a value that is scope-specific. |
219 | +type Placement struct { |
220 | + // Scope is the scope of the placement directive. Scope may |
221 | + // be a container type (lxc, kvm), instance.MachineScope, or |
222 | + // an environment name. |
223 | + // |
224 | + // If Scope is empty, then it must be inferred from the context. |
225 | + Scope string |
226 | + |
227 | + // Directive is a scope-specific placement idrective. |
228 | + // |
229 | + // For MachineScope or a container scope, this may be empty or |
230 | + // the ID of an existing machine. |
231 | + Directive string |
232 | +} |
233 | + |
234 | +func (p *Placement) String() string { |
235 | + return fmt.Sprintf("%s:%s", p.Scope, p.Directive) |
236 | +} |
237 | + |
238 | +func isContainerType(s string) bool { |
239 | + _, err := ParseContainerType(s) |
240 | + return err == nil |
241 | +} |
242 | + |
243 | +// ParsePlacement attempts to parse the specified string and create a |
244 | +// corresponding Placement structure. |
245 | +// |
246 | +// If the placement directive is non-empty and missing a scope, |
247 | +// ErrPlacementScopeMissing will be returned as well as a Placement |
248 | +// with an empty Scope field. |
249 | +func ParsePlacement(directive string) (*Placement, error) { |
250 | + if directive == "" { |
251 | + return nil, nil |
252 | + } |
253 | + if colon := strings.IndexRune(directive, ':'); colon != -1 { |
254 | + scope, directive := directive[:colon], directive[colon+1:] |
255 | + if scope == "" { |
256 | + return nil, ErrPlacementScopeMissing |
257 | + } |
258 | + // Sanity check: machine/container scopes require a machine ID as the value. |
259 | + if (scope == MachineScope || isContainerType(scope)) && !names.IsMachine(directive) { |
260 | + return nil, fmt.Errorf("invalid value %q for %q scope: expected machine-id", directive, scope) |
261 | + } |
262 | + return &Placement{Scope: scope, Directive: directive}, nil |
263 | + } |
264 | + if names.IsMachine(directive) { |
265 | + return &Placement{Scope: MachineScope, Directive: directive}, nil |
266 | + } |
267 | + if isContainerType(directive) { |
268 | + return &Placement{Scope: directive}, nil |
269 | + } |
270 | + return nil, ErrPlacementScopeMissing |
271 | +} |
272 | + |
273 | +// MustParsePlacement attempts to parse the specified string and create |
274 | +// a corresponding Placement structure, panicking if an error occurs. |
275 | +func MustParsePlacement(directive string) *Placement { |
276 | + placement, err := ParsePlacement(directive) |
277 | + if err != nil { |
278 | + panic(err) |
279 | + } |
280 | + return placement |
281 | +} |
282 | |
283 | === added file 'instance/placement_test.go' |
284 | --- instance/placement_test.go 1970-01-01 00:00:00 +0000 |
285 | +++ instance/placement_test.go 2014-04-23 06:25:18 +0000 |
286 | @@ -0,0 +1,76 @@ |
287 | +// Copyright 2014 Canonical Ltd. |
288 | +// Licensed under the AGPLv3, see LICENCE file for details. |
289 | + |
290 | +package instance_test |
291 | + |
292 | +import ( |
293 | + gc "launchpad.net/gocheck" |
294 | + |
295 | + "launchpad.net/juju-core/instance" |
296 | +) |
297 | + |
298 | +type PlacementSuite struct{} |
299 | + |
300 | +var _ = gc.Suite(&PlacementSuite{}) |
301 | + |
302 | +func (s *PlacementSuite) TestParsePlacement(c *gc.C) { |
303 | + parsePlacementTests := []struct { |
304 | + arg string |
305 | + expectScope, expectDirective string |
306 | + err string |
307 | + }{{ |
308 | + arg: "", |
309 | + }, { |
310 | + arg: "0", |
311 | + expectScope: instance.MachineScope, |
312 | + expectDirective: "0", |
313 | + }, { |
314 | + arg: "0/lxc/0", |
315 | + expectScope: instance.MachineScope, |
316 | + expectDirective: "0/lxc/0", |
317 | + }, { |
318 | + arg: "#:x", |
319 | + err: `invalid value "x" for "#" scope: expected machine-id`, |
320 | + }, { |
321 | + arg: "lxc:x", |
322 | + err: `invalid value "x" for "lxc" scope: expected machine-id`, |
323 | + }, { |
324 | + arg: "kvm:x", |
325 | + err: `invalid value "x" for "kvm" scope: expected machine-id`, |
326 | + }, { |
327 | + arg: "kvm:123", |
328 | + expectScope: string(instance.KVM), |
329 | + expectDirective: "123", |
330 | + }, { |
331 | + arg: "lxc", |
332 | + expectScope: string(instance.LXC), |
333 | + }, { |
334 | + arg: "non-standard", |
335 | + err: "placement scope missing", |
336 | + }, { |
337 | + arg: ":non-standard", |
338 | + err: "placement scope missing", |
339 | + }, { |
340 | + arg: "non:standard", |
341 | + expectScope: "non", |
342 | + expectDirective: "standard", |
343 | + }} |
344 | + |
345 | + for i, t := range parsePlacementTests { |
346 | + c.Logf("test %d: %s", i, t.arg) |
347 | + p, err := instance.ParsePlacement(t.arg) |
348 | + if t.err != "" { |
349 | + c.Assert(err, gc.ErrorMatches, t.err) |
350 | + } else { |
351 | + c.Assert(err, gc.IsNil) |
352 | + } |
353 | + if t.expectScope == "" && t.expectDirective == "" { |
354 | + c.Assert(p, gc.IsNil) |
355 | + } else { |
356 | + c.Assert(p, gc.DeepEquals, &instance.Placement{ |
357 | + Scope: t.expectScope, |
358 | + Directive: t.expectDirective, |
359 | + }) |
360 | + } |
361 | + } |
362 | +} |
363 | |
364 | === modified file 'names/machine.go' |
365 | --- names/machine.go 2013-11-25 05:03:44 +0000 |
366 | +++ names/machine.go 2014-04-23 06:25:18 +0000 |
367 | @@ -21,6 +21,11 @@ |
368 | return validMachine.MatchString(id) |
369 | } |
370 | |
371 | +// IsMachine returns whether id is a valid container machine id. |
372 | +func IsContainerMachine(id string) bool { |
373 | + return validMachine.MatchString(id) && strings.Contains(id, "/") |
374 | +} |
375 | + |
376 | // MachineTag returns the tag for the machine with the given id. |
377 | func MachineTag(id string) string { |
378 | tag := makeTag(MachineTagKind, id) |
379 | |
380 | === modified file 'names/machine_test.go' |
381 | --- names/machine_test.go 2013-08-06 09:20:36 +0000 |
382 | +++ names/machine_test.go 2014-04-23 06:25:18 +0000 |
383 | @@ -26,8 +26,9 @@ |
384 | } |
385 | |
386 | var machineIdTests = []struct { |
387 | - pattern string |
388 | - valid bool |
389 | + pattern string |
390 | + valid bool |
391 | + container bool |
392 | }{ |
393 | {pattern: "42", valid: true}, |
394 | {pattern: "042", valid: false}, |
395 | @@ -37,7 +38,7 @@ |
396 | {pattern: "55/", valid: false}, |
397 | {pattern: "1/foo", valid: false}, |
398 | {pattern: "2/foo/", valid: false}, |
399 | - {pattern: "3/lxc/42", valid: true}, |
400 | + {pattern: "3/lxc/42", valid: true, container: true}, |
401 | {pattern: "3/lxc-nodash/42", valid: false}, |
402 | {pattern: "0/lxc/00", valid: false}, |
403 | {pattern: "0/lxc/0/", valid: false}, |
404 | @@ -45,7 +46,7 @@ |
405 | {pattern: "3/lxc/042", valid: false}, |
406 | {pattern: "4/foo/bar", valid: false}, |
407 | {pattern: "5/lxc/42/foo", valid: false}, |
408 | - {pattern: "6/lxc/42/kvm/0", valid: true}, |
409 | + {pattern: "6/lxc/42/kvm/0", valid: true, container: true}, |
410 | {pattern: "06/lxc/42/kvm/0", valid: false}, |
411 | {pattern: "6/lxc/042/kvm/0", valid: false}, |
412 | {pattern: "6/lxc/42/kvm/00", valid: false}, |
413 | @@ -56,5 +57,6 @@ |
414 | for i, test := range machineIdTests { |
415 | c.Logf("test %d: %q", i, test.pattern) |
416 | c.Assert(names.IsMachine(test.pattern), gc.Equals, test.valid) |
417 | + c.Assert(names.IsContainerMachine(test.pattern), gc.Equals, test.container) |
418 | } |
419 | } |
420 | |
421 | === modified file 'provider/azure/environ.go' |
422 | --- provider/azure/environ.go 2014-04-21 23:50:20 +0000 |
423 | +++ provider/azure/environ.go 2014-04-23 06:25:18 +0000 |
424 | @@ -434,7 +434,10 @@ |
425 | } |
426 | |
427 | // PrecheckInstance is defined on the state.Prechecker interface. |
428 | -func (env *azureEnviron) PrecheckInstance(series string, cons constraints.Value) error { |
429 | +func (env *azureEnviron) PrecheckInstance(series string, cons constraints.Value, placement string) error { |
430 | + if placement != "" { |
431 | + return fmt.Errorf("unknown placement directive: %s", placement) |
432 | + } |
433 | if !cons.HasInstanceType() { |
434 | return nil |
435 | } |
436 | |
437 | === modified file 'provider/azure/instancetype_test.go' |
438 | --- provider/azure/instancetype_test.go 2014-04-17 06:44:49 +0000 |
439 | +++ provider/azure/instancetype_test.go 2014-04-23 06:25:18 +0000 |
440 | @@ -449,13 +449,15 @@ |
441 | func (s *instanceTypeSuite) TestPrecheckInstanceValidInstanceType(c *gc.C) { |
442 | env := s.setupEnvWithDummyMetadata(c) |
443 | cons := constraints.MustParse("instance-type=Large") |
444 | - err := env.PrecheckInstance("precise", cons) |
445 | + placement := "" |
446 | + err := env.PrecheckInstance("precise", cons, placement) |
447 | c.Assert(err, gc.IsNil) |
448 | } |
449 | |
450 | func (s *instanceTypeSuite) TestPrecheckInstanceInvalidInstanceType(c *gc.C) { |
451 | env := s.setupEnvWithDummyMetadata(c) |
452 | cons := constraints.MustParse("instance-type=Super") |
453 | - err := env.PrecheckInstance("precise", cons) |
454 | + placement := "" |
455 | + err := env.PrecheckInstance("precise", cons, placement) |
456 | c.Assert(err, gc.ErrorMatches, `invalid Azure instance "Super" specified`) |
457 | } |
458 | |
459 | === modified file 'provider/common/policies.go' |
460 | --- provider/common/policies.go 2014-04-17 03:10:18 +0000 |
461 | +++ provider/common/policies.go 2014-04-23 06:25:18 +0000 |
462 | @@ -3,11 +3,6 @@ |
463 | |
464 | package common |
465 | |
466 | -import ( |
467 | - "launchpad.net/juju-core/constraints" |
468 | - "launchpad.net/juju-core/state" |
469 | -) |
470 | - |
471 | // SupportsUnitPlacementPolicy provides an |
472 | // implementation of SupportsUnitPlacement |
473 | // that never returns an error, and is |
474 | @@ -18,13 +13,3 @@ |
475 | func (*SupportsUnitPlacementPolicy) SupportsUnitPlacement() error { |
476 | return nil |
477 | } |
478 | - |
479 | -// NopPrecheckerPolicy provides an implementation of the |
480 | -// state.Prechecker interface that passes all checks. |
481 | -type NopPrecheckerPolicy struct{} |
482 | - |
483 | -var _ state.Prechecker = (*NopPrecheckerPolicy)(nil) |
484 | - |
485 | -func (*NopPrecheckerPolicy) PrecheckInstance(series string, cons constraints.Value) error { |
486 | - return nil |
487 | -} |
488 | |
489 | === modified file 'provider/dummy/environs.go' |
490 | --- provider/dummy/environs.go 2014-04-21 23:10:05 +0000 |
491 | +++ provider/dummy/environs.go 2014-04-23 06:25:18 +0000 |
492 | @@ -184,7 +184,6 @@ |
493 | // environ represents a client's connection to a given environment's |
494 | // state. |
495 | type environ struct { |
496 | - common.NopPrecheckerPolicy |
497 | common.SupportsUnitPlacementPolicy |
498 | |
499 | name string |
500 | @@ -543,6 +542,14 @@ |
501 | return true |
502 | } |
503 | |
504 | +// PrecheckInstance is specified in the state.Prechecker interface. |
505 | +func (*environ) PrecheckInstance(series string, cons constraints.Value, placement string) error { |
506 | + if placement != "" && placement != "valid" { |
507 | + return fmt.Errorf("%s placement is invalid", placement) |
508 | + } |
509 | + return nil |
510 | +} |
511 | + |
512 | // GetImageSources returns a list of sources which are used to search for simplestreams image metadata. |
513 | func (e *environ) GetImageSources() ([]simplestreams.DataSource, error) { |
514 | return []simplestreams.DataSource{ |
515 | |
516 | === modified file 'provider/ec2/ec2.go' |
517 | --- provider/ec2/ec2.go 2014-04-21 23:50:20 +0000 |
518 | +++ provider/ec2/ec2.go 2014-04-23 06:25:18 +0000 |
519 | @@ -385,7 +385,10 @@ |
520 | } |
521 | |
522 | // PrecheckInstance is defined on the state.Prechecker interface. |
523 | -func (e *environ) PrecheckInstance(series string, cons constraints.Value) error { |
524 | +func (e *environ) PrecheckInstance(series string, cons constraints.Value, placement string) error { |
525 | + if placement != "" { |
526 | + return fmt.Errorf("unknown placement directive: %s", placement) |
527 | + } |
528 | if !cons.HasInstanceType() { |
529 | return nil |
530 | } |
531 | |
532 | === modified file 'provider/ec2/local_test.go' |
533 | --- provider/ec2/local_test.go 2014-04-18 13:51:46 +0000 |
534 | +++ provider/ec2/local_test.go 2014-04-23 06:25:18 +0000 |
535 | @@ -381,21 +381,24 @@ |
536 | func (t *localServerSuite) TestPrecheckInstanceValidInstanceType(c *gc.C) { |
537 | env := t.Prepare(c) |
538 | cons := constraints.MustParse("instance-type=m1.small root-disk=1G") |
539 | - err := env.PrecheckInstance("precise", cons) |
540 | + placement := "" |
541 | + err := env.PrecheckInstance("precise", cons, placement) |
542 | c.Assert(err, gc.IsNil) |
543 | } |
544 | |
545 | func (t *localServerSuite) TestPrecheckInstanceInvalidInstanceType(c *gc.C) { |
546 | env := t.Prepare(c) |
547 | cons := constraints.MustParse("instance-type=m1.invalid") |
548 | - err := env.PrecheckInstance("precise", cons) |
549 | + placement := "" |
550 | + err := env.PrecheckInstance("precise", cons, placement) |
551 | c.Assert(err, gc.ErrorMatches, `invalid AWS instance type "m1.invalid" specified`) |
552 | } |
553 | |
554 | func (t *localServerSuite) TestPrecheckInstanceUnsupportedArch(c *gc.C) { |
555 | env := t.Prepare(c) |
556 | cons := constraints.MustParse("instance-type=cc1.4xlarge arch=i386") |
557 | - err := env.PrecheckInstance("precise", cons) |
558 | + placement := "" |
559 | + err := env.PrecheckInstance("precise", cons, placement) |
560 | c.Assert(err, gc.ErrorMatches, `invalid AWS instance type "cc1.4xlarge" and arch "i386" specified`) |
561 | } |
562 | |
563 | |
564 | === modified file 'provider/joyent/environ.go' |
565 | --- provider/joyent/environ.go 2014-04-17 06:53:42 +0000 |
566 | +++ provider/joyent/environ.go 2014-04-23 06:25:18 +0000 |
567 | @@ -76,7 +76,10 @@ |
568 | } |
569 | |
570 | // PrecheckInstance is defined on the state.Prechecker interface. |
571 | -func (env *joyentEnviron) PrecheckInstance(series string, cons constraints.Value) error { |
572 | +func (env *joyentEnviron) PrecheckInstance(series string, cons constraints.Value, placement string) error { |
573 | + if placement != "" { |
574 | + return fmt.Errorf("unknown placement directive: %s", placement) |
575 | + } |
576 | if !cons.HasInstanceType() { |
577 | return nil |
578 | } |
579 | |
580 | === modified file 'provider/local/environ.go' |
581 | --- provider/local/environ.go 2014-04-21 23:10:05 +0000 |
582 | +++ provider/local/environ.go 2014-04-23 06:25:18 +0000 |
583 | @@ -58,7 +58,6 @@ |
584 | var _ envtools.SupportsCustomSources = (*localEnviron)(nil) |
585 | |
586 | type localEnviron struct { |
587 | - common.NopPrecheckerPolicy |
588 | common.SupportsUnitPlacementPolicy |
589 | |
590 | localMutex sync.Mutex |
591 | @@ -88,6 +87,13 @@ |
592 | return false |
593 | } |
594 | |
595 | +func (*localEnviron) PrecheckInstance(series string, cons constraints.Value, placement string) error { |
596 | + if placement != "" { |
597 | + return fmt.Errorf("unknown placement directive: %s", placement) |
598 | + } |
599 | + return nil |
600 | +} |
601 | + |
602 | // Name is specified in the Environ interface. |
603 | func (env *localEnviron) Name() string { |
604 | return env.name |
605 | |
606 | === modified file 'provider/maas/environ.go' |
607 | --- provider/maas/environ.go 2014-04-21 23:10:05 +0000 |
608 | +++ provider/maas/environ.go 2014-04-23 06:25:18 +0000 |
609 | @@ -51,7 +51,6 @@ |
610 | } |
611 | |
612 | type maasEnviron struct { |
613 | - common.NopPrecheckerPolicy |
614 | common.SupportsUnitPlacementPolicy |
615 | |
616 | name string |
617 | @@ -171,6 +170,15 @@ |
618 | return caps.Contains(capNetworksManagement) |
619 | } |
620 | |
621 | +func (env *maasEnviron) PrecheckInstance(series string, cons constraints.Value, placement string) error { |
622 | + // TODO(axw) 2014-04-22 #1237709 |
623 | + // Handle maas-name placement directive. |
624 | + if placement != "" { |
625 | + return fmt.Errorf("unknown placement directive: %s", placement) |
626 | + } |
627 | + return nil |
628 | +} |
629 | + |
630 | const capNetworksManagement = "networks-management" |
631 | |
632 | // getCapabilities asks the MAAS server for its capabilities, if |
633 | |
634 | === modified file 'provider/manual/environ.go' |
635 | --- provider/manual/environ.go 2014-04-21 23:10:05 +0000 |
636 | +++ provider/manual/environ.go 2014-04-23 06:25:18 +0000 |
637 | @@ -61,7 +61,6 @@ |
638 | } |
639 | |
640 | var _ envtools.SupportsCustomSources = (*manualEnviron)(nil) |
641 | -var _ state.Prechecker = (*manualEnviron)(nil) |
642 | |
643 | var errNoStartInstance = errors.New("manual provider cannot start instances") |
644 | var errNoStopInstance = errors.New("manual provider cannot stop instances") |
645 | @@ -262,7 +261,7 @@ |
646 | return err |
647 | } |
648 | |
649 | -func (*manualEnviron) PrecheckInstance(series string, cons constraints.Value) error { |
650 | +func (*manualEnviron) PrecheckInstance(series string, _ constraints.Value, placement string) error { |
651 | return errors.New(`use "juju add-machine ssh:[user@]<host>" to provision machines`) |
652 | } |
653 | |
654 | |
655 | === modified file 'provider/openstack/local_test.go' |
656 | --- provider/openstack/local_test.go 2014-04-21 23:50:20 +0000 |
657 | +++ provider/openstack/local_test.go 2014-04-23 06:25:18 +0000 |
658 | @@ -716,14 +716,16 @@ |
659 | func (s *localServerSuite) TestPrecheckInstanceValidInstanceType(c *gc.C) { |
660 | env := s.Open(c) |
661 | cons := constraints.MustParse("instance-type=m1.small") |
662 | - err := env.PrecheckInstance("precise", cons) |
663 | + placement := "" |
664 | + err := env.PrecheckInstance("precise", cons, placement) |
665 | c.Assert(err, gc.IsNil) |
666 | } |
667 | |
668 | func (s *localServerSuite) TestPrecheckInstanceInvalidInstanceType(c *gc.C) { |
669 | env := s.Open(c) |
670 | cons := constraints.MustParse("instance-type=m1.large") |
671 | - err := env.PrecheckInstance("precise", cons) |
672 | + placement := "" |
673 | + err := env.PrecheckInstance("precise", cons, placement) |
674 | c.Assert(err, gc.ErrorMatches, `invalid Openstack flavour "m1.large" specified`) |
675 | } |
676 | |
677 | |
678 | === modified file 'provider/openstack/provider.go' |
679 | --- provider/openstack/provider.go 2014-04-21 23:50:20 +0000 |
680 | +++ provider/openstack/provider.go 2014-04-23 06:25:18 +0000 |
681 | @@ -538,7 +538,10 @@ |
682 | } |
683 | |
684 | // PrecheckInstance is defined on the state.Prechecker interface. |
685 | -func (e *environ) PrecheckInstance(series string, cons constraints.Value) error { |
686 | +func (e *environ) PrecheckInstance(series string, cons constraints.Value, placement string) error { |
687 | + if placement != "" { |
688 | + return fmt.Errorf("unknown placement directive: %s", placement) |
689 | + } |
690 | if !cons.HasInstanceType() { |
691 | return nil |
692 | } |
693 | |
694 | === modified file 'state/addmachine.go' |
695 | --- state/addmachine.go 2014-04-21 23:10:05 +0000 |
696 | +++ state/addmachine.go 2014-04-23 06:25:18 +0000 |
697 | @@ -69,6 +69,10 @@ |
698 | // as unclean for unit-assignment purposes. |
699 | Dirty bool |
700 | |
701 | + // Placement holds the placement directive that will be associated |
702 | + // with the machine. |
703 | + Placement string |
704 | + |
705 | // principals holds the principal units that will |
706 | // associated with the machine. |
707 | principals []string |
708 | @@ -234,7 +238,7 @@ |
709 | return nil, nil, err |
710 | } |
711 | if template.InstanceId == "" { |
712 | - if err := st.precheckInstance(template.Series, template.Constraints); err != nil { |
713 | + if err := st.precheckInstance(template.Series, template.Constraints, template.Placement); err != nil { |
714 | return nil, nil, err |
715 | } |
716 | } |
717 | @@ -359,13 +363,13 @@ |
718 | return nil, nil, fmt.Errorf("no container type specified") |
719 | } |
720 | if parentTemplate.InstanceId == "" { |
721 | - if err := st.precheckInstance(parentTemplate.Series, parentTemplate.Constraints); err != nil { |
722 | - return nil, nil, err |
723 | - } |
724 | // Adding a machine within a machine implies add-machine or placement. |
725 | if err := st.supportsUnitPlacement(); err != nil { |
726 | return nil, nil, err |
727 | } |
728 | + if err := st.precheckInstance(parentTemplate.Series, parentTemplate.Constraints, parentTemplate.Placement); err != nil { |
729 | + return nil, nil, err |
730 | + } |
731 | } |
732 | |
733 | parentDoc := machineDocForTemplate(parentTemplate, strconv.Itoa(seq)) |
734 | @@ -403,6 +407,7 @@ |
735 | Nonce: template.Nonce, |
736 | Addresses: instanceAddressesToAddresses(template.Addresses), |
737 | NoVote: template.NoVote, |
738 | + Placement: template.Placement, |
739 | } |
740 | } |
741 | |
742 | |
743 | === modified file 'state/api/client.go' |
744 | --- state/api/client.go 2014-04-17 19:01:44 +0000 |
745 | +++ state/api/client.go 2014-04-23 06:25:18 +0000 |
746 | @@ -227,13 +227,26 @@ |
747 | return results.CharmRelations, err |
748 | } |
749 | |
750 | +// AddMachines1dot18 adds new machines with the supplied parameters. |
751 | +// |
752 | +// TODO(axw) 2014-04-11 #XXX |
753 | +// This exists for backwards compatibility; remove in 1.21 (client only). |
754 | +func (c *Client) AddMachines1dot18(machineParams []params.AddMachineParams) ([]params.AddMachinesResult, error) { |
755 | + args := params.AddMachines{ |
756 | + MachineParams: machineParams, |
757 | + } |
758 | + results := new(params.AddMachinesResults) |
759 | + err := c.call("AddMachines", args, results) |
760 | + return results.Machines, err |
761 | +} |
762 | + |
763 | // AddMachines adds new machines with the supplied parameters. |
764 | func (c *Client) AddMachines(machineParams []params.AddMachineParams) ([]params.AddMachinesResult, error) { |
765 | args := params.AddMachines{ |
766 | MachineParams: machineParams, |
767 | } |
768 | results := new(params.AddMachinesResults) |
769 | - err := c.call("AddMachines", args, results) |
770 | + err := c.call("AddMachinesV2", args, results) |
771 | return results.Machines, err |
772 | } |
773 | |
774 | |
775 | === modified file 'state/api/params/internal.go' |
776 | --- state/api/params/internal.go 2014-04-18 16:37:28 +0000 |
777 | +++ state/api/params/internal.go 2014-04-23 06:25:18 +0000 |
778 | @@ -599,3 +599,23 @@ |
779 | type AgentVersionResult struct { |
780 | Version version.Number |
781 | } |
782 | + |
783 | +// ProvisioningInfo holds machine provisioning info. |
784 | +type ProvisioningInfo struct { |
785 | + Constraints constraints.Value |
786 | + Series string |
787 | + Placement string |
788 | + IncludeNetworks []string |
789 | + ExcludeNetworks []string |
790 | +} |
791 | + |
792 | +// ProvisioningInfoResult holds machine provisioning info or an error. |
793 | +type ProvisioningInfoResult struct { |
794 | + Error *Error |
795 | + Result *ProvisioningInfo |
796 | +} |
797 | + |
798 | +// ProvisioningInfoResults holds multiple machine provisioning info results. |
799 | +type ProvisioningInfoResults struct { |
800 | + Results []ProvisioningInfoResult |
801 | +} |
802 | |
803 | === modified file 'state/api/params/params.go' |
804 | --- state/api/params/params.go 2014-04-12 05:53:58 +0000 |
805 | +++ state/api/params/params.go 2014-04-23 06:25:18 +0000 |
806 | @@ -92,6 +92,10 @@ |
807 | Constraints constraints.Value |
808 | Jobs []MachineJob |
809 | |
810 | + // If Placement is non-nil, it contains a placement directive |
811 | + // that will be used to decide how to instantiate the machine. |
812 | + Placement *instance.Placement |
813 | + |
814 | // If ParentId is non-empty, it specifies the id of the |
815 | // parent machine within which the new machine will |
816 | // be created. In that case, ContainerType must also be |
817 | @@ -117,7 +121,8 @@ |
818 | Addrs []instance.Address |
819 | } |
820 | |
821 | -// AddMachines holds the parameters for making the AddMachines call. |
822 | +// AddMachines holds the parameters for making the |
823 | +// AddMachinesWithPlacement call. |
824 | type AddMachines struct { |
825 | MachineParams []AddMachineParams |
826 | } |
827 | |
828 | === modified file 'state/api/provisioner/machine.go' |
829 | --- state/api/provisioner/machine.go 2014-04-13 11:06:42 +0000 |
830 | +++ state/api/provisioner/machine.go 2014-04-23 06:25:18 +0000 |
831 | @@ -7,7 +7,6 @@ |
832 | "errors" |
833 | "fmt" |
834 | |
835 | - "launchpad.net/juju-core/constraints" |
836 | "launchpad.net/juju-core/instance" |
837 | "launchpad.net/juju-core/names" |
838 | "launchpad.net/juju-core/state/api/params" |
839 | @@ -55,23 +54,22 @@ |
840 | return nil |
841 | } |
842 | |
843 | -// RequestedNetworks returns a pair of lists of networks to |
844 | -// enable/disable on the machine. |
845 | -func (m *Machine) RequestedNetworks() (includeNetworks, excludeNetworks []string, err error) { |
846 | - var results params.RequestedNetworksResults |
847 | +// ProvisioningInfo returns the information required to provisiong a machine. |
848 | +func (m *Machine) ProvisioningInfo() (*params.ProvisioningInfo, error) { |
849 | + var results params.ProvisioningInfoResults |
850 | args := params.Entities{Entities: []params.Entity{{m.tag}}} |
851 | - err = m.st.call("RequestedNetworks", args, &results) |
852 | + err := m.st.call("ProvisioningInfo", args, &results) |
853 | if err != nil { |
854 | - return nil, nil, err |
855 | + return nil, err |
856 | } |
857 | if len(results.Results) != 1 { |
858 | - return nil, nil, fmt.Errorf("expected 1 result, got %d", len(results.Results)) |
859 | + return nil, fmt.Errorf("expected 1 result, got %d", len(results.Results)) |
860 | } |
861 | result := results.Results[0] |
862 | if result.Error != nil { |
863 | - return nil, nil, result.Error |
864 | + return nil, result.Error |
865 | } |
866 | - return result.IncludeNetworks, result.ExcludeNetworks, nil |
867 | + return result.Result, nil |
868 | } |
869 | |
870 | // SetStatus sets the status of the machine. |
871 | @@ -109,28 +107,6 @@ |
872 | return result.Status, result.Info, nil |
873 | } |
874 | |
875 | -// Constraints returns the exact constraints that should apply when provisioning |
876 | -// an instance for the machine. |
877 | -func (m *Machine) Constraints() (constraints.Value, error) { |
878 | - nothing := constraints.Value{} |
879 | - var results params.ConstraintsResults |
880 | - args := params.Entities{ |
881 | - Entities: []params.Entity{{Tag: m.tag}}, |
882 | - } |
883 | - err := m.st.call("Constraints", args, &results) |
884 | - if err != nil { |
885 | - return nothing, err |
886 | - } |
887 | - if len(results.Results) != 1 { |
888 | - return nothing, fmt.Errorf("expected 1 result, got %d", len(results.Results)) |
889 | - } |
890 | - result := results.Results[0] |
891 | - if result.Error != nil { |
892 | - return nothing, result.Error |
893 | - } |
894 | - return result.Constraints, nil |
895 | -} |
896 | - |
897 | // EnsureDead sets the machine lifecycle to Dead if it is Alive or |
898 | // Dying. It does nothing otherwise. |
899 | func (m *Machine) EnsureDead() error { |
900 | |
901 | === modified file 'state/api/provisioner/provisioner_test.go' |
902 | --- state/api/provisioner/provisioner_test.go 2014-04-18 15:31:56 +0000 |
903 | +++ state/api/provisioner/provisioner_test.go 2014-04-23 06:25:18 +0000 |
904 | @@ -388,55 +388,41 @@ |
905 | c.Assert(err, jc.Satisfies, params.IsCodeNotFound) |
906 | } |
907 | |
908 | -func (s *provisionerSuite) TestConstraints(c *gc.C) { |
909 | - // Create a fresh machine with some constraints. |
910 | - template := state.MachineTemplate{ |
911 | - Series: "quantal", |
912 | - Jobs: []state.MachineJob{state.JobHostUnits}, |
913 | - Constraints: constraints.MustParse("cpu-cores=12", "mem=8G"), |
914 | - } |
915 | - consMachine, err := s.State.AddOneMachine(template) |
916 | - c.Assert(err, gc.IsNil) |
917 | - |
918 | - apiMachine, err := s.provisioner.Machine(consMachine.Tag()) |
919 | - c.Assert(err, gc.IsNil) |
920 | - cons, err := apiMachine.Constraints() |
921 | - c.Assert(err, gc.IsNil) |
922 | - c.Assert(cons, gc.DeepEquals, template.Constraints) |
923 | - |
924 | - // Now try machine 0. |
925 | - apiMachine, err = s.provisioner.Machine(s.machine.Tag()) |
926 | - c.Assert(err, gc.IsNil) |
927 | - cons, err = apiMachine.Constraints() |
928 | - c.Assert(err, gc.IsNil) |
929 | - c.Assert(cons, gc.DeepEquals, constraints.Value{}) |
930 | -} |
931 | - |
932 | -func (s *provisionerSuite) TestRequestedNetworks(c *gc.C) { |
933 | - // Create a fresh machine with some requested networks. |
934 | +func (s *provisionerSuite) TestProvisioningInfo(c *gc.C) { |
935 | template := state.MachineTemplate{ |
936 | Series: "quantal", |
937 | Jobs: []state.MachineJob{state.JobHostUnits}, |
938 | + Placement: "valid", |
939 | + Constraints: constraints.MustParse("cpu-cores=12", "mem=8G"), |
940 | IncludeNetworks: []string{"net1", "net2"}, |
941 | ExcludeNetworks: []string{"net3", "net4"}, |
942 | } |
943 | - netsMachine, err := s.State.AddOneMachine(template) |
944 | - c.Assert(err, gc.IsNil) |
945 | - |
946 | - apiMachine, err := s.provisioner.Machine(netsMachine.Tag()) |
947 | - c.Assert(err, gc.IsNil) |
948 | - includeNetworks, excludeNetworks, err := apiMachine.RequestedNetworks() |
949 | - c.Assert(err, gc.IsNil) |
950 | - c.Assert(includeNetworks, gc.DeepEquals, template.IncludeNetworks) |
951 | - c.Assert(excludeNetworks, gc.DeepEquals, template.ExcludeNetworks) |
952 | - |
953 | - // Now try machine 0. |
954 | - apiMachine, err = s.provisioner.Machine(s.machine.Tag()) |
955 | - c.Assert(err, gc.IsNil) |
956 | - includeNetworks, excludeNetworks, err = apiMachine.RequestedNetworks() |
957 | - c.Assert(err, gc.IsNil) |
958 | - c.Assert(includeNetworks, gc.HasLen, 0) |
959 | - c.Assert(excludeNetworks, gc.HasLen, 0) |
960 | + machine, err := s.State.AddOneMachine(template) |
961 | + c.Assert(err, gc.IsNil) |
962 | + apiMachine, err := s.provisioner.Machine(machine.Tag()) |
963 | + c.Assert(err, gc.IsNil) |
964 | + provisioningInfo, err := apiMachine.ProvisioningInfo() |
965 | + c.Assert(err, gc.IsNil) |
966 | + c.Assert(provisioningInfo.Series, gc.Equals, template.Series) |
967 | + c.Assert(provisioningInfo.Placement, gc.Equals, template.Placement) |
968 | + c.Assert(provisioningInfo.Constraints, gc.DeepEquals, template.Constraints) |
969 | + c.Assert(provisioningInfo.IncludeNetworks, gc.DeepEquals, template.IncludeNetworks) |
970 | + c.Assert(provisioningInfo.ExcludeNetworks, gc.DeepEquals, template.ExcludeNetworks) |
971 | +} |
972 | + |
973 | +func (s *provisionerSuite) TestProvisioningInfoMachineNotFound(c *gc.C) { |
974 | + stateMachine, err := s.State.AddMachine("quantal", state.JobHostUnits) |
975 | + c.Assert(err, gc.IsNil) |
976 | + apiMachine, err := s.provisioner.Machine(stateMachine.Tag()) |
977 | + c.Assert(err, gc.IsNil) |
978 | + err = apiMachine.EnsureDead() |
979 | + c.Assert(err, gc.IsNil) |
980 | + err = apiMachine.Remove() |
981 | + c.Assert(err, gc.IsNil) |
982 | + _, err = apiMachine.ProvisioningInfo() |
983 | + c.Assert(err, gc.ErrorMatches, "machine 1 not found") |
984 | + c.Assert(err, jc.Satisfies, params.IsCodeNotFound) |
985 | + // auth tests in apiserver |
986 | } |
987 | |
988 | func (s *provisionerSuite) TestWatchContainers(c *gc.C) { |
989 | |
990 | === modified file 'state/apiserver/client/client.go' |
991 | --- state/apiserver/client/client.go 2014-04-17 12:53:23 +0000 |
992 | +++ state/apiserver/client/client.go 2014-04-23 06:25:18 +0000 |
993 | @@ -587,6 +587,11 @@ |
994 | |
995 | // AddMachines adds new machines with the supplied parameters. |
996 | func (c *Client) AddMachines(args params.AddMachines) (params.AddMachinesResults, error) { |
997 | + return c.AddMachinesV2(args) |
998 | +} |
999 | + |
1000 | +// AddMachinesV2 adds new machines with the supplied parameters. |
1001 | +func (c *Client) AddMachinesV2(args params.AddMachines) (params.AddMachinesResults, error) { |
1002 | results := params.AddMachinesResults{ |
1003 | Machines: make([]params.AddMachinesResult, len(args.MachineParams)), |
1004 | } |
1005 | @@ -606,23 +611,50 @@ |
1006 | } |
1007 | |
1008 | func (c *Client) addOneMachine(p params.AddMachineParams) (*state.Machine, error) { |
1009 | - if p.Series == "" { |
1010 | - conf, err := c.api.state.EnvironConfig() |
1011 | - if err != nil { |
1012 | - return nil, err |
1013 | + if p.ParentId != "" && p.ContainerType == "" { |
1014 | + return nil, fmt.Errorf("parent machine specified without container type") |
1015 | + } |
1016 | + if p.ContainerType != "" && p.Placement != nil { |
1017 | + return nil, fmt.Errorf("container type and placement are mutually exclusive") |
1018 | + } |
1019 | + if p.Placement != nil { |
1020 | + // Extract container type and parent from container placement directives. |
1021 | + containerType, err := instance.ParseContainerType(p.Placement.Scope) |
1022 | + if err == nil { |
1023 | + p.ContainerType = containerType |
1024 | + p.ParentId = p.Placement.Directive |
1025 | + p.Placement = nil |
1026 | } |
1027 | - p.Series = config.PreferredSeries(conf) |
1028 | } |
1029 | - if p.ContainerType != "" { |
1030 | + |
1031 | + if p.ContainerType != "" || p.Placement != nil { |
1032 | // Guard against dubious client by making sure that |
1033 | // the following attributes can only be set when we're |
1034 | - // not making a new container. |
1035 | + // not using placement. |
1036 | p.InstanceId = "" |
1037 | p.Nonce = "" |
1038 | p.HardwareCharacteristics = instance.HardwareCharacteristics{} |
1039 | p.Addrs = nil |
1040 | - } else if p.ParentId != "" { |
1041 | - return nil, fmt.Errorf("parent machine specified without container type") |
1042 | + } |
1043 | + |
1044 | + if p.Series == "" { |
1045 | + conf, err := c.api.state.EnvironConfig() |
1046 | + if err != nil { |
1047 | + return nil, err |
1048 | + } |
1049 | + p.Series = config.PreferredSeries(conf) |
1050 | + } |
1051 | + |
1052 | + var placementDirective string |
1053 | + if p.Placement != nil { |
1054 | + env, err := c.api.state.Environment() |
1055 | + if err != nil { |
1056 | + return nil, err |
1057 | + } |
1058 | + if p.Placement.Scope != env.Name() { |
1059 | + return nil, fmt.Errorf("invalid environment name %q", p.Placement.Scope) |
1060 | + } |
1061 | + placementDirective = p.Placement.Directive |
1062 | } |
1063 | |
1064 | jobs, err := stateJobs(p.Jobs) |
1065 | @@ -637,6 +669,7 @@ |
1066 | Nonce: p.Nonce, |
1067 | HardwareCharacteristics: p.HardwareCharacteristics, |
1068 | Addresses: p.Addrs, |
1069 | + Placement: placementDirective, |
1070 | } |
1071 | if p.ContainerType == "" { |
1072 | return c.api.state.AddOneMachine(template) |
1073 | |
1074 | === modified file 'state/apiserver/client/client_test.go' |
1075 | --- state/apiserver/client/client_test.go 2014-04-17 12:53:23 +0000 |
1076 | +++ state/apiserver/client/client_test.go 2014-04-23 06:25:18 +0000 |
1077 | @@ -1665,8 +1665,8 @@ |
1078 | |
1079 | machines, err := s.APIState.Client().AddMachines([]params.AddMachineParams{{ |
1080 | Jobs: []params.MachineJob{params.JobHostUnits}, |
1081 | + ContainerType: instance.LXC, |
1082 | ParentId: "0", |
1083 | - ContainerType: instance.LXC, |
1084 | Series: "quantal", |
1085 | }}) |
1086 | c.Assert(err, gc.IsNil) |
1087 | @@ -1692,13 +1692,65 @@ |
1088 | } |
1089 | } |
1090 | |
1091 | +func (s *clientSuite) TestClientAddMachinesWithPlacement(c *gc.C) { |
1092 | + apiParams := make([]params.AddMachineParams, 4) |
1093 | + for i := range apiParams { |
1094 | + apiParams[i] = params.AddMachineParams{ |
1095 | + Jobs: []params.MachineJob{params.JobHostUnits}, |
1096 | + } |
1097 | + } |
1098 | + apiParams[0].Placement = instance.MustParsePlacement("lxc") |
1099 | + apiParams[1].Placement = instance.MustParsePlacement("lxc:0") |
1100 | + apiParams[1].ContainerType = instance.LXC |
1101 | + apiParams[2].Placement = instance.MustParsePlacement("dummyenv:invalid") |
1102 | + apiParams[3].Placement = instance.MustParsePlacement("dummyenv:valid") |
1103 | + machines, err := s.APIState.Client().AddMachines(apiParams) |
1104 | + c.Assert(err, gc.IsNil) |
1105 | + c.Assert(len(machines), gc.Equals, 4) |
1106 | + c.Assert(machines[0].Machine, gc.Equals, "0/lxc/0") |
1107 | + c.Assert(machines[1].Error, gc.ErrorMatches, "container type and placement are mutually exclusive") |
1108 | + c.Assert(machines[2].Error, gc.ErrorMatches, "cannot add a new machine: invalid placement is invalid") |
1109 | + c.Assert(machines[3].Machine, gc.Equals, "1") |
1110 | + |
1111 | + m, err := s.BackingState.Machine(machines[3].Machine) |
1112 | + c.Assert(err, gc.IsNil) |
1113 | + c.Assert(m.Placement(), gc.DeepEquals, apiParams[3].Placement.Directive) |
1114 | +} |
1115 | + |
1116 | +func (s *clientSuite) TestClientAddMachines1dot18(c *gc.C) { |
1117 | + apiParams := make([]params.AddMachineParams, 2) |
1118 | + for i := range apiParams { |
1119 | + apiParams[i] = params.AddMachineParams{ |
1120 | + Jobs: []params.MachineJob{params.JobHostUnits}, |
1121 | + } |
1122 | + } |
1123 | + apiParams[1].ContainerType = instance.LXC |
1124 | + apiParams[1].ParentId = "0" |
1125 | + machines, err := s.APIState.Client().AddMachines1dot18(apiParams) |
1126 | + c.Assert(err, gc.IsNil) |
1127 | + c.Assert(len(machines), gc.Equals, 2) |
1128 | + c.Assert(machines[0].Machine, gc.Equals, "0") |
1129 | + c.Assert(machines[1].Machine, gc.Equals, "0/lxc/0") |
1130 | +} |
1131 | + |
1132 | +func (s *clientSuite) TestClientAddMachines1dot18SomeErrors(c *gc.C) { |
1133 | + apiParams := []params.AddMachineParams{{ |
1134 | + Jobs: []params.MachineJob{params.JobHostUnits}, |
1135 | + ParentId: "123", |
1136 | + }} |
1137 | + machines, err := s.APIState.Client().AddMachines1dot18(apiParams) |
1138 | + c.Assert(err, gc.IsNil) |
1139 | + c.Assert(len(machines), gc.Equals, 1) |
1140 | + c.Check(machines[0].Error, gc.ErrorMatches, "parent machine specified without container type") |
1141 | +} |
1142 | + |
1143 | func (s *clientSuite) TestClientAddMachinesSomeErrors(c *gc.C) { |
1144 | // Here we check that adding a number of containers correctly handles the |
1145 | // case that some adds succeed and others fail and report the errors |
1146 | // accordingly. |
1147 | - // We will set up params to the AddMachines API to attempt to create 4 machines. |
1148 | + // We will set up params to the AddMachines API to attempt to create 3 machines. |
1149 | // Machines 0 and 1 will be added successfully. |
1150 | - // Mchines 2 and 3 will fail due to different reasons. |
1151 | + // Remaining machines will fail due to different reasons. |
1152 | |
1153 | // Create a machine to host the requested containers. |
1154 | host, err := s.State.AddMachine("quantal", state.JobHostUnits) |
1155 | @@ -1707,32 +1759,26 @@ |
1156 | err = host.SetSupportedContainers([]instance.ContainerType{instance.LXC}) |
1157 | c.Assert(err, gc.IsNil) |
1158 | |
1159 | - // Set up params for adding 4 containers. |
1160 | - apiParams := make([]params.AddMachineParams, 4) |
1161 | - for i := 0; i < 4; i++ { |
1162 | + // Set up params for adding 3 containers. |
1163 | + apiParams := make([]params.AddMachineParams, 3) |
1164 | + for i := range apiParams { |
1165 | apiParams[i] = params.AddMachineParams{ |
1166 | Jobs: []params.MachineJob{params.JobHostUnits}, |
1167 | } |
1168 | } |
1169 | - // Make it so that machines 2 and 3 will fail to be added. |
1170 | - // This will cause a machine add to fail because of an invalid parent. |
1171 | - apiParams[2].ParentId = "123" |
1172 | // This will cause a machine add to fail due to an unsupported container. |
1173 | - apiParams[3].ParentId = host.Id() |
1174 | - apiParams[3].ContainerType = instance.KVM |
1175 | + apiParams[2].ContainerType = instance.KVM |
1176 | + apiParams[2].ParentId = host.Id() |
1177 | machines, err := s.APIState.Client().AddMachines(apiParams) |
1178 | c.Assert(err, gc.IsNil) |
1179 | - c.Assert(len(machines), gc.Equals, 4) |
1180 | + c.Assert(len(machines), gc.Equals, 3) |
1181 | |
1182 | // Check the results - machines 2 and 3 will have errors. |
1183 | c.Check(machines[0].Machine, gc.Equals, "1") |
1184 | c.Check(machines[0].Error, gc.IsNil) |
1185 | c.Check(machines[1].Machine, gc.Equals, "2") |
1186 | c.Check(machines[1].Error, gc.IsNil) |
1187 | - c.Assert(machines[2].Error, gc.NotNil) |
1188 | - c.Check(machines[2].Error, gc.ErrorMatches, "parent machine specified without container type") |
1189 | - c.Assert(machines[2].Error, gc.NotNil) |
1190 | - c.Check(machines[3].Error, gc.ErrorMatches, "cannot add a new machine: machine 0 cannot host kvm containers") |
1191 | + c.Check(machines[2].Error, gc.ErrorMatches, "cannot add a new machine: machine 0 cannot host kvm containers") |
1192 | } |
1193 | |
1194 | func (s *clientSuite) TestClientAddMachinesWithInstanceIdSomeErrors(c *gc.C) { |
1195 | |
1196 | === modified file 'state/apiserver/provisioner/provisioner.go' |
1197 | --- state/apiserver/provisioner/provisioner.go 2014-04-18 15:31:56 +0000 |
1198 | +++ state/apiserver/provisioner/provisioner.go 2014-04-23 06:25:18 +0000 |
1199 | @@ -285,6 +285,49 @@ |
1200 | return result, nil |
1201 | } |
1202 | |
1203 | +// ProvisioningInfo returns the provisioning information for each given machine entity. |
1204 | +func (p *ProvisionerAPI) ProvisioningInfo(args params.Entities) (params.ProvisioningInfoResults, error) { |
1205 | + result := params.ProvisioningInfoResults{ |
1206 | + Results: make([]params.ProvisioningInfoResult, len(args.Entities)), |
1207 | + } |
1208 | + canAccess, err := p.getAuthFunc() |
1209 | + if err != nil { |
1210 | + return result, err |
1211 | + } |
1212 | + for i, entity := range args.Entities { |
1213 | + machine, err := p.getMachine(canAccess, entity.Tag) |
1214 | + if err == nil { |
1215 | + result.Results[i].Result, err = getProvisioningInfo(machine) |
1216 | + } |
1217 | + result.Results[i].Error = common.ServerError(err) |
1218 | + } |
1219 | + return result, nil |
1220 | +} |
1221 | + |
1222 | +func getProvisioningInfo(m *state.Machine) (*params.ProvisioningInfo, error) { |
1223 | + cons, err := m.Constraints() |
1224 | + if err != nil { |
1225 | + return nil, err |
1226 | + } |
1227 | + // TODO(dimitern) For now, since network names and |
1228 | + // provider ids are the same, we return what we got |
1229 | + // from state. In the future, when networks can be |
1230 | + // added before provisioning, we should convert both |
1231 | + // slices from juju network names to provider-specific |
1232 | + // ids before returning them. |
1233 | + includeNetworks, excludeNetworks, err := m.RequestedNetworks() |
1234 | + if err != nil { |
1235 | + return nil, err |
1236 | + } |
1237 | + return ¶ms.ProvisioningInfo{ |
1238 | + Constraints: cons, |
1239 | + Series: m.Series(), |
1240 | + Placement: m.Placement(), |
1241 | + IncludeNetworks: includeNetworks, |
1242 | + ExcludeNetworks: excludeNetworks, |
1243 | + }, nil |
1244 | +} |
1245 | + |
1246 | // DistributionGroup returns, for each given machine entity, |
1247 | // a slice of instance.Ids that belong to the same distribution |
1248 | // group as that machine. This information may be used to |
1249 | |
1250 | === modified file 'state/apiserver/provisioner/provisioner_test.go' |
1251 | --- state/apiserver/provisioner/provisioner_test.go 2014-04-18 15:31:56 +0000 |
1252 | +++ state/apiserver/provisioner/provisioner_test.go 2014-04-23 06:25:18 +0000 |
1253 | @@ -726,6 +726,87 @@ |
1254 | }) |
1255 | } |
1256 | |
1257 | +func (s *withoutStateServerSuite) TestProvisioningInfo(c *gc.C) { |
1258 | + template := state.MachineTemplate{ |
1259 | + Series: "quantal", |
1260 | + Jobs: []state.MachineJob{state.JobHostUnits}, |
1261 | + Constraints: constraints.MustParse("cpu-cores=123", "mem=8G"), |
1262 | + Placement: "valid", |
1263 | + IncludeNetworks: []string{"net1", "net2"}, |
1264 | + ExcludeNetworks: []string{"net3", "net4"}, |
1265 | + } |
1266 | + placementMachine, err := s.State.AddOneMachine(template) |
1267 | + c.Assert(err, gc.IsNil) |
1268 | + |
1269 | + args := params.Entities{Entities: []params.Entity{ |
1270 | + {Tag: s.machines[0].Tag()}, |
1271 | + {Tag: placementMachine.Tag()}, |
1272 | + {Tag: "machine-42"}, |
1273 | + {Tag: "unit-foo-0"}, |
1274 | + {Tag: "service-bar"}, |
1275 | + }} |
1276 | + result, err := s.provisioner.ProvisioningInfo(args) |
1277 | + c.Assert(err, gc.IsNil) |
1278 | + c.Assert(result, gc.DeepEquals, params.ProvisioningInfoResults{ |
1279 | + Results: []params.ProvisioningInfoResult{ |
1280 | + { |
1281 | + Result: ¶ms.ProvisioningInfo{ |
1282 | + Series: "quantal", |
1283 | + IncludeNetworks: []string{}, |
1284 | + ExcludeNetworks: []string{}, |
1285 | + }, |
1286 | + }, |
1287 | + { |
1288 | + Result: ¶ms.ProvisioningInfo{ |
1289 | + Series: "quantal", |
1290 | + Constraints: template.Constraints, |
1291 | + Placement: template.Placement, |
1292 | + IncludeNetworks: template.IncludeNetworks, |
1293 | + ExcludeNetworks: template.ExcludeNetworks, |
1294 | + }, |
1295 | + }, |
1296 | + {Error: apiservertesting.NotFoundError("machine 42")}, |
1297 | + {Error: apiservertesting.ErrUnauthorized}, |
1298 | + {Error: apiservertesting.ErrUnauthorized}, |
1299 | + }, |
1300 | + }) |
1301 | +} |
1302 | + |
1303 | +func (s *withoutStateServerSuite) TestProvisioningInfoPermissions(c *gc.C) { |
1304 | + // Login as a machine agent for machine 0. |
1305 | + anAuthorizer := s.authorizer |
1306 | + anAuthorizer.MachineAgent = true |
1307 | + anAuthorizer.EnvironManager = false |
1308 | + anAuthorizer.Tag = s.machines[0].Tag() |
1309 | + aProvisioner, err := provisioner.NewProvisionerAPI(s.State, s.resources, anAuthorizer) |
1310 | + c.Assert(err, gc.IsNil) |
1311 | + c.Assert(aProvisioner, gc.NotNil) |
1312 | + |
1313 | + args := params.Entities{Entities: []params.Entity{ |
1314 | + {Tag: s.machines[0].Tag()}, |
1315 | + {Tag: s.machines[0].Tag() + "-lxc-0"}, |
1316 | + {Tag: "machine-42"}, |
1317 | + {Tag: s.machines[1].Tag()}, |
1318 | + {Tag: "service-bar"}, |
1319 | + }} |
1320 | + |
1321 | + // Only machine 0 and containers therein can be accessed. |
1322 | + results, err := aProvisioner.ProvisioningInfo(args) |
1323 | + c.Assert(results, gc.DeepEquals, params.ProvisioningInfoResults{ |
1324 | + Results: []params.ProvisioningInfoResult{ |
1325 | + {Result: ¶ms.ProvisioningInfo{ |
1326 | + Series: "quantal", |
1327 | + IncludeNetworks: []string{}, |
1328 | + ExcludeNetworks: []string{}, |
1329 | + }}, |
1330 | + {Error: apiservertesting.NotFoundError("machine 0/lxc/0")}, |
1331 | + {Error: apiservertesting.ErrUnauthorized}, |
1332 | + {Error: apiservertesting.ErrUnauthorized}, |
1333 | + {Error: apiservertesting.ErrUnauthorized}, |
1334 | + }, |
1335 | + }) |
1336 | +} |
1337 | + |
1338 | func (s *withoutStateServerSuite) TestConstraints(c *gc.C) { |
1339 | // Add a machine with some constraints. |
1340 | template := state.MachineTemplate{ |
1341 | |
1342 | === modified file 'state/machine.go' |
1343 | --- state/machine.go 2014-04-21 23:10:05 +0000 |
1344 | +++ state/machine.go 2014-04-23 06:25:18 +0000 |
1345 | @@ -114,6 +114,9 @@ |
1346 | // machine is capable of hosting. |
1347 | SupportedContainersKnown bool |
1348 | SupportedContainers []instance.ContainerType `bson:",omitempty"` |
1349 | + // Placement is the placement directive that should be used when provisioning |
1350 | + // an instance for the machine. |
1351 | + Placement string `bson:",omitempty"` |
1352 | // Deprecated. InstanceId, now lives on instanceData. |
1353 | // This attribute is retained so that data from existing machines can be read. |
1354 | // SCHEMACHANGE |
1355 | @@ -1090,6 +1093,12 @@ |
1356 | return m.doc.Id |
1357 | } |
1358 | |
1359 | +// Placement returns the machine's Placement structure that should be used when |
1360 | +// provisioning an instance for the machine. |
1361 | +func (m *Machine) Placement() string { |
1362 | + return m.doc.Placement |
1363 | +} |
1364 | + |
1365 | // Constraints returns the exact constraints that should apply when provisioning |
1366 | // an instance for the machine. |
1367 | func (m *Machine) Constraints() (constraints.Value, error) { |
1368 | |
1369 | === modified file 'state/policy.go' |
1370 | --- state/policy.go 2014-04-21 23:24:57 +0000 |
1371 | +++ state/policy.go 2014-04-23 06:25:18 +0000 |
1372 | @@ -49,7 +49,7 @@ |
1373 | // all invalid parameters. If PrecheckInstance returns nil, it is not |
1374 | // guaranteed that the constraints are valid; if a non-nil error is |
1375 | // returned, then the constraints are definitely invalid. |
1376 | - PrecheckInstance(series string, cons constraints.Value) error |
1377 | + PrecheckInstance(series string, cons constraints.Value, placement string) error |
1378 | } |
1379 | |
1380 | // ConfigValidator is a policy interface that is provided to State |
1381 | @@ -78,7 +78,7 @@ |
1382 | |
1383 | // precheckInstance calls the state's assigned policy, if non-nil, to obtain |
1384 | // a Prechecker, and calls PrecheckInstance if a non-nil Prechecker is returned. |
1385 | -func (st *State) precheckInstance(series string, cons constraints.Value) error { |
1386 | +func (st *State) precheckInstance(series string, cons constraints.Value, placement string) error { |
1387 | if st.policy == nil { |
1388 | return nil |
1389 | } |
1390 | @@ -95,7 +95,7 @@ |
1391 | if prechecker == nil { |
1392 | return fmt.Errorf("policy returned nil prechecker without an error") |
1393 | } |
1394 | - return prechecker.PrecheckInstance(series, cons) |
1395 | + return prechecker.PrecheckInstance(series, cons, placement) |
1396 | } |
1397 | |
1398 | func (st *State) constraintsValidator() (constraints.Validator, error) { |
1399 | |
1400 | === modified file 'state/prechecker_test.go' |
1401 | --- state/prechecker_test.go 2014-04-21 23:10:05 +0000 |
1402 | +++ state/prechecker_test.go 2014-04-23 06:25:18 +0000 |
1403 | @@ -26,11 +26,13 @@ |
1404 | precheckInstanceError error |
1405 | precheckInstanceSeries string |
1406 | precheckInstanceConstraints constraints.Value |
1407 | + precheckInstancePlacement string |
1408 | } |
1409 | |
1410 | -func (p *mockPrechecker) PrecheckInstance(series string, cons constraints.Value) error { |
1411 | +func (p *mockPrechecker) PrecheckInstance(series string, cons constraints.Value, placement string) error { |
1412 | p.precheckInstanceSeries = series |
1413 | p.precheckInstanceConstraints = cons |
1414 | + p.precheckInstancePlacement = placement |
1415 | return p.precheckInstanceError |
1416 | } |
1417 | |
1418 | @@ -44,13 +46,15 @@ |
1419 | |
1420 | func (s *PrecheckerSuite) TestPrecheckInstance(c *gc.C) { |
1421 | // PrecheckInstance should be called with the specified |
1422 | - // series, and the specified constraints merged with the |
1423 | - // environment constraints, when attempting to create an |
1424 | - // instance. |
1425 | + // series and placement, and the specified constraints |
1426 | + // merged with the environment constraints, when attempting |
1427 | + // to create an instance. |
1428 | envCons := constraints.MustParse("mem=4G") |
1429 | - template, err := s.addOneMachine(c, envCons) |
1430 | + placement := "abc123" |
1431 | + template, err := s.addOneMachine(c, envCons, placement) |
1432 | c.Assert(err, gc.IsNil) |
1433 | c.Assert(s.prechecker.precheckInstanceSeries, gc.Equals, template.Series) |
1434 | + c.Assert(s.prechecker.precheckInstancePlacement, gc.Equals, placement) |
1435 | validator := constraints.NewValidator() |
1436 | cons, err := validator.Merge(envCons, template.Constraints) |
1437 | c.Assert(err, gc.IsNil) |
1438 | @@ -60,14 +64,14 @@ |
1439 | func (s *PrecheckerSuite) TestPrecheckErrors(c *gc.C) { |
1440 | // Ensure that AddOneMachine fails when PrecheckInstance returns an error. |
1441 | s.prechecker.precheckInstanceError = fmt.Errorf("no instance for you") |
1442 | - _, err := s.addOneMachine(c, constraints.Value{}) |
1443 | + _, err := s.addOneMachine(c, constraints.Value{}, "placement") |
1444 | c.Assert(err, gc.ErrorMatches, ".*no instance for you") |
1445 | |
1446 | // If the policy's Prechecker method fails, that will be returned first. |
1447 | s.policy.getPrechecker = func(*config.Config) (state.Prechecker, error) { |
1448 | return nil, fmt.Errorf("no prechecker for you") |
1449 | } |
1450 | - _, err = s.addOneMachine(c, constraints.Value{}) |
1451 | + _, err = s.addOneMachine(c, constraints.Value{}, "placement") |
1452 | c.Assert(err, gc.ErrorMatches, ".*no prechecker for you") |
1453 | } |
1454 | |
1455 | @@ -76,10 +80,10 @@ |
1456 | s.policy.getPrechecker = func(*config.Config) (state.Prechecker, error) { |
1457 | return nil, precheckerErr |
1458 | } |
1459 | - _, err := s.addOneMachine(c, constraints.Value{}) |
1460 | + _, err := s.addOneMachine(c, constraints.Value{}, "placement") |
1461 | c.Assert(err, gc.ErrorMatches, "cannot add a new machine: policy returned nil prechecker without an error") |
1462 | precheckerErr = errors.NotImplementedf("Prechecker") |
1463 | - _, err = s.addOneMachine(c, constraints.Value{}) |
1464 | + _, err = s.addOneMachine(c, constraints.Value{}, "placement") |
1465 | c.Assert(err, gc.IsNil) |
1466 | } |
1467 | |
1468 | @@ -89,11 +93,11 @@ |
1469 | return nil, nil |
1470 | } |
1471 | state.SetPolicy(s.State, nil) |
1472 | - _, err := s.addOneMachine(c, constraints.Value{}) |
1473 | + _, err := s.addOneMachine(c, constraints.Value{}, "placement") |
1474 | c.Assert(err, gc.IsNil) |
1475 | } |
1476 | |
1477 | -func (s *PrecheckerSuite) addOneMachine(c *gc.C, envCons constraints.Value) (state.MachineTemplate, error) { |
1478 | +func (s *PrecheckerSuite) addOneMachine(c *gc.C, envCons constraints.Value, placement string) (state.MachineTemplate, error) { |
1479 | err := s.State.SetEnvironConstraints(envCons) |
1480 | c.Assert(err, gc.IsNil) |
1481 | oneJob := []state.MachineJob{state.JobHostUnits} |
1482 | @@ -102,6 +106,7 @@ |
1483 | Series: "precise", |
1484 | Constraints: extraCons, |
1485 | Jobs: oneJob, |
1486 | + Placement: placement, |
1487 | } |
1488 | _, err = s.State.AddOneMachine(template) |
1489 | return template, err |
1490 | @@ -113,22 +118,26 @@ |
1491 | Series: "precise", |
1492 | Nonce: state.BootstrapNonce, |
1493 | Jobs: []state.MachineJob{state.JobManageEnviron}, |
1494 | + Placement: "anyoldthing", |
1495 | } |
1496 | _, err := s.State.AddOneMachine(template) |
1497 | c.Assert(err, gc.IsNil) |
1498 | // PrecheckInstance should not have been called, as we've |
1499 | // injected a machine with an existing instance. |
1500 | c.Assert(s.prechecker.precheckInstanceSeries, gc.Equals, "") |
1501 | + c.Assert(s.prechecker.precheckInstancePlacement, gc.Equals, "") |
1502 | } |
1503 | |
1504 | func (s *PrecheckerSuite) TestPrecheckContainerNewMachine(c *gc.C) { |
1505 | // Attempting to add a container to a new machine should cause |
1506 | // PrecheckInstance to be called. |
1507 | template := state.MachineTemplate{ |
1508 | - Series: "precise", |
1509 | - Jobs: []state.MachineJob{state.JobHostUnits}, |
1510 | + Series: "precise", |
1511 | + Jobs: []state.MachineJob{state.JobHostUnits}, |
1512 | + Placement: "intertubes", |
1513 | } |
1514 | _, err := s.State.AddMachineInsideNewMachine(template, template, instance.LXC) |
1515 | c.Assert(err, gc.IsNil) |
1516 | c.Assert(s.prechecker.precheckInstanceSeries, gc.Equals, template.Series) |
1517 | + c.Assert(s.prechecker.precheckInstancePlacement, gc.Equals, template.Placement) |
1518 | } |
1519 | |
1520 | === modified file 'worker/provisioner/provisioner_task.go' |
1521 | --- worker/provisioner/provisioner_task.go 2014-04-18 16:37:28 +0000 |
1522 | +++ worker/provisioner/provisioner_task.go 2014-04-23 06:25:18 +0000 |
1523 | @@ -5,6 +5,7 @@ |
1524 | |
1525 | import ( |
1526 | "fmt" |
1527 | + "time" |
1528 | |
1529 | "launchpad.net/tomb" |
1530 | |
1531 | @@ -450,26 +451,19 @@ |
1532 | } |
1533 | |
1534 | func (task *provisionerTask) startMachine(machine *apiprovisioner.Machine) error { |
1535 | - cons, err := machine.Constraints() |
1536 | - if err != nil { |
1537 | - return err |
1538 | - } |
1539 | - series, err := machine.Series() |
1540 | - if err != nil { |
1541 | - return err |
1542 | - } |
1543 | - possibleTools, err := task.possibleTools(series, cons) |
1544 | - if err != nil { |
1545 | - return err |
1546 | - } |
1547 | - machineConfig, err := task.machineConfig(machine) |
1548 | + provisioningInfo, err := task.provisioningInfo(machine) |
1549 | + if err != nil { |
1550 | + return err |
1551 | + } |
1552 | + possibleTools, err := task.possibleTools(provisioningInfo.Series, provisioningInfo.Constraints) |
1553 | if err != nil { |
1554 | return err |
1555 | } |
1556 | inst, metadata, networkInfo, err := task.broker.StartInstance(environs.StartInstanceParams{ |
1557 | - Constraints: cons, |
1558 | + Constraints: provisioningInfo.Constraints, |
1559 | Tools: possibleTools, |
1560 | - MachineConfig: machineConfig, |
1561 | + MachineConfig: provisioningInfo.MachineConfig, |
1562 | + Placement: provisioningInfo.Placement, |
1563 | DistributionGroup: machine.DistributionGroup, |
1564 | }) |
1565 | if err != nil { |
1566 | @@ -478,7 +472,7 @@ |
1567 | // error; just keep going with the other machines. |
1568 | return task.setErrorStatus("cannot start instance for machine %q: %v", machine, err) |
1569 | } |
1570 | - nonce := machineConfig.MachineNonce |
1571 | + nonce := provisioningInfo.MachineConfig.MachineNonce |
1572 | networks, ifaces := task.prepareNetworkAndInterfaces(networkInfo) |
1573 | |
1574 | err = machine.SetInstanceInfo(inst.Id(), nonce, metadata, networks, ifaces) |
1575 | @@ -512,7 +506,14 @@ |
1576 | panic(fmt.Errorf("broker of type %T does not provide any tools", task.broker)) |
1577 | } |
1578 | |
1579 | -func (task *provisionerTask) machineConfig(machine *apiprovisioner.Machine) (*cloudinit.MachineConfig, error) { |
1580 | +type provisioningInfo struct { |
1581 | + Constraints constraints.Value |
1582 | + Series string |
1583 | + Placement string |
1584 | + MachineConfig *cloudinit.MachineConfig |
1585 | +} |
1586 | + |
1587 | +func (task *provisionerTask) provisioningInfo(machine *apiprovisioner.Machine) (*provisioningInfo, error) { |
1588 | stateInfo, apiInfo, err := task.auth.SetupAuthentication(machine) |
1589 | if err != nil { |
1590 | logger.Errorf("failed to setup authentication: %v", err) |
1591 | @@ -525,11 +526,32 @@ |
1592 | if err != nil { |
1593 | return nil, err |
1594 | } |
1595 | - includeNetworks, excludeNetworks, err := machine.RequestedNetworks() |
1596 | - if err != nil { |
1597 | + // ProvisioningInfo is new in 1.20; wait for the API server to be upgraded |
1598 | + // so we don't spew errors on upgrade. |
1599 | + var pInfo *params.ProvisioningInfo |
1600 | + for { |
1601 | + if pInfo, err = machine.ProvisioningInfo(); err == nil { |
1602 | + break |
1603 | + } |
1604 | + if params.IsCodeNotImplemented(err) { |
1605 | + logger.Infof("waiting for state server to be upgraded") |
1606 | + select { |
1607 | + case <-task.tomb.Dying(): |
1608 | + return nil, tomb.ErrDying |
1609 | + case <-time.After(15 * time.Second): |
1610 | + continue |
1611 | + } |
1612 | + } |
1613 | return nil, err |
1614 | } |
1615 | + includeNetworks := pInfo.IncludeNetworks |
1616 | + excludeNetworks := pInfo.ExcludeNetworks |
1617 | nonce := fmt.Sprintf("%s:%s", task.machineTag, uuid.String()) |
1618 | machineConfig := environs.NewMachineConfig(machine.Id(), nonce, includeNetworks, excludeNetworks, stateInfo, apiInfo) |
1619 | - return machineConfig, nil |
1620 | + return &provisioningInfo{ |
1621 | + Constraints: pInfo.Constraints, |
1622 | + Series: pInfo.Series, |
1623 | + Placement: pInfo.Placement, |
1624 | + MachineConfig: machineConfig, |
1625 | + }, nil |
1626 | } |
Reviewers: mp+214761_ code.launchpad. net,
Message:
Please take a look.
Description:
Introduce instance.Placement
This change introduces a new Placement specific placement
structure in the instance package that
encapsulates machine-id, container, and
(new) environment-
directives.
The add-machine subcommand is modified lacement.
to pass this new Placement structure
through the AddMachine client API, which
calls a new state method AddMachineWithP
Machine-id and container placement do the
same thing as before, while new environment-
specific placement structures are stored in
state. A new state policy is added to allow
an Environ to validate placement.
The provisioner API and worker are updated
to pass Placement through to StartInstance.
Follow-ups to come:
- update MAAS provider to pick maas-name out
the Placement.
- update ec2, OpenStack (and MAAS?) to pick
out availbility-zone from Placement.
- update add-unit and deploy subcommands to
use Placement; this will involve modifications
to state unit assignment to choose existing
matching machines (e.g. for maas-name).
https:/ /code.launchpad .net/~axwalk/ juju-core/ addmachine- placement/ +merge/ 214761
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/85040046/
Affected files (+622, -63 lines): addmachine. go addmachine_ test.go interface. go statepolicy. go placement. go placement_ test.go azure/environ. go dummy/environs. go joyent/ environ. go local/environ. go maas/environ. go manual/ environ. go openstack/ provider. go params/ params. go provisioner/ machine. go provisioner/ provisioner_ test.go /client/ client. go /client/ client_ test.go /provisioner/ provisioner. go /provisioner/ provisioner_ test.go validator_ test.go provisioner/ provisioner_ task.go
A [revision details]
M cmd/juju/
M cmd/juju/
M environs/broker.go
M environs/
M environs/
A instance/
A instance/
M provider/
M provider/
M provider/ec2/ec2.go
M provider/
M provider/
M provider/
M provider/
M provider/
M state/addmachine.go
M state/api/
M state/api/
M state/api/
M state/apiserver
M state/apiserver
M state/apiserver
M state/apiserver
M state/conn_test.go
M state/machine.go
A state/placement
M state/policy.go
M worker/