Merge lp:~axwalk/juju-core/addmachine-placement into lp:~go-bot/juju-core/trunk

Proposed by Andrew Wilkins
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
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-specific placement
directives.

The add-machine subcommand is modified
to pass this new Placement structure
through the AddMachine client API, which
calls a new state method AddMachineWithPlacement.

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.

https://codereview.appspot.com/85040046/

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-specific placement
directives.

The add-machine subcommand is modified
to pass this new Placement structure
through the AddMachine client API, which
calls a new state method AddMachineWithPlacement.

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://codereview.appspot.com/85040046/

To post a comment you must log in.
Revision history for this message
Andrew Wilkins (axwalk) wrote :

Reviewers: mp+214761_code.launchpad.net,

Message:
Please take a look.

Description:
Introduce instance.Placement

This change introduces a new Placement
structure in the instance package that
encapsulates machine-id, container, and
(new) environment-specific placement
directives.

The add-machine subcommand is modified
to pass this new Placement structure
through the AddMachine client API, which
calls a new state method AddMachineWithPlacement.

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):
   A [revision details]
   M cmd/juju/addmachine.go
   M cmd/juju/addmachine_test.go
   M environs/broker.go
   M environs/interface.go
   M environs/statepolicy.go
   A instance/placement.go
   A instance/placement_test.go
   M provider/azure/environ.go
   M provider/dummy/environs.go
   M provider/ec2/ec2.go
   M provider/joyent/environ.go
   M provider/local/environ.go
   M provider/maas/environ.go
   M provider/manual/environ.go
   M provider/openstack/provider.go
   M state/addmachine.go
   M state/api/params/params.go
   M state/api/provisioner/machine.go
   M state/api/provisioner/provisioner_test.go
   M state/apiserver/client/client.go
   M state/apiserver/client/client_test.go
   M state/apiserver/provisioner/provisioner.go
   M state/apiserver/provisioner/provisioner_test.go
   M state/conn_test.go
   M state/machine.go
   A state/placementvalidator_test.go
   M state/policy.go
   M worker/provisioner/provisioner_task.go

Revision history for this message
Ian Booth (wallyworld) wrote :
Download full text (4.4 KiB)

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://codereview.appspot.com/85040046/diff/1/cmd/juju/addmachine.go
File cmd/juju/addmachine.go (right):

https://codereview.appspot.com/85040046/diff/1/cmd/juju/addmachine.go#newcode127
cmd/juju/addmachine.go:127: results, err :=
client.AddMachines([]params.AddMachineParams{machineParams})
What about backwards compatibility?

https://codereview.appspot.com/85040046/diff/1/cmd/juju/addmachine.go#newcode137
cmd/juju/addmachine.go:137: // TODO(axw) figure out how to convey
whether it's a container?
Can't we inspect the scope to see if it's a valid container type?

https://codereview.appspot.com/85040046/diff/1/instance/placement_test.go
File instance/placement_test.go (right):

https://codereview.appspot.com/85040046/diff/1/instance/placement_test.go#newcode25
instance/placement_test.go:25: expect: &instance.Placement{Scope:
instance.MachineScope, Value: "0"},
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://codereview.appspot.com/85040046/diff/1/provider/dummy/environs.go
File provider/dummy/environs.go (right):

https://codereview.appspot.com/85040046/diff/1/provider/dummy/environs.go#newcode546
provider/dummy/environs.go:546: func (*environ) ValidatePlacement(p
*instance.Placement) error {
I know we check for nil elsewhere, but maybe we should have it here as
well to be defensive and avoid a npe?

https://codereview.appspot.com/85040046/diff/1/state/addmachine.go
File state/addmachine.go (right):

https://codereview.appspot.com/85040046/diff/1/state/addmachine.go#newcode85
state/addmachine.go:85: func (st *State)
AddMachineWithPlacement(template MachineTemplate, placement
*instance.Placement) (*Machine, error) {
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://codereview.appspot.com/85040046/diff/1/state/api/params/params.go
File state/api/params/params.go (right):

https://codereview.appspot.com/85040046/diff/1/state/api/params/params.go#newcode107
state/api/params/params.go:107: ContainerType instance.ContainerType
So we are retaining ContainerType and ParentId but add-machine does not
set them anymore?

https://codereview.appspot.com/85040046/diff/1/state/api/provisioner/provisioner_test.go
File state/api/provisioner/provisioner_test.go (right):

https://codereview.appspot.com/85040046/diff/1/state/api/provisioner/provisioner_test.go#newcode330
state/api/provisioner/provisioner_test.go:330: c.Assert(err,
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?

https://codereview.appspot.com/85...

Read more...

Revision history for this message
Andrew Wilkins (axwalk) wrote :
Download full text (4.2 KiB)

> 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://codereview.appspot.com/85040046/diff/1/cmd/juju/addmachine.go
File cmd/juju/addmachine.go (right):

https://codereview.appspot.com/85040046/diff/1/cmd/juju/addmachine.go#newcode127
cmd/juju/addmachine.go:127: results, err :=
client.AddMachines([]params.AddMachineParams{machineParams})
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://codereview.appspot.com/85040046/diff/1/cmd/juju/addmachine.go#newcode137
cmd/juju/addmachine.go:137: // TODO(axw) figure out how to convey
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://codereview.appspot.com/85040046/diff/1/instance/placement_test.go
File instance/placement_test.go (right):

https://codereview.appspot.com/85040046/diff/1/instance/placement_test.go#newcode25
instance/placement_test.go:25: expect: &instance.Placement{Scope:
instance.MachineScope, Value: "0"},
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://codereview.appspot.com/85040046/diff/1/provider/dummy/environs.go
File provider/dummy/environs.go (right):

https://codereview.appspot.com/85040046/diff/1/provider/dummy/environs.go#newcode546
provider/dummy/environs.go:546: func (*environ) ValidatePlacement(p
*instance.Placement) error {
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://codereview.appspot.com/85040046/diff/1/state/api/params/params.go
File state/api/params/params.go (right):

https://codereview.appspot.com/85040046/diff/1/state/api/params/params.go#newcode107
state/api/params/params.go:107: ContainerType instance.ContainerType
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://codereview.appspot.com/85040046/diff/1/state/apiserver/provisioner/provisioner_test.go
File state/apiserver/provisioner/provisio...

Read more...

Revision history for this message
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.)

https://codereview.appspot.com/85040046/

Revision history for this message
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/ParentId.

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.

https://codereview.appspot.com/85040046/

Revision history for this message
William Reade (fwereade) wrote :
Download full text (9.1 KiB)

Coming along nicely -- let me know what you think about the following.

https://codereview.appspot.com/85040046/diff/1/cmd/juju/addmachine.go
File cmd/juju/addmachine.go (right):

https://codereview.appspot.com/85040046/diff/1/cmd/juju/addmachine.go#newcode137
cmd/juju/addmachine.go:137: // TODO(axw) figure out how to convey
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://codereview.appspot.com/85040046/diff/1/environs/interface.go
File environs/interface.go (right):

https://codereview.appspot.com/85040046/diff/1/environs/interface.go#newcode160
environs/interface.go:160: state.PlacementValidator
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://codereview.appspot.com/85040046/diff/1/instance/placement.go
File instance/placement.go (right):

https://codereview.appspot.com/85040046/diff/1/instance/placement.go#newcode54
instance/placement.go:54: if (scope == MachineScope ||
isContainerType(scope)) && !names.IsMachine(value) {
Hmm. Problem coming: environments named after a container type will act
in a surprising way. Can you think of any way around this?

https://codereview.appspot.com/85040046/diff/1/instance/placement.go#newcode63
instance/placement.go:63: return &Placement{Scope: directive}, nil
I'm not following this case. `add-machine --to kvm`? I think we need a
target machine for this to make sense.

https://codereview.appspot.com/85040046/diff/1/instance/placement.go#newcode66
instance/placement.go:66: return &Placement{Value: directive}, nil
I think I'd prefer an explict ErrScopeUnclear, and explicit handling
thereof in the client.

https://codereview.appspot.com/85040046/diff/1/instance/placement.go#newcode69
instance/placement.go:69: // ParsePlacement attempts to parse the
specified string and create a
s/Parse/MustParse/

https://codereview.appspot.com/85040046/diff/1/provider/azure/environ.go
File provider/azure/environ.go (right):

https://codereview.appspot.com/85040046/diff/1/provider/azure/environ.go#newcode1179
provider/azure/environ.go:1179: return fmt.Errorf("unknown placement
directive: %s", p)
Do we want a whole Placement here, or should we maybe just handle the
directive alone?

https://codereview.appspot.com/85040046/diff/1/provider/dummy/environs.go
File provider/dummy/environs.go (right):

https://codereview.appspot.com/85040046/diff/1/provider/dummy/environs.go#newcode548
provider/dummy/environs.go:548: case "valid":
"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...

Read more...

Revision history for this message
Andrew Wilkins (axwalk) wrote :
Download full text (7.3 KiB)

Please take a look.

https://codereview.appspot.com/85040046/diff/1/cmd/juju/addmachine.go
File cmd/juju/addmachine.go (right):

https://codereview.appspot.com/85040046/diff/1/cmd/juju/addmachine.go#newcode127
cmd/juju/addmachine.go:127: results, err :=
client.AddMachines([]params.AddMachineParams{machineParams})
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://codereview.appspot.com/85040046/diff/1/cmd/juju/addmachine.go#newcode137
cmd/juju/addmachine.go:137: // TODO(axw) figure out how to convey
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://codereview.appspot.com/85040046/diff/1/environs/interface.go
File environs/interface.go (right):

https://codereview.appspot.com/85040046/diff/1/environs/interface.go#newcode160
environs/interface.go:160: state.PlacementValidator
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://codereview.appspot.com/85040046/diff/1/instance/placement.go
File instance/placement.go (right):

https://codereview.appspot.com/85040046/diff/1/instance/placement.go#newcode54
instance/placement.go:54: if (scope == MachineScope ||
isContainerType(scope)) && !names.IsMachine(value) {
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://codereview.appspot.com/85040046/diff/1/instance/placement.go#newcode63
instance/placement.go:63: return &Placement{Scope: directive}, nil
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.Client.addOneMachine.

https://codereview.appspot.com/85040046/diff/1/instance/placement.go#newcode66
ins...

Read more...

Revision history for this message
William Reade (fwereade) wrote :
Download full text (5.8 KiB)

Looking very nice, only a couple of details that maybe demand
thought/discussion.

https://codereview.appspot.com/85040046/diff/1/instance/placement.go
File instance/placement.go (right):

https://codereview.appspot.com/85040046/diff/1/instance/placement.go#newcode54
instance/placement.go:54: if (scope == MachineScope ||
isContainerType(scope)) && !names.IsMachine(value) {
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://codereview.appspot.com/85040046/diff/1/instance/placement.go#newcode63
instance/placement.go:63: return &Placement{Scope: directive}, nil
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.Client.addOneMachine.

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://codereview.appspot.com/85040046/diff/20001/cmd/juju/addmachine.go
File cmd/juju/addmachine.go (right):

https://codereview.appspot.com/85040046/diff/20001/cmd/juju/addmachine.go#newcode123
cmd/juju/addmachine.go:123: // message.
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://codereview.appspot.com/85040046/diff/20001/environs/jujutest/livetests.go
File environs/jujutest/livetests.go (right):

https://codereview.appspot.com/85040046/diff/20001/environs/jujutest/livetests.go#newcode166
environs/jujutest/livetests.go:166: err :=
prechecker.PrecheckInstance("precise", constraints.Value{}, placement)
<3

https://codereview.appspot.com/85040046/diff/20001/instance/placement_test.go
File instance/placement_test.go (right):

https://codereview.appspot.com/85040046/diff/20001/instance/placement_test.go#newcode16
instance/placement_test.go:16: var parsePlacementTests = []struct {
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://codereview.appspot.com/85040046/diff/20001/provider/maas/environ.go
File provider/maas/env...

Read more...

Revision history for this message
Andrew Wilkins (axwalk) wrote :
Download full text (5.1 KiB)

Please take a look.

https://codereview.appspot.com/85040046/diff/20001/cmd/juju/addmachine.go
File cmd/juju/addmachine.go (right):

https://codereview.appspot.com/85040046/diff/20001/cmd/juju/addmachine.go#newcode123
cmd/juju/addmachine.go:123: // message.
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/ParentId. I now think you were referring to machine
templates.

https://codereview.appspot.com/85040046/diff/20001/instance/placement_test.go
File instance/placement_test.go (right):

https://codereview.appspot.com/85040046/diff/20001/instance/placement_test.go#newcode16
instance/placement_test.go:16: var parsePlacementTests = []struct {
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://codereview.appspot.com/85040046/diff/20001/provider/maas/environ.go
File provider/maas/environ.go (right):

https://codereview.appspot.com/85040046/diff/20001/provider/maas/environ.go#newcode173
provider/maas/environ.go:173: // TODO(axw) handle maas-name placement
directive
On 2014/04/18 09:15:52, fwereade wrote:
> deserves a bug#

Done.

https://codereview.appspot.com/85040046/diff/20001/state/api/client.go
File state/api/client.go (right):

https://codereview.appspot.com/85040046/diff/20001/state/api/client.go#newcode230
state/api/client.go:230: // This exists for backwards compatibility;
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-to-1.18-server compat.

https://codereview.appspot.com/85040046/diff/20001/state/api/client.go#newcode246
state/api/client.go:246: err := c.call("AddMachinesV2", args, results)
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://codereview.appspot.com/85040046/diff/20001/state/api/params/params.go
File state/api/params/params.go (right):

https://codereview.appspot.com/85040046/diff/20001/state/api/params/params.go#newcode714
state/api/params/params.go:714: Results []PlacementResult
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+constraints+placement (and I guess instanc...

Read more...

Revision history for this message
Andrew Wilkins (axwalk) wrote :
Revision history for this message
Andrew Wilkins (axwalk) wrote :
Revision history for this message
Ian Booth (wallyworld) wrote :

LGTM with the error tweak as discussed

https://codereview.appspot.com/85040046/

Revision history for this message
Andrew Wilkins (axwalk) wrote :
Revision history for this message
Andrew Wilkins (axwalk) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'cmd/juju/addmachine.go'
--- cmd/juju/addmachine.go 2014-04-09 22:01:03 +0000
+++ cmd/juju/addmachine.go 2014-04-23 06:25:18 +0000
@@ -5,7 +5,6 @@
55
6import (6import (
7 "fmt"7 "fmt"
8 "strings"
98
10 "launchpad.net/gnuflag"9 "launchpad.net/gnuflag"
1110
@@ -60,10 +59,9 @@
60 // If specified, use this series, else use the environment default-series59 // If specified, use this series, else use the environment default-series
61 Series string60 Series string
62 // If specified, these constraints are merged with those already in the environment.61 // If specified, these constraints are merged with those already in the environment.
63 Constraints constraints.Value62 Constraints constraints.Value
64 MachineId string63 // Placement is passed verbatim to the API, to be parsed and evaluated server-side.
65 ContainerType instance.ContainerType64 Placement *instance.Placement
66 SSHHost string
67}65}
6866
69func (c *AddMachineCommand) Info() *cmd.Info {67func (c *AddMachineCommand) Info() *cmd.Info {
@@ -89,33 +87,25 @@
89 if c.Constraints.Container != nil {87 if c.Constraints.Container != nil {
90 return fmt.Errorf("container constraint %q not allowed when adding a machine", *c.Constraints.Container)88 return fmt.Errorf("container constraint %q not allowed when adding a machine", *c.Constraints.Container)
91 }89 }
92 containerSpec, err := cmd.ZeroOrOneArgs(args)90 placement, err := cmd.ZeroOrOneArgs(args)
93 if err != nil {91 if err != nil {
94 return err92 return err
95 }93 }
96 if containerSpec == "" {94 c.Placement, err = instance.ParsePlacement(placement)
97 return nil95 if err == instance.ErrPlacementScopeMissing {
98 }96 placement = c.EnvironName() + ":" + placement
99 if strings.HasPrefix(containerSpec, sshHostPrefix) {97 c.Placement, err = instance.ParsePlacement(placement)
100 c.SSHHost = containerSpec[len(sshHostPrefix):]98 }
101 } else {99 if err != nil {
102 // container arg can either be 'type:machine' or 'type'100 return err
103 if c.ContainerType, err = instance.ParseContainerType(containerSpec); err != nil {101 }
104 if names.IsMachine(containerSpec) || !cmd.IsMachineOrNewContainer(containerSpec) {102 return nil
105 return fmt.Errorf("malformed container argument %q", containerSpec)
106 }
107 sep := strings.Index(containerSpec, ":")
108 c.MachineId = containerSpec[sep+1:]
109 c.ContainerType, err = instance.ParseContainerType(containerSpec[:sep])
110 }
111 }
112 return err
113}103}
114104
115func (c *AddMachineCommand) Run(ctx *cmd.Context) error {105func (c *AddMachineCommand) Run(ctx *cmd.Context) error {
116 if c.SSHHost != "" {106 if c.Placement != nil && c.Placement.Scope == "ssh" {
117 args := manual.ProvisionMachineArgs{107 args := manual.ProvisionMachineArgs{
118 Host: c.SSHHost,108 Host: c.Placement.Directive,
119 EnvName: c.EnvName,109 EnvName: c.EnvName,
120 Stdin: ctx.Stdin,110 Stdin: ctx.Stdin,
121 Stdout: ctx.Stdout,111 Stdout: ctx.Stdout,
@@ -131,27 +121,51 @@
131 }121 }
132 defer client.Close()122 defer client.Close()
133123
124 if c.Placement != nil && c.Placement.Scope == instance.MachineScope {
125 // It does not make sense to add-machine <id>.
126 return fmt.Errorf("machine-id cannot be specified when adding machines")
127 }
128
134 machineParams := params.AddMachineParams{129 machineParams := params.AddMachineParams{
135 ParentId: c.MachineId,130 Placement: c.Placement,
136 ContainerType: c.ContainerType,131 Series: c.Series,
137 Series: c.Series,132 Constraints: c.Constraints,
138 Constraints: c.Constraints,133 Jobs: []params.MachineJob{params.JobHostUnits},
139 Jobs: []params.MachineJob{params.JobHostUnits},
140 }134 }
141 results, err := client.AddMachines([]params.AddMachineParams{machineParams})135 results, err := client.AddMachines([]params.AddMachineParams{machineParams})
136 if params.IsCodeNotImplemented(err) {
137 if c.Placement != nil {
138 containerType, parseErr := instance.ParseContainerType(c.Placement.Scope)
139 if parseErr != nil {
140 // The user specified a non-container placement directive:
141 // return original API not implemented error.
142 return err
143 }
144 machineParams.ContainerType = containerType
145 machineParams.ParentId = c.Placement.Directive
146 machineParams.Placement = nil
147 }
148 logger.Infof(
149 "AddMachinesWithPlacement not supported by the API server, " +
150 "falling back to 1.18 compatibility mode",
151 )
152 results, err = client.AddMachines1dot18([]params.AddMachineParams{machineParams})
153 }
142 if err != nil {154 if err != nil {
143 return err155 return err
144 }156 }
157
145 // Currently, only one machine is added, but in future there may be several added in one call.158 // Currently, only one machine is added, but in future there may be several added in one call.
146 machineInfo := results[0]159 machineInfo := results[0]
147 if machineInfo.Error != nil {160 if machineInfo.Error != nil {
148 return machineInfo.Error161 return machineInfo.Error
149 }162 }
150 machineId := machineInfo.Machine163 machineId := machineInfo.Machine
151 if c.ContainerType == "" {164
152 logger.Infof("created machine %v", machineId)165 if names.IsContainerMachine(machineId) {
166 ctx.Infof("created container %v", machineId)
153 } else {167 } else {
154 logger.Infof("created %q container on machine %v", c.ContainerType, machineId)168 ctx.Infof("created machine %v", machineId)
155 }169 }
156 return nil170 return nil
157}171}
158172
=== modified file 'cmd/juju/addmachine_test.go'
--- cmd/juju/addmachine_test.go 2014-03-13 07:54:56 +0000
+++ cmd/juju/addmachine_test.go 2014-04-23 06:25:18 +0000
@@ -107,13 +107,17 @@
107107
108func (s *AddMachineSuite) TestAddMachineErrors(c *gc.C) {108func (s *AddMachineSuite) TestAddMachineErrors(c *gc.C) {
109 err := runAddMachine(c, ":lxc")109 err := runAddMachine(c, ":lxc")
110 c.Assert(err, gc.ErrorMatches, `malformed container argument ":lxc"`)110 c.Check(err, gc.ErrorMatches, `cannot add a new machine: :lxc placement is invalid`)
111 err = runAddMachine(c, "lxc:")111 err = runAddMachine(c, "lxc:")
112 c.Assert(err, gc.ErrorMatches, `malformed container argument "lxc:"`)112 c.Check(err, gc.ErrorMatches, `invalid value "" for "lxc" scope: expected machine-id`)
113 err = runAddMachine(c, "2")113 err = runAddMachine(c, "2")
114 c.Assert(err, gc.ErrorMatches, `malformed container argument "2"`)114 c.Check(err, gc.ErrorMatches, `machine-id cannot be specified when adding machines`)
115 err = runAddMachine(c, "foo")115 err = runAddMachine(c, "foo")
116 c.Assert(err, gc.ErrorMatches, `malformed container argument "foo"`)116 c.Check(err, gc.ErrorMatches, `cannot add a new machine: foo placement is invalid`)
117 err = runAddMachine(c, "foo:bar")
118 c.Check(err, gc.ErrorMatches, `invalid environment name "foo"`)
119 err = runAddMachine(c, "dummyenv:invalid")
120 c.Check(err, gc.ErrorMatches, `cannot add a new machine: invalid placement is invalid`)
117 err = runAddMachine(c, "lxc", "--constraints", "container=lxc")121 err = runAddMachine(c, "lxc", "--constraints", "container=lxc")
118 c.Assert(err, gc.ErrorMatches, `container constraint "lxc" not allowed when adding a machine`)122 c.Check(err, gc.ErrorMatches, `container constraint "lxc" not allowed when adding a machine`)
119}123}
120124
=== modified file 'environs/broker.go'
--- environs/broker.go 2014-04-18 16:37:28 +0000
+++ environs/broker.go 2014-04-23 06:25:18 +0000
@@ -25,6 +25,11 @@
25 // MachineConfig describes the machine's configuration.25 // MachineConfig describes the machine's configuration.
26 MachineConfig *cloudinit.MachineConfig26 MachineConfig *cloudinit.MachineConfig
2727
28 // Placement, if non-empty, contains an environment-specific
29 // placement directive that may be used to decide how the
30 // instance should be started.
31 Placement string
32
28 // DistributionGroup, if non-nil, is a function33 // DistributionGroup, if non-nil, is a function
29 // that returns a slice of instance.Ids that belong34 // that returns a slice of instance.Ids that belong
30 // to the same distribution group as the machine35 // to the same distribution group as the machine
3136
=== modified file 'environs/jujutest/livetests.go'
--- environs/jujutest/livetests.go 2014-04-21 23:10:05 +0000
+++ environs/jujutest/livetests.go 2014-04-23 06:25:18 +0000
@@ -162,7 +162,8 @@
162 if !ok {162 if !ok {
163 return163 return
164 }164 }
165 err := prechecker.PrecheckInstance("precise", constraints.Value{})165 const placement = ""
166 err := prechecker.PrecheckInstance("precise", constraints.Value{}, placement)
166 c.Assert(err, gc.IsNil)167 c.Assert(err, gc.IsNil)
167}168}
168169
169170
=== added file 'instance/placement.go'
--- instance/placement.go 1970-01-01 00:00:00 +0000
+++ instance/placement.go 2014-04-23 06:25:18 +0000
@@ -0,0 +1,85 @@
1// Copyright 2014 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package instance
5
6import (
7 "fmt"
8 "strings"
9
10 "launchpad.net/juju-core/names"
11)
12
13const (
14 // MachineScope is a special scope name that is used
15 // for machine placement directives (e.g. --to 0).
16 MachineScope = "#"
17)
18
19var ErrPlacementScopeMissing = fmt.Errorf("placement scope missing")
20
21// Placement defines a placement directive, which has a scope
22// and a value that is scope-specific.
23type Placement struct {
24 // Scope is the scope of the placement directive. Scope may
25 // be a container type (lxc, kvm), instance.MachineScope, or
26 // an environment name.
27 //
28 // If Scope is empty, then it must be inferred from the context.
29 Scope string
30
31 // Directive is a scope-specific placement idrective.
32 //
33 // For MachineScope or a container scope, this may be empty or
34 // the ID of an existing machine.
35 Directive string
36}
37
38func (p *Placement) String() string {
39 return fmt.Sprintf("%s:%s", p.Scope, p.Directive)
40}
41
42func isContainerType(s string) bool {
43 _, err := ParseContainerType(s)
44 return err == nil
45}
46
47// ParsePlacement attempts to parse the specified string and create a
48// corresponding Placement structure.
49//
50// If the placement directive is non-empty and missing a scope,
51// ErrPlacementScopeMissing will be returned as well as a Placement
52// with an empty Scope field.
53func ParsePlacement(directive string) (*Placement, error) {
54 if directive == "" {
55 return nil, nil
56 }
57 if colon := strings.IndexRune(directive, ':'); colon != -1 {
58 scope, directive := directive[:colon], directive[colon+1:]
59 if scope == "" {
60 return nil, ErrPlacementScopeMissing
61 }
62 // Sanity check: machine/container scopes require a machine ID as the value.
63 if (scope == MachineScope || isContainerType(scope)) && !names.IsMachine(directive) {
64 return nil, fmt.Errorf("invalid value %q for %q scope: expected machine-id", directive, scope)
65 }
66 return &Placement{Scope: scope, Directive: directive}, nil
67 }
68 if names.IsMachine(directive) {
69 return &Placement{Scope: MachineScope, Directive: directive}, nil
70 }
71 if isContainerType(directive) {
72 return &Placement{Scope: directive}, nil
73 }
74 return nil, ErrPlacementScopeMissing
75}
76
77// MustParsePlacement attempts to parse the specified string and create
78// a corresponding Placement structure, panicking if an error occurs.
79func MustParsePlacement(directive string) *Placement {
80 placement, err := ParsePlacement(directive)
81 if err != nil {
82 panic(err)
83 }
84 return placement
85}
086
=== added file 'instance/placement_test.go'
--- instance/placement_test.go 1970-01-01 00:00:00 +0000
+++ instance/placement_test.go 2014-04-23 06:25:18 +0000
@@ -0,0 +1,76 @@
1// Copyright 2014 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package instance_test
5
6import (
7 gc "launchpad.net/gocheck"
8
9 "launchpad.net/juju-core/instance"
10)
11
12type PlacementSuite struct{}
13
14var _ = gc.Suite(&PlacementSuite{})
15
16func (s *PlacementSuite) TestParsePlacement(c *gc.C) {
17 parsePlacementTests := []struct {
18 arg string
19 expectScope, expectDirective string
20 err string
21 }{{
22 arg: "",
23 }, {
24 arg: "0",
25 expectScope: instance.MachineScope,
26 expectDirective: "0",
27 }, {
28 arg: "0/lxc/0",
29 expectScope: instance.MachineScope,
30 expectDirective: "0/lxc/0",
31 }, {
32 arg: "#:x",
33 err: `invalid value "x" for "#" scope: expected machine-id`,
34 }, {
35 arg: "lxc:x",
36 err: `invalid value "x" for "lxc" scope: expected machine-id`,
37 }, {
38 arg: "kvm:x",
39 err: `invalid value "x" for "kvm" scope: expected machine-id`,
40 }, {
41 arg: "kvm:123",
42 expectScope: string(instance.KVM),
43 expectDirective: "123",
44 }, {
45 arg: "lxc",
46 expectScope: string(instance.LXC),
47 }, {
48 arg: "non-standard",
49 err: "placement scope missing",
50 }, {
51 arg: ":non-standard",
52 err: "placement scope missing",
53 }, {
54 arg: "non:standard",
55 expectScope: "non",
56 expectDirective: "standard",
57 }}
58
59 for i, t := range parsePlacementTests {
60 c.Logf("test %d: %s", i, t.arg)
61 p, err := instance.ParsePlacement(t.arg)
62 if t.err != "" {
63 c.Assert(err, gc.ErrorMatches, t.err)
64 } else {
65 c.Assert(err, gc.IsNil)
66 }
67 if t.expectScope == "" && t.expectDirective == "" {
68 c.Assert(p, gc.IsNil)
69 } else {
70 c.Assert(p, gc.DeepEquals, &instance.Placement{
71 Scope: t.expectScope,
72 Directive: t.expectDirective,
73 })
74 }
75 }
76}
077
=== modified file 'names/machine.go'
--- names/machine.go 2013-11-25 05:03:44 +0000
+++ names/machine.go 2014-04-23 06:25:18 +0000
@@ -21,6 +21,11 @@
21 return validMachine.MatchString(id)21 return validMachine.MatchString(id)
22}22}
2323
24// IsMachine returns whether id is a valid container machine id.
25func IsContainerMachine(id string) bool {
26 return validMachine.MatchString(id) && strings.Contains(id, "/")
27}
28
24// MachineTag returns the tag for the machine with the given id.29// MachineTag returns the tag for the machine with the given id.
25func MachineTag(id string) string {30func MachineTag(id string) string {
26 tag := makeTag(MachineTagKind, id)31 tag := makeTag(MachineTagKind, id)
2732
=== modified file 'names/machine_test.go'
--- names/machine_test.go 2013-08-06 09:20:36 +0000
+++ names/machine_test.go 2014-04-23 06:25:18 +0000
@@ -26,8 +26,9 @@
26}26}
2727
28var machineIdTests = []struct {28var machineIdTests = []struct {
29 pattern string29 pattern string
30 valid bool30 valid bool
31 container bool
31}{32}{
32 {pattern: "42", valid: true},33 {pattern: "42", valid: true},
33 {pattern: "042", valid: false},34 {pattern: "042", valid: false},
@@ -37,7 +38,7 @@
37 {pattern: "55/", valid: false},38 {pattern: "55/", valid: false},
38 {pattern: "1/foo", valid: false},39 {pattern: "1/foo", valid: false},
39 {pattern: "2/foo/", valid: false},40 {pattern: "2/foo/", valid: false},
40 {pattern: "3/lxc/42", valid: true},41 {pattern: "3/lxc/42", valid: true, container: true},
41 {pattern: "3/lxc-nodash/42", valid: false},42 {pattern: "3/lxc-nodash/42", valid: false},
42 {pattern: "0/lxc/00", valid: false},43 {pattern: "0/lxc/00", valid: false},
43 {pattern: "0/lxc/0/", valid: false},44 {pattern: "0/lxc/0/", valid: false},
@@ -45,7 +46,7 @@
45 {pattern: "3/lxc/042", valid: false},46 {pattern: "3/lxc/042", valid: false},
46 {pattern: "4/foo/bar", valid: false},47 {pattern: "4/foo/bar", valid: false},
47 {pattern: "5/lxc/42/foo", valid: false},48 {pattern: "5/lxc/42/foo", valid: false},
48 {pattern: "6/lxc/42/kvm/0", valid: true},49 {pattern: "6/lxc/42/kvm/0", valid: true, container: true},
49 {pattern: "06/lxc/42/kvm/0", valid: false},50 {pattern: "06/lxc/42/kvm/0", valid: false},
50 {pattern: "6/lxc/042/kvm/0", valid: false},51 {pattern: "6/lxc/042/kvm/0", valid: false},
51 {pattern: "6/lxc/42/kvm/00", valid: false},52 {pattern: "6/lxc/42/kvm/00", valid: false},
@@ -56,5 +57,6 @@
56 for i, test := range machineIdTests {57 for i, test := range machineIdTests {
57 c.Logf("test %d: %q", i, test.pattern)58 c.Logf("test %d: %q", i, test.pattern)
58 c.Assert(names.IsMachine(test.pattern), gc.Equals, test.valid)59 c.Assert(names.IsMachine(test.pattern), gc.Equals, test.valid)
60 c.Assert(names.IsContainerMachine(test.pattern), gc.Equals, test.container)
59 }61 }
60}62}
6163
=== modified file 'provider/azure/environ.go'
--- provider/azure/environ.go 2014-04-21 23:50:20 +0000
+++ provider/azure/environ.go 2014-04-23 06:25:18 +0000
@@ -434,7 +434,10 @@
434}434}
435435
436// PrecheckInstance is defined on the state.Prechecker interface.436// PrecheckInstance is defined on the state.Prechecker interface.
437func (env *azureEnviron) PrecheckInstance(series string, cons constraints.Value) error {437func (env *azureEnviron) PrecheckInstance(series string, cons constraints.Value, placement string) error {
438 if placement != "" {
439 return fmt.Errorf("unknown placement directive: %s", placement)
440 }
438 if !cons.HasInstanceType() {441 if !cons.HasInstanceType() {
439 return nil442 return nil
440 }443 }
441444
=== modified file 'provider/azure/instancetype_test.go'
--- provider/azure/instancetype_test.go 2014-04-17 06:44:49 +0000
+++ provider/azure/instancetype_test.go 2014-04-23 06:25:18 +0000
@@ -449,13 +449,15 @@
449func (s *instanceTypeSuite) TestPrecheckInstanceValidInstanceType(c *gc.C) {449func (s *instanceTypeSuite) TestPrecheckInstanceValidInstanceType(c *gc.C) {
450 env := s.setupEnvWithDummyMetadata(c)450 env := s.setupEnvWithDummyMetadata(c)
451 cons := constraints.MustParse("instance-type=Large")451 cons := constraints.MustParse("instance-type=Large")
452 err := env.PrecheckInstance("precise", cons)452 placement := ""
453 err := env.PrecheckInstance("precise", cons, placement)
453 c.Assert(err, gc.IsNil)454 c.Assert(err, gc.IsNil)
454}455}
455456
456func (s *instanceTypeSuite) TestPrecheckInstanceInvalidInstanceType(c *gc.C) {457func (s *instanceTypeSuite) TestPrecheckInstanceInvalidInstanceType(c *gc.C) {
457 env := s.setupEnvWithDummyMetadata(c)458 env := s.setupEnvWithDummyMetadata(c)
458 cons := constraints.MustParse("instance-type=Super")459 cons := constraints.MustParse("instance-type=Super")
459 err := env.PrecheckInstance("precise", cons)460 placement := ""
461 err := env.PrecheckInstance("precise", cons, placement)
460 c.Assert(err, gc.ErrorMatches, `invalid Azure instance "Super" specified`)462 c.Assert(err, gc.ErrorMatches, `invalid Azure instance "Super" specified`)
461}463}
462464
=== modified file 'provider/common/policies.go'
--- provider/common/policies.go 2014-04-17 03:10:18 +0000
+++ provider/common/policies.go 2014-04-23 06:25:18 +0000
@@ -3,11 +3,6 @@
33
4package common4package common
55
6import (
7 "launchpad.net/juju-core/constraints"
8 "launchpad.net/juju-core/state"
9)
10
11// SupportsUnitPlacementPolicy provides an6// SupportsUnitPlacementPolicy provides an
12// implementation of SupportsUnitPlacement7// implementation of SupportsUnitPlacement
13// that never returns an error, and is8// that never returns an error, and is
@@ -18,13 +13,3 @@
18func (*SupportsUnitPlacementPolicy) SupportsUnitPlacement() error {13func (*SupportsUnitPlacementPolicy) SupportsUnitPlacement() error {
19 return nil14 return nil
20}15}
21
22// NopPrecheckerPolicy provides an implementation of the
23// state.Prechecker interface that passes all checks.
24type NopPrecheckerPolicy struct{}
25
26var _ state.Prechecker = (*NopPrecheckerPolicy)(nil)
27
28func (*NopPrecheckerPolicy) PrecheckInstance(series string, cons constraints.Value) error {
29 return nil
30}
3116
=== modified file 'provider/dummy/environs.go'
--- provider/dummy/environs.go 2014-04-21 23:10:05 +0000
+++ provider/dummy/environs.go 2014-04-23 06:25:18 +0000
@@ -184,7 +184,6 @@
184// environ represents a client's connection to a given environment's184// environ represents a client's connection to a given environment's
185// state.185// state.
186type environ struct {186type environ struct {
187 common.NopPrecheckerPolicy
188 common.SupportsUnitPlacementPolicy187 common.SupportsUnitPlacementPolicy
189188
190 name string189 name string
@@ -543,6 +542,14 @@
543 return true542 return true
544}543}
545544
545// PrecheckInstance is specified in the state.Prechecker interface.
546func (*environ) PrecheckInstance(series string, cons constraints.Value, placement string) error {
547 if placement != "" && placement != "valid" {
548 return fmt.Errorf("%s placement is invalid", placement)
549 }
550 return nil
551}
552
546// GetImageSources returns a list of sources which are used to search for simplestreams image metadata.553// GetImageSources returns a list of sources which are used to search for simplestreams image metadata.
547func (e *environ) GetImageSources() ([]simplestreams.DataSource, error) {554func (e *environ) GetImageSources() ([]simplestreams.DataSource, error) {
548 return []simplestreams.DataSource{555 return []simplestreams.DataSource{
549556
=== modified file 'provider/ec2/ec2.go'
--- provider/ec2/ec2.go 2014-04-21 23:50:20 +0000
+++ provider/ec2/ec2.go 2014-04-23 06:25:18 +0000
@@ -385,7 +385,10 @@
385}385}
386386
387// PrecheckInstance is defined on the state.Prechecker interface.387// PrecheckInstance is defined on the state.Prechecker interface.
388func (e *environ) PrecheckInstance(series string, cons constraints.Value) error {388func (e *environ) PrecheckInstance(series string, cons constraints.Value, placement string) error {
389 if placement != "" {
390 return fmt.Errorf("unknown placement directive: %s", placement)
391 }
389 if !cons.HasInstanceType() {392 if !cons.HasInstanceType() {
390 return nil393 return nil
391 }394 }
392395
=== modified file 'provider/ec2/local_test.go'
--- provider/ec2/local_test.go 2014-04-18 13:51:46 +0000
+++ provider/ec2/local_test.go 2014-04-23 06:25:18 +0000
@@ -381,21 +381,24 @@
381func (t *localServerSuite) TestPrecheckInstanceValidInstanceType(c *gc.C) {381func (t *localServerSuite) TestPrecheckInstanceValidInstanceType(c *gc.C) {
382 env := t.Prepare(c)382 env := t.Prepare(c)
383 cons := constraints.MustParse("instance-type=m1.small root-disk=1G")383 cons := constraints.MustParse("instance-type=m1.small root-disk=1G")
384 err := env.PrecheckInstance("precise", cons)384 placement := ""
385 err := env.PrecheckInstance("precise", cons, placement)
385 c.Assert(err, gc.IsNil)386 c.Assert(err, gc.IsNil)
386}387}
387388
388func (t *localServerSuite) TestPrecheckInstanceInvalidInstanceType(c *gc.C) {389func (t *localServerSuite) TestPrecheckInstanceInvalidInstanceType(c *gc.C) {
389 env := t.Prepare(c)390 env := t.Prepare(c)
390 cons := constraints.MustParse("instance-type=m1.invalid")391 cons := constraints.MustParse("instance-type=m1.invalid")
391 err := env.PrecheckInstance("precise", cons)392 placement := ""
393 err := env.PrecheckInstance("precise", cons, placement)
392 c.Assert(err, gc.ErrorMatches, `invalid AWS instance type "m1.invalid" specified`)394 c.Assert(err, gc.ErrorMatches, `invalid AWS instance type "m1.invalid" specified`)
393}395}
394396
395func (t *localServerSuite) TestPrecheckInstanceUnsupportedArch(c *gc.C) {397func (t *localServerSuite) TestPrecheckInstanceUnsupportedArch(c *gc.C) {
396 env := t.Prepare(c)398 env := t.Prepare(c)
397 cons := constraints.MustParse("instance-type=cc1.4xlarge arch=i386")399 cons := constraints.MustParse("instance-type=cc1.4xlarge arch=i386")
398 err := env.PrecheckInstance("precise", cons)400 placement := ""
401 err := env.PrecheckInstance("precise", cons, placement)
399 c.Assert(err, gc.ErrorMatches, `invalid AWS instance type "cc1.4xlarge" and arch "i386" specified`)402 c.Assert(err, gc.ErrorMatches, `invalid AWS instance type "cc1.4xlarge" and arch "i386" specified`)
400}403}
401404
402405
=== modified file 'provider/joyent/environ.go'
--- provider/joyent/environ.go 2014-04-17 06:53:42 +0000
+++ provider/joyent/environ.go 2014-04-23 06:25:18 +0000
@@ -76,7 +76,10 @@
76}76}
7777
78// PrecheckInstance is defined on the state.Prechecker interface.78// PrecheckInstance is defined on the state.Prechecker interface.
79func (env *joyentEnviron) PrecheckInstance(series string, cons constraints.Value) error {79func (env *joyentEnviron) PrecheckInstance(series string, cons constraints.Value, placement string) error {
80 if placement != "" {
81 return fmt.Errorf("unknown placement directive: %s", placement)
82 }
80 if !cons.HasInstanceType() {83 if !cons.HasInstanceType() {
81 return nil84 return nil
82 }85 }
8386
=== modified file 'provider/local/environ.go'
--- provider/local/environ.go 2014-04-21 23:10:05 +0000
+++ provider/local/environ.go 2014-04-23 06:25:18 +0000
@@ -58,7 +58,6 @@
58var _ envtools.SupportsCustomSources = (*localEnviron)(nil)58var _ envtools.SupportsCustomSources = (*localEnviron)(nil)
5959
60type localEnviron struct {60type localEnviron struct {
61 common.NopPrecheckerPolicy
62 common.SupportsUnitPlacementPolicy61 common.SupportsUnitPlacementPolicy
6362
64 localMutex sync.Mutex63 localMutex sync.Mutex
@@ -88,6 +87,13 @@
88 return false87 return false
89}88}
9089
90func (*localEnviron) PrecheckInstance(series string, cons constraints.Value, placement string) error {
91 if placement != "" {
92 return fmt.Errorf("unknown placement directive: %s", placement)
93 }
94 return nil
95}
96
91// Name is specified in the Environ interface.97// Name is specified in the Environ interface.
92func (env *localEnviron) Name() string {98func (env *localEnviron) Name() string {
93 return env.name99 return env.name
94100
=== modified file 'provider/maas/environ.go'
--- provider/maas/environ.go 2014-04-21 23:10:05 +0000
+++ provider/maas/environ.go 2014-04-23 06:25:18 +0000
@@ -51,7 +51,6 @@
51}51}
5252
53type maasEnviron struct {53type maasEnviron struct {
54 common.NopPrecheckerPolicy
55 common.SupportsUnitPlacementPolicy54 common.SupportsUnitPlacementPolicy
5655
57 name string56 name string
@@ -171,6 +170,15 @@
171 return caps.Contains(capNetworksManagement)170 return caps.Contains(capNetworksManagement)
172}171}
173172
173func (env *maasEnviron) PrecheckInstance(series string, cons constraints.Value, placement string) error {
174 // TODO(axw) 2014-04-22 #1237709
175 // Handle maas-name placement directive.
176 if placement != "" {
177 return fmt.Errorf("unknown placement directive: %s", placement)
178 }
179 return nil
180}
181
174const capNetworksManagement = "networks-management"182const capNetworksManagement = "networks-management"
175183
176// getCapabilities asks the MAAS server for its capabilities, if184// getCapabilities asks the MAAS server for its capabilities, if
177185
=== modified file 'provider/manual/environ.go'
--- provider/manual/environ.go 2014-04-21 23:10:05 +0000
+++ provider/manual/environ.go 2014-04-23 06:25:18 +0000
@@ -61,7 +61,6 @@
61}61}
6262
63var _ envtools.SupportsCustomSources = (*manualEnviron)(nil)63var _ envtools.SupportsCustomSources = (*manualEnviron)(nil)
64var _ state.Prechecker = (*manualEnviron)(nil)
6564
66var errNoStartInstance = errors.New("manual provider cannot start instances")65var errNoStartInstance = errors.New("manual provider cannot start instances")
67var errNoStopInstance = errors.New("manual provider cannot stop instances")66var errNoStopInstance = errors.New("manual provider cannot stop instances")
@@ -262,7 +261,7 @@
262 return err261 return err
263}262}
264263
265func (*manualEnviron) PrecheckInstance(series string, cons constraints.Value) error {264func (*manualEnviron) PrecheckInstance(series string, _ constraints.Value, placement string) error {
266 return errors.New(`use "juju add-machine ssh:[user@]<host>" to provision machines`)265 return errors.New(`use "juju add-machine ssh:[user@]<host>" to provision machines`)
267}266}
268267
269268
=== modified file 'provider/openstack/local_test.go'
--- provider/openstack/local_test.go 2014-04-21 23:50:20 +0000
+++ provider/openstack/local_test.go 2014-04-23 06:25:18 +0000
@@ -716,14 +716,16 @@
716func (s *localServerSuite) TestPrecheckInstanceValidInstanceType(c *gc.C) {716func (s *localServerSuite) TestPrecheckInstanceValidInstanceType(c *gc.C) {
717 env := s.Open(c)717 env := s.Open(c)
718 cons := constraints.MustParse("instance-type=m1.small")718 cons := constraints.MustParse("instance-type=m1.small")
719 err := env.PrecheckInstance("precise", cons)719 placement := ""
720 err := env.PrecheckInstance("precise", cons, placement)
720 c.Assert(err, gc.IsNil)721 c.Assert(err, gc.IsNil)
721}722}
722723
723func (s *localServerSuite) TestPrecheckInstanceInvalidInstanceType(c *gc.C) {724func (s *localServerSuite) TestPrecheckInstanceInvalidInstanceType(c *gc.C) {
724 env := s.Open(c)725 env := s.Open(c)
725 cons := constraints.MustParse("instance-type=m1.large")726 cons := constraints.MustParse("instance-type=m1.large")
726 err := env.PrecheckInstance("precise", cons)727 placement := ""
728 err := env.PrecheckInstance("precise", cons, placement)
727 c.Assert(err, gc.ErrorMatches, `invalid Openstack flavour "m1.large" specified`)729 c.Assert(err, gc.ErrorMatches, `invalid Openstack flavour "m1.large" specified`)
728}730}
729731
730732
=== modified file 'provider/openstack/provider.go'
--- provider/openstack/provider.go 2014-04-21 23:50:20 +0000
+++ provider/openstack/provider.go 2014-04-23 06:25:18 +0000
@@ -538,7 +538,10 @@
538}538}
539539
540// PrecheckInstance is defined on the state.Prechecker interface.540// PrecheckInstance is defined on the state.Prechecker interface.
541func (e *environ) PrecheckInstance(series string, cons constraints.Value) error {541func (e *environ) PrecheckInstance(series string, cons constraints.Value, placement string) error {
542 if placement != "" {
543 return fmt.Errorf("unknown placement directive: %s", placement)
544 }
542 if !cons.HasInstanceType() {545 if !cons.HasInstanceType() {
543 return nil546 return nil
544 }547 }
545548
=== modified file 'state/addmachine.go'
--- state/addmachine.go 2014-04-21 23:10:05 +0000
+++ state/addmachine.go 2014-04-23 06:25:18 +0000
@@ -69,6 +69,10 @@
69 // as unclean for unit-assignment purposes.69 // as unclean for unit-assignment purposes.
70 Dirty bool70 Dirty bool
7171
72 // Placement holds the placement directive that will be associated
73 // with the machine.
74 Placement string
75
72 // principals holds the principal units that will76 // principals holds the principal units that will
73 // associated with the machine.77 // associated with the machine.
74 principals []string78 principals []string
@@ -234,7 +238,7 @@
234 return nil, nil, err238 return nil, nil, err
235 }239 }
236 if template.InstanceId == "" {240 if template.InstanceId == "" {
237 if err := st.precheckInstance(template.Series, template.Constraints); err != nil {241 if err := st.precheckInstance(template.Series, template.Constraints, template.Placement); err != nil {
238 return nil, nil, err242 return nil, nil, err
239 }243 }
240 }244 }
@@ -359,13 +363,13 @@
359 return nil, nil, fmt.Errorf("no container type specified")363 return nil, nil, fmt.Errorf("no container type specified")
360 }364 }
361 if parentTemplate.InstanceId == "" {365 if parentTemplate.InstanceId == "" {
362 if err := st.precheckInstance(parentTemplate.Series, parentTemplate.Constraints); err != nil {
363 return nil, nil, err
364 }
365 // Adding a machine within a machine implies add-machine or placement.366 // Adding a machine within a machine implies add-machine or placement.
366 if err := st.supportsUnitPlacement(); err != nil {367 if err := st.supportsUnitPlacement(); err != nil {
367 return nil, nil, err368 return nil, nil, err
368 }369 }
370 if err := st.precheckInstance(parentTemplate.Series, parentTemplate.Constraints, parentTemplate.Placement); err != nil {
371 return nil, nil, err
372 }
369 }373 }
370374
371 parentDoc := machineDocForTemplate(parentTemplate, strconv.Itoa(seq))375 parentDoc := machineDocForTemplate(parentTemplate, strconv.Itoa(seq))
@@ -403,6 +407,7 @@
403 Nonce: template.Nonce,407 Nonce: template.Nonce,
404 Addresses: instanceAddressesToAddresses(template.Addresses),408 Addresses: instanceAddressesToAddresses(template.Addresses),
405 NoVote: template.NoVote,409 NoVote: template.NoVote,
410 Placement: template.Placement,
406 }411 }
407}412}
408413
409414
=== modified file 'state/api/client.go'
--- state/api/client.go 2014-04-17 19:01:44 +0000
+++ state/api/client.go 2014-04-23 06:25:18 +0000
@@ -227,13 +227,26 @@
227 return results.CharmRelations, err227 return results.CharmRelations, err
228}228}
229229
230// AddMachines1dot18 adds new machines with the supplied parameters.
231//
232// TODO(axw) 2014-04-11 #XXX
233// This exists for backwards compatibility; remove in 1.21 (client only).
234func (c *Client) AddMachines1dot18(machineParams []params.AddMachineParams) ([]params.AddMachinesResult, error) {
235 args := params.AddMachines{
236 MachineParams: machineParams,
237 }
238 results := new(params.AddMachinesResults)
239 err := c.call("AddMachines", args, results)
240 return results.Machines, err
241}
242
230// AddMachines adds new machines with the supplied parameters.243// AddMachines adds new machines with the supplied parameters.
231func (c *Client) AddMachines(machineParams []params.AddMachineParams) ([]params.AddMachinesResult, error) {244func (c *Client) AddMachines(machineParams []params.AddMachineParams) ([]params.AddMachinesResult, error) {
232 args := params.AddMachines{245 args := params.AddMachines{
233 MachineParams: machineParams,246 MachineParams: machineParams,
234 }247 }
235 results := new(params.AddMachinesResults)248 results := new(params.AddMachinesResults)
236 err := c.call("AddMachines", args, results)249 err := c.call("AddMachinesV2", args, results)
237 return results.Machines, err250 return results.Machines, err
238}251}
239252
240253
=== modified file 'state/api/params/internal.go'
--- state/api/params/internal.go 2014-04-18 16:37:28 +0000
+++ state/api/params/internal.go 2014-04-23 06:25:18 +0000
@@ -599,3 +599,23 @@
599type AgentVersionResult struct {599type AgentVersionResult struct {
600 Version version.Number600 Version version.Number
601}601}
602
603// ProvisioningInfo holds machine provisioning info.
604type ProvisioningInfo struct {
605 Constraints constraints.Value
606 Series string
607 Placement string
608 IncludeNetworks []string
609 ExcludeNetworks []string
610}
611
612// ProvisioningInfoResult holds machine provisioning info or an error.
613type ProvisioningInfoResult struct {
614 Error *Error
615 Result *ProvisioningInfo
616}
617
618// ProvisioningInfoResults holds multiple machine provisioning info results.
619type ProvisioningInfoResults struct {
620 Results []ProvisioningInfoResult
621}
602622
=== modified file 'state/api/params/params.go'
--- state/api/params/params.go 2014-04-12 05:53:58 +0000
+++ state/api/params/params.go 2014-04-23 06:25:18 +0000
@@ -92,6 +92,10 @@
92 Constraints constraints.Value92 Constraints constraints.Value
93 Jobs []MachineJob93 Jobs []MachineJob
9494
95 // If Placement is non-nil, it contains a placement directive
96 // that will be used to decide how to instantiate the machine.
97 Placement *instance.Placement
98
95 // If ParentId is non-empty, it specifies the id of the99 // If ParentId is non-empty, it specifies the id of the
96 // parent machine within which the new machine will100 // parent machine within which the new machine will
97 // be created. In that case, ContainerType must also be101 // be created. In that case, ContainerType must also be
@@ -117,7 +121,8 @@
117 Addrs []instance.Address121 Addrs []instance.Address
118}122}
119123
120// AddMachines holds the parameters for making the AddMachines call.124// AddMachines holds the parameters for making the
125// AddMachinesWithPlacement call.
121type AddMachines struct {126type AddMachines struct {
122 MachineParams []AddMachineParams127 MachineParams []AddMachineParams
123}128}
124129
=== modified file 'state/api/provisioner/machine.go'
--- state/api/provisioner/machine.go 2014-04-13 11:06:42 +0000
+++ state/api/provisioner/machine.go 2014-04-23 06:25:18 +0000
@@ -7,7 +7,6 @@
7 "errors"7 "errors"
8 "fmt"8 "fmt"
99
10 "launchpad.net/juju-core/constraints"
11 "launchpad.net/juju-core/instance"10 "launchpad.net/juju-core/instance"
12 "launchpad.net/juju-core/names"11 "launchpad.net/juju-core/names"
13 "launchpad.net/juju-core/state/api/params"12 "launchpad.net/juju-core/state/api/params"
@@ -55,23 +54,22 @@
55 return nil54 return nil
56}55}
5756
58// RequestedNetworks returns a pair of lists of networks to57// ProvisioningInfo returns the information required to provisiong a machine.
59// enable/disable on the machine.58func (m *Machine) ProvisioningInfo() (*params.ProvisioningInfo, error) {
60func (m *Machine) RequestedNetworks() (includeNetworks, excludeNetworks []string, err error) {59 var results params.ProvisioningInfoResults
61 var results params.RequestedNetworksResults
62 args := params.Entities{Entities: []params.Entity{{m.tag}}}60 args := params.Entities{Entities: []params.Entity{{m.tag}}}
63 err = m.st.call("RequestedNetworks", args, &results)61 err := m.st.call("ProvisioningInfo", args, &results)
64 if err != nil {62 if err != nil {
65 return nil, nil, err63 return nil, err
66 }64 }
67 if len(results.Results) != 1 {65 if len(results.Results) != 1 {
68 return nil, nil, fmt.Errorf("expected 1 result, got %d", len(results.Results))66 return nil, fmt.Errorf("expected 1 result, got %d", len(results.Results))
69 }67 }
70 result := results.Results[0]68 result := results.Results[0]
71 if result.Error != nil {69 if result.Error != nil {
72 return nil, nil, result.Error70 return nil, result.Error
73 }71 }
74 return result.IncludeNetworks, result.ExcludeNetworks, nil72 return result.Result, nil
75}73}
7674
77// SetStatus sets the status of the machine.75// SetStatus sets the status of the machine.
@@ -109,28 +107,6 @@
109 return result.Status, result.Info, nil107 return result.Status, result.Info, nil
110}108}
111109
112// Constraints returns the exact constraints that should apply when provisioning
113// an instance for the machine.
114func (m *Machine) Constraints() (constraints.Value, error) {
115 nothing := constraints.Value{}
116 var results params.ConstraintsResults
117 args := params.Entities{
118 Entities: []params.Entity{{Tag: m.tag}},
119 }
120 err := m.st.call("Constraints", args, &results)
121 if err != nil {
122 return nothing, err
123 }
124 if len(results.Results) != 1 {
125 return nothing, fmt.Errorf("expected 1 result, got %d", len(results.Results))
126 }
127 result := results.Results[0]
128 if result.Error != nil {
129 return nothing, result.Error
130 }
131 return result.Constraints, nil
132}
133
134// EnsureDead sets the machine lifecycle to Dead if it is Alive or110// EnsureDead sets the machine lifecycle to Dead if it is Alive or
135// Dying. It does nothing otherwise.111// Dying. It does nothing otherwise.
136func (m *Machine) EnsureDead() error {112func (m *Machine) EnsureDead() error {
137113
=== modified file 'state/api/provisioner/provisioner_test.go'
--- state/api/provisioner/provisioner_test.go 2014-04-18 15:31:56 +0000
+++ state/api/provisioner/provisioner_test.go 2014-04-23 06:25:18 +0000
@@ -388,55 +388,41 @@
388 c.Assert(err, jc.Satisfies, params.IsCodeNotFound)388 c.Assert(err, jc.Satisfies, params.IsCodeNotFound)
389}389}
390390
391func (s *provisionerSuite) TestConstraints(c *gc.C) {391func (s *provisionerSuite) TestProvisioningInfo(c *gc.C) {
392 // Create a fresh machine with some constraints.
393 template := state.MachineTemplate{
394 Series: "quantal",
395 Jobs: []state.MachineJob{state.JobHostUnits},
396 Constraints: constraints.MustParse("cpu-cores=12", "mem=8G"),
397 }
398 consMachine, err := s.State.AddOneMachine(template)
399 c.Assert(err, gc.IsNil)
400
401 apiMachine, err := s.provisioner.Machine(consMachine.Tag())
402 c.Assert(err, gc.IsNil)
403 cons, err := apiMachine.Constraints()
404 c.Assert(err, gc.IsNil)
405 c.Assert(cons, gc.DeepEquals, template.Constraints)
406
407 // Now try machine 0.
408 apiMachine, err = s.provisioner.Machine(s.machine.Tag())
409 c.Assert(err, gc.IsNil)
410 cons, err = apiMachine.Constraints()
411 c.Assert(err, gc.IsNil)
412 c.Assert(cons, gc.DeepEquals, constraints.Value{})
413}
414
415func (s *provisionerSuite) TestRequestedNetworks(c *gc.C) {
416 // Create a fresh machine with some requested networks.
417 template := state.MachineTemplate{392 template := state.MachineTemplate{
418 Series: "quantal",393 Series: "quantal",
419 Jobs: []state.MachineJob{state.JobHostUnits},394 Jobs: []state.MachineJob{state.JobHostUnits},
395 Placement: "valid",
396 Constraints: constraints.MustParse("cpu-cores=12", "mem=8G"),
420 IncludeNetworks: []string{"net1", "net2"},397 IncludeNetworks: []string{"net1", "net2"},
421 ExcludeNetworks: []string{"net3", "net4"},398 ExcludeNetworks: []string{"net3", "net4"},
422 }399 }
423 netsMachine, err := s.State.AddOneMachine(template)400 machine, err := s.State.AddOneMachine(template)
424 c.Assert(err, gc.IsNil)401 c.Assert(err, gc.IsNil)
425402 apiMachine, err := s.provisioner.Machine(machine.Tag())
426 apiMachine, err := s.provisioner.Machine(netsMachine.Tag())403 c.Assert(err, gc.IsNil)
427 c.Assert(err, gc.IsNil)404 provisioningInfo, err := apiMachine.ProvisioningInfo()
428 includeNetworks, excludeNetworks, err := apiMachine.RequestedNetworks()405 c.Assert(err, gc.IsNil)
429 c.Assert(err, gc.IsNil)406 c.Assert(provisioningInfo.Series, gc.Equals, template.Series)
430 c.Assert(includeNetworks, gc.DeepEquals, template.IncludeNetworks)407 c.Assert(provisioningInfo.Placement, gc.Equals, template.Placement)
431 c.Assert(excludeNetworks, gc.DeepEquals, template.ExcludeNetworks)408 c.Assert(provisioningInfo.Constraints, gc.DeepEquals, template.Constraints)
432409 c.Assert(provisioningInfo.IncludeNetworks, gc.DeepEquals, template.IncludeNetworks)
433 // Now try machine 0.410 c.Assert(provisioningInfo.ExcludeNetworks, gc.DeepEquals, template.ExcludeNetworks)
434 apiMachine, err = s.provisioner.Machine(s.machine.Tag())411}
435 c.Assert(err, gc.IsNil)412
436 includeNetworks, excludeNetworks, err = apiMachine.RequestedNetworks()413func (s *provisionerSuite) TestProvisioningInfoMachineNotFound(c *gc.C) {
437 c.Assert(err, gc.IsNil)414 stateMachine, err := s.State.AddMachine("quantal", state.JobHostUnits)
438 c.Assert(includeNetworks, gc.HasLen, 0)415 c.Assert(err, gc.IsNil)
439 c.Assert(excludeNetworks, gc.HasLen, 0)416 apiMachine, err := s.provisioner.Machine(stateMachine.Tag())
417 c.Assert(err, gc.IsNil)
418 err = apiMachine.EnsureDead()
419 c.Assert(err, gc.IsNil)
420 err = apiMachine.Remove()
421 c.Assert(err, gc.IsNil)
422 _, err = apiMachine.ProvisioningInfo()
423 c.Assert(err, gc.ErrorMatches, "machine 1 not found")
424 c.Assert(err, jc.Satisfies, params.IsCodeNotFound)
425 // auth tests in apiserver
440}426}
441427
442func (s *provisionerSuite) TestWatchContainers(c *gc.C) {428func (s *provisionerSuite) TestWatchContainers(c *gc.C) {
443429
=== modified file 'state/apiserver/client/client.go'
--- state/apiserver/client/client.go 2014-04-17 12:53:23 +0000
+++ state/apiserver/client/client.go 2014-04-23 06:25:18 +0000
@@ -587,6 +587,11 @@
587587
588// AddMachines adds new machines with the supplied parameters.588// AddMachines adds new machines with the supplied parameters.
589func (c *Client) AddMachines(args params.AddMachines) (params.AddMachinesResults, error) {589func (c *Client) AddMachines(args params.AddMachines) (params.AddMachinesResults, error) {
590 return c.AddMachinesV2(args)
591}
592
593// AddMachinesV2 adds new machines with the supplied parameters.
594func (c *Client) AddMachinesV2(args params.AddMachines) (params.AddMachinesResults, error) {
590 results := params.AddMachinesResults{595 results := params.AddMachinesResults{
591 Machines: make([]params.AddMachinesResult, len(args.MachineParams)),596 Machines: make([]params.AddMachinesResult, len(args.MachineParams)),
592 }597 }
@@ -606,23 +611,50 @@
606}611}
607612
608func (c *Client) addOneMachine(p params.AddMachineParams) (*state.Machine, error) {613func (c *Client) addOneMachine(p params.AddMachineParams) (*state.Machine, error) {
609 if p.Series == "" {614 if p.ParentId != "" && p.ContainerType == "" {
610 conf, err := c.api.state.EnvironConfig()615 return nil, fmt.Errorf("parent machine specified without container type")
611 if err != nil {616 }
612 return nil, err617 if p.ContainerType != "" && p.Placement != nil {
618 return nil, fmt.Errorf("container type and placement are mutually exclusive")
619 }
620 if p.Placement != nil {
621 // Extract container type and parent from container placement directives.
622 containerType, err := instance.ParseContainerType(p.Placement.Scope)
623 if err == nil {
624 p.ContainerType = containerType
625 p.ParentId = p.Placement.Directive
626 p.Placement = nil
613 }627 }
614 p.Series = config.PreferredSeries(conf)
615 }628 }
616 if p.ContainerType != "" {629
630 if p.ContainerType != "" || p.Placement != nil {
617 // Guard against dubious client by making sure that631 // Guard against dubious client by making sure that
618 // the following attributes can only be set when we're632 // the following attributes can only be set when we're
619 // not making a new container.633 // not using placement.
620 p.InstanceId = ""634 p.InstanceId = ""
621 p.Nonce = ""635 p.Nonce = ""
622 p.HardwareCharacteristics = instance.HardwareCharacteristics{}636 p.HardwareCharacteristics = instance.HardwareCharacteristics{}
623 p.Addrs = nil637 p.Addrs = nil
624 } else if p.ParentId != "" {638 }
625 return nil, fmt.Errorf("parent machine specified without container type")639
640 if p.Series == "" {
641 conf, err := c.api.state.EnvironConfig()
642 if err != nil {
643 return nil, err
644 }
645 p.Series = config.PreferredSeries(conf)
646 }
647
648 var placementDirective string
649 if p.Placement != nil {
650 env, err := c.api.state.Environment()
651 if err != nil {
652 return nil, err
653 }
654 if p.Placement.Scope != env.Name() {
655 return nil, fmt.Errorf("invalid environment name %q", p.Placement.Scope)
656 }
657 placementDirective = p.Placement.Directive
626 }658 }
627659
628 jobs, err := stateJobs(p.Jobs)660 jobs, err := stateJobs(p.Jobs)
@@ -637,6 +669,7 @@
637 Nonce: p.Nonce,669 Nonce: p.Nonce,
638 HardwareCharacteristics: p.HardwareCharacteristics,670 HardwareCharacteristics: p.HardwareCharacteristics,
639 Addresses: p.Addrs,671 Addresses: p.Addrs,
672 Placement: placementDirective,
640 }673 }
641 if p.ContainerType == "" {674 if p.ContainerType == "" {
642 return c.api.state.AddOneMachine(template)675 return c.api.state.AddOneMachine(template)
643676
=== modified file 'state/apiserver/client/client_test.go'
--- state/apiserver/client/client_test.go 2014-04-17 12:53:23 +0000
+++ state/apiserver/client/client_test.go 2014-04-23 06:25:18 +0000
@@ -1665,8 +1665,8 @@
16651665
1666 machines, err := s.APIState.Client().AddMachines([]params.AddMachineParams{{1666 machines, err := s.APIState.Client().AddMachines([]params.AddMachineParams{{
1667 Jobs: []params.MachineJob{params.JobHostUnits},1667 Jobs: []params.MachineJob{params.JobHostUnits},
1668 ContainerType: instance.LXC,
1668 ParentId: "0",1669 ParentId: "0",
1669 ContainerType: instance.LXC,
1670 Series: "quantal",1670 Series: "quantal",
1671 }})1671 }})
1672 c.Assert(err, gc.IsNil)1672 c.Assert(err, gc.IsNil)
@@ -1692,13 +1692,65 @@
1692 }1692 }
1693}1693}
16941694
1695func (s *clientSuite) TestClientAddMachinesWithPlacement(c *gc.C) {
1696 apiParams := make([]params.AddMachineParams, 4)
1697 for i := range apiParams {
1698 apiParams[i] = params.AddMachineParams{
1699 Jobs: []params.MachineJob{params.JobHostUnits},
1700 }
1701 }
1702 apiParams[0].Placement = instance.MustParsePlacement("lxc")
1703 apiParams[1].Placement = instance.MustParsePlacement("lxc:0")
1704 apiParams[1].ContainerType = instance.LXC
1705 apiParams[2].Placement = instance.MustParsePlacement("dummyenv:invalid")
1706 apiParams[3].Placement = instance.MustParsePlacement("dummyenv:valid")
1707 machines, err := s.APIState.Client().AddMachines(apiParams)
1708 c.Assert(err, gc.IsNil)
1709 c.Assert(len(machines), gc.Equals, 4)
1710 c.Assert(machines[0].Machine, gc.Equals, "0/lxc/0")
1711 c.Assert(machines[1].Error, gc.ErrorMatches, "container type and placement are mutually exclusive")
1712 c.Assert(machines[2].Error, gc.ErrorMatches, "cannot add a new machine: invalid placement is invalid")
1713 c.Assert(machines[3].Machine, gc.Equals, "1")
1714
1715 m, err := s.BackingState.Machine(machines[3].Machine)
1716 c.Assert(err, gc.IsNil)
1717 c.Assert(m.Placement(), gc.DeepEquals, apiParams[3].Placement.Directive)
1718}
1719
1720func (s *clientSuite) TestClientAddMachines1dot18(c *gc.C) {
1721 apiParams := make([]params.AddMachineParams, 2)
1722 for i := range apiParams {
1723 apiParams[i] = params.AddMachineParams{
1724 Jobs: []params.MachineJob{params.JobHostUnits},
1725 }
1726 }
1727 apiParams[1].ContainerType = instance.LXC
1728 apiParams[1].ParentId = "0"
1729 machines, err := s.APIState.Client().AddMachines1dot18(apiParams)
1730 c.Assert(err, gc.IsNil)
1731 c.Assert(len(machines), gc.Equals, 2)
1732 c.Assert(machines[0].Machine, gc.Equals, "0")
1733 c.Assert(machines[1].Machine, gc.Equals, "0/lxc/0")
1734}
1735
1736func (s *clientSuite) TestClientAddMachines1dot18SomeErrors(c *gc.C) {
1737 apiParams := []params.AddMachineParams{{
1738 Jobs: []params.MachineJob{params.JobHostUnits},
1739 ParentId: "123",
1740 }}
1741 machines, err := s.APIState.Client().AddMachines1dot18(apiParams)
1742 c.Assert(err, gc.IsNil)
1743 c.Assert(len(machines), gc.Equals, 1)
1744 c.Check(machines[0].Error, gc.ErrorMatches, "parent machine specified without container type")
1745}
1746
1695func (s *clientSuite) TestClientAddMachinesSomeErrors(c *gc.C) {1747func (s *clientSuite) TestClientAddMachinesSomeErrors(c *gc.C) {
1696 // Here we check that adding a number of containers correctly handles the1748 // Here we check that adding a number of containers correctly handles the
1697 // case that some adds succeed and others fail and report the errors1749 // case that some adds succeed and others fail and report the errors
1698 // accordingly.1750 // accordingly.
1699 // We will set up params to the AddMachines API to attempt to create 4 machines.1751 // We will set up params to the AddMachines API to attempt to create 3 machines.
1700 // Machines 0 and 1 will be added successfully.1752 // Machines 0 and 1 will be added successfully.
1701 // Mchines 2 and 3 will fail due to different reasons.1753 // Remaining machines will fail due to different reasons.
17021754
1703 // Create a machine to host the requested containers.1755 // Create a machine to host the requested containers.
1704 host, err := s.State.AddMachine("quantal", state.JobHostUnits)1756 host, err := s.State.AddMachine("quantal", state.JobHostUnits)
@@ -1707,32 +1759,26 @@
1707 err = host.SetSupportedContainers([]instance.ContainerType{instance.LXC})1759 err = host.SetSupportedContainers([]instance.ContainerType{instance.LXC})
1708 c.Assert(err, gc.IsNil)1760 c.Assert(err, gc.IsNil)
17091761
1710 // Set up params for adding 4 containers.1762 // Set up params for adding 3 containers.
1711 apiParams := make([]params.AddMachineParams, 4)1763 apiParams := make([]params.AddMachineParams, 3)
1712 for i := 0; i < 4; i++ {1764 for i := range apiParams {
1713 apiParams[i] = params.AddMachineParams{1765 apiParams[i] = params.AddMachineParams{
1714 Jobs: []params.MachineJob{params.JobHostUnits},1766 Jobs: []params.MachineJob{params.JobHostUnits},
1715 }1767 }
1716 }1768 }
1717 // Make it so that machines 2 and 3 will fail to be added.
1718 // This will cause a machine add to fail because of an invalid parent.
1719 apiParams[2].ParentId = "123"
1720 // This will cause a machine add to fail due to an unsupported container.1769 // This will cause a machine add to fail due to an unsupported container.
1721 apiParams[3].ParentId = host.Id()1770 apiParams[2].ContainerType = instance.KVM
1722 apiParams[3].ContainerType = instance.KVM1771 apiParams[2].ParentId = host.Id()
1723 machines, err := s.APIState.Client().AddMachines(apiParams)1772 machines, err := s.APIState.Client().AddMachines(apiParams)
1724 c.Assert(err, gc.IsNil)1773 c.Assert(err, gc.IsNil)
1725 c.Assert(len(machines), gc.Equals, 4)1774 c.Assert(len(machines), gc.Equals, 3)
17261775
1727 // Check the results - machines 2 and 3 will have errors.1776 // Check the results - machines 2 and 3 will have errors.
1728 c.Check(machines[0].Machine, gc.Equals, "1")1777 c.Check(machines[0].Machine, gc.Equals, "1")
1729 c.Check(machines[0].Error, gc.IsNil)1778 c.Check(machines[0].Error, gc.IsNil)
1730 c.Check(machines[1].Machine, gc.Equals, "2")1779 c.Check(machines[1].Machine, gc.Equals, "2")
1731 c.Check(machines[1].Error, gc.IsNil)1780 c.Check(machines[1].Error, gc.IsNil)
1732 c.Assert(machines[2].Error, gc.NotNil)1781 c.Check(machines[2].Error, gc.ErrorMatches, "cannot add a new machine: machine 0 cannot host kvm containers")
1733 c.Check(machines[2].Error, gc.ErrorMatches, "parent machine specified without container type")
1734 c.Assert(machines[2].Error, gc.NotNil)
1735 c.Check(machines[3].Error, gc.ErrorMatches, "cannot add a new machine: machine 0 cannot host kvm containers")
1736}1782}
17371783
1738func (s *clientSuite) TestClientAddMachinesWithInstanceIdSomeErrors(c *gc.C) {1784func (s *clientSuite) TestClientAddMachinesWithInstanceIdSomeErrors(c *gc.C) {
17391785
=== modified file 'state/apiserver/provisioner/provisioner.go'
--- state/apiserver/provisioner/provisioner.go 2014-04-18 15:31:56 +0000
+++ state/apiserver/provisioner/provisioner.go 2014-04-23 06:25:18 +0000
@@ -285,6 +285,49 @@
285 return result, nil285 return result, nil
286}286}
287287
288// ProvisioningInfo returns the provisioning information for each given machine entity.
289func (p *ProvisionerAPI) ProvisioningInfo(args params.Entities) (params.ProvisioningInfoResults, error) {
290 result := params.ProvisioningInfoResults{
291 Results: make([]params.ProvisioningInfoResult, len(args.Entities)),
292 }
293 canAccess, err := p.getAuthFunc()
294 if err != nil {
295 return result, err
296 }
297 for i, entity := range args.Entities {
298 machine, err := p.getMachine(canAccess, entity.Tag)
299 if err == nil {
300 result.Results[i].Result, err = getProvisioningInfo(machine)
301 }
302 result.Results[i].Error = common.ServerError(err)
303 }
304 return result, nil
305}
306
307func getProvisioningInfo(m *state.Machine) (*params.ProvisioningInfo, error) {
308 cons, err := m.Constraints()
309 if err != nil {
310 return nil, err
311 }
312 // TODO(dimitern) For now, since network names and
313 // provider ids are the same, we return what we got
314 // from state. In the future, when networks can be
315 // added before provisioning, we should convert both
316 // slices from juju network names to provider-specific
317 // ids before returning them.
318 includeNetworks, excludeNetworks, err := m.RequestedNetworks()
319 if err != nil {
320 return nil, err
321 }
322 return &params.ProvisioningInfo{
323 Constraints: cons,
324 Series: m.Series(),
325 Placement: m.Placement(),
326 IncludeNetworks: includeNetworks,
327 ExcludeNetworks: excludeNetworks,
328 }, nil
329}
330
288// DistributionGroup returns, for each given machine entity,331// DistributionGroup returns, for each given machine entity,
289// a slice of instance.Ids that belong to the same distribution332// a slice of instance.Ids that belong to the same distribution
290// group as that machine. This information may be used to333// group as that machine. This information may be used to
291334
=== modified file 'state/apiserver/provisioner/provisioner_test.go'
--- state/apiserver/provisioner/provisioner_test.go 2014-04-18 15:31:56 +0000
+++ state/apiserver/provisioner/provisioner_test.go 2014-04-23 06:25:18 +0000
@@ -726,6 +726,87 @@
726 })726 })
727}727}
728728
729func (s *withoutStateServerSuite) TestProvisioningInfo(c *gc.C) {
730 template := state.MachineTemplate{
731 Series: "quantal",
732 Jobs: []state.MachineJob{state.JobHostUnits},
733 Constraints: constraints.MustParse("cpu-cores=123", "mem=8G"),
734 Placement: "valid",
735 IncludeNetworks: []string{"net1", "net2"},
736 ExcludeNetworks: []string{"net3", "net4"},
737 }
738 placementMachine, err := s.State.AddOneMachine(template)
739 c.Assert(err, gc.IsNil)
740
741 args := params.Entities{Entities: []params.Entity{
742 {Tag: s.machines[0].Tag()},
743 {Tag: placementMachine.Tag()},
744 {Tag: "machine-42"},
745 {Tag: "unit-foo-0"},
746 {Tag: "service-bar"},
747 }}
748 result, err := s.provisioner.ProvisioningInfo(args)
749 c.Assert(err, gc.IsNil)
750 c.Assert(result, gc.DeepEquals, params.ProvisioningInfoResults{
751 Results: []params.ProvisioningInfoResult{
752 {
753 Result: &params.ProvisioningInfo{
754 Series: "quantal",
755 IncludeNetworks: []string{},
756 ExcludeNetworks: []string{},
757 },
758 },
759 {
760 Result: &params.ProvisioningInfo{
761 Series: "quantal",
762 Constraints: template.Constraints,
763 Placement: template.Placement,
764 IncludeNetworks: template.IncludeNetworks,
765 ExcludeNetworks: template.ExcludeNetworks,
766 },
767 },
768 {Error: apiservertesting.NotFoundError("machine 42")},
769 {Error: apiservertesting.ErrUnauthorized},
770 {Error: apiservertesting.ErrUnauthorized},
771 },
772 })
773}
774
775func (s *withoutStateServerSuite) TestProvisioningInfoPermissions(c *gc.C) {
776 // Login as a machine agent for machine 0.
777 anAuthorizer := s.authorizer
778 anAuthorizer.MachineAgent = true
779 anAuthorizer.EnvironManager = false
780 anAuthorizer.Tag = s.machines[0].Tag()
781 aProvisioner, err := provisioner.NewProvisionerAPI(s.State, s.resources, anAuthorizer)
782 c.Assert(err, gc.IsNil)
783 c.Assert(aProvisioner, gc.NotNil)
784
785 args := params.Entities{Entities: []params.Entity{
786 {Tag: s.machines[0].Tag()},
787 {Tag: s.machines[0].Tag() + "-lxc-0"},
788 {Tag: "machine-42"},
789 {Tag: s.machines[1].Tag()},
790 {Tag: "service-bar"},
791 }}
792
793 // Only machine 0 and containers therein can be accessed.
794 results, err := aProvisioner.ProvisioningInfo(args)
795 c.Assert(results, gc.DeepEquals, params.ProvisioningInfoResults{
796 Results: []params.ProvisioningInfoResult{
797 {Result: &params.ProvisioningInfo{
798 Series: "quantal",
799 IncludeNetworks: []string{},
800 ExcludeNetworks: []string{},
801 }},
802 {Error: apiservertesting.NotFoundError("machine 0/lxc/0")},
803 {Error: apiservertesting.ErrUnauthorized},
804 {Error: apiservertesting.ErrUnauthorized},
805 {Error: apiservertesting.ErrUnauthorized},
806 },
807 })
808}
809
729func (s *withoutStateServerSuite) TestConstraints(c *gc.C) {810func (s *withoutStateServerSuite) TestConstraints(c *gc.C) {
730 // Add a machine with some constraints.811 // Add a machine with some constraints.
731 template := state.MachineTemplate{812 template := state.MachineTemplate{
732813
=== modified file 'state/machine.go'
--- state/machine.go 2014-04-21 23:10:05 +0000
+++ state/machine.go 2014-04-23 06:25:18 +0000
@@ -114,6 +114,9 @@
114 // machine is capable of hosting.114 // machine is capable of hosting.
115 SupportedContainersKnown bool115 SupportedContainersKnown bool
116 SupportedContainers []instance.ContainerType `bson:",omitempty"`116 SupportedContainers []instance.ContainerType `bson:",omitempty"`
117 // Placement is the placement directive that should be used when provisioning
118 // an instance for the machine.
119 Placement string `bson:",omitempty"`
117 // Deprecated. InstanceId, now lives on instanceData.120 // Deprecated. InstanceId, now lives on instanceData.
118 // This attribute is retained so that data from existing machines can be read.121 // This attribute is retained so that data from existing machines can be read.
119 // SCHEMACHANGE122 // SCHEMACHANGE
@@ -1090,6 +1093,12 @@
1090 return m.doc.Id1093 return m.doc.Id
1091}1094}
10921095
1096// Placement returns the machine's Placement structure that should be used when
1097// provisioning an instance for the machine.
1098func (m *Machine) Placement() string {
1099 return m.doc.Placement
1100}
1101
1093// Constraints returns the exact constraints that should apply when provisioning1102// Constraints returns the exact constraints that should apply when provisioning
1094// an instance for the machine.1103// an instance for the machine.
1095func (m *Machine) Constraints() (constraints.Value, error) {1104func (m *Machine) Constraints() (constraints.Value, error) {
10961105
=== modified file 'state/policy.go'
--- state/policy.go 2014-04-21 23:24:57 +0000
+++ state/policy.go 2014-04-23 06:25:18 +0000
@@ -49,7 +49,7 @@
49 // all invalid parameters. If PrecheckInstance returns nil, it is not49 // all invalid parameters. If PrecheckInstance returns nil, it is not
50 // guaranteed that the constraints are valid; if a non-nil error is50 // guaranteed that the constraints are valid; if a non-nil error is
51 // returned, then the constraints are definitely invalid.51 // returned, then the constraints are definitely invalid.
52 PrecheckInstance(series string, cons constraints.Value) error52 PrecheckInstance(series string, cons constraints.Value, placement string) error
53}53}
5454
55// ConfigValidator is a policy interface that is provided to State55// ConfigValidator is a policy interface that is provided to State
@@ -78,7 +78,7 @@
7878
79// precheckInstance calls the state's assigned policy, if non-nil, to obtain79// precheckInstance calls the state's assigned policy, if non-nil, to obtain
80// a Prechecker, and calls PrecheckInstance if a non-nil Prechecker is returned.80// a Prechecker, and calls PrecheckInstance if a non-nil Prechecker is returned.
81func (st *State) precheckInstance(series string, cons constraints.Value) error {81func (st *State) precheckInstance(series string, cons constraints.Value, placement string) error {
82 if st.policy == nil {82 if st.policy == nil {
83 return nil83 return nil
84 }84 }
@@ -95,7 +95,7 @@
95 if prechecker == nil {95 if prechecker == nil {
96 return fmt.Errorf("policy returned nil prechecker without an error")96 return fmt.Errorf("policy returned nil prechecker without an error")
97 }97 }
98 return prechecker.PrecheckInstance(series, cons)98 return prechecker.PrecheckInstance(series, cons, placement)
99}99}
100100
101func (st *State) constraintsValidator() (constraints.Validator, error) {101func (st *State) constraintsValidator() (constraints.Validator, error) {
102102
=== modified file 'state/prechecker_test.go'
--- state/prechecker_test.go 2014-04-21 23:10:05 +0000
+++ state/prechecker_test.go 2014-04-23 06:25:18 +0000
@@ -26,11 +26,13 @@
26 precheckInstanceError error26 precheckInstanceError error
27 precheckInstanceSeries string27 precheckInstanceSeries string
28 precheckInstanceConstraints constraints.Value28 precheckInstanceConstraints constraints.Value
29 precheckInstancePlacement string
29}30}
3031
31func (p *mockPrechecker) PrecheckInstance(series string, cons constraints.Value) error {32func (p *mockPrechecker) PrecheckInstance(series string, cons constraints.Value, placement string) error {
32 p.precheckInstanceSeries = series33 p.precheckInstanceSeries = series
33 p.precheckInstanceConstraints = cons34 p.precheckInstanceConstraints = cons
35 p.precheckInstancePlacement = placement
34 return p.precheckInstanceError36 return p.precheckInstanceError
35}37}
3638
@@ -44,13 +46,15 @@
4446
45func (s *PrecheckerSuite) TestPrecheckInstance(c *gc.C) {47func (s *PrecheckerSuite) TestPrecheckInstance(c *gc.C) {
46 // PrecheckInstance should be called with the specified48 // PrecheckInstance should be called with the specified
47 // series, and the specified constraints merged with the49 // series and placement, and the specified constraints
48 // environment constraints, when attempting to create an50 // merged with the environment constraints, when attempting
49 // instance.51 // to create an instance.
50 envCons := constraints.MustParse("mem=4G")52 envCons := constraints.MustParse("mem=4G")
51 template, err := s.addOneMachine(c, envCons)53 placement := "abc123"
54 template, err := s.addOneMachine(c, envCons, placement)
52 c.Assert(err, gc.IsNil)55 c.Assert(err, gc.IsNil)
53 c.Assert(s.prechecker.precheckInstanceSeries, gc.Equals, template.Series)56 c.Assert(s.prechecker.precheckInstanceSeries, gc.Equals, template.Series)
57 c.Assert(s.prechecker.precheckInstancePlacement, gc.Equals, placement)
54 validator := constraints.NewValidator()58 validator := constraints.NewValidator()
55 cons, err := validator.Merge(envCons, template.Constraints)59 cons, err := validator.Merge(envCons, template.Constraints)
56 c.Assert(err, gc.IsNil)60 c.Assert(err, gc.IsNil)
@@ -60,14 +64,14 @@
60func (s *PrecheckerSuite) TestPrecheckErrors(c *gc.C) {64func (s *PrecheckerSuite) TestPrecheckErrors(c *gc.C) {
61 // Ensure that AddOneMachine fails when PrecheckInstance returns an error.65 // Ensure that AddOneMachine fails when PrecheckInstance returns an error.
62 s.prechecker.precheckInstanceError = fmt.Errorf("no instance for you")66 s.prechecker.precheckInstanceError = fmt.Errorf("no instance for you")
63 _, err := s.addOneMachine(c, constraints.Value{})67 _, err := s.addOneMachine(c, constraints.Value{}, "placement")
64 c.Assert(err, gc.ErrorMatches, ".*no instance for you")68 c.Assert(err, gc.ErrorMatches, ".*no instance for you")
6569
66 // If the policy's Prechecker method fails, that will be returned first.70 // If the policy's Prechecker method fails, that will be returned first.
67 s.policy.getPrechecker = func(*config.Config) (state.Prechecker, error) {71 s.policy.getPrechecker = func(*config.Config) (state.Prechecker, error) {
68 return nil, fmt.Errorf("no prechecker for you")72 return nil, fmt.Errorf("no prechecker for you")
69 }73 }
70 _, err = s.addOneMachine(c, constraints.Value{})74 _, err = s.addOneMachine(c, constraints.Value{}, "placement")
71 c.Assert(err, gc.ErrorMatches, ".*no prechecker for you")75 c.Assert(err, gc.ErrorMatches, ".*no prechecker for you")
72}76}
7377
@@ -76,10 +80,10 @@
76 s.policy.getPrechecker = func(*config.Config) (state.Prechecker, error) {80 s.policy.getPrechecker = func(*config.Config) (state.Prechecker, error) {
77 return nil, precheckerErr81 return nil, precheckerErr
78 }82 }
79 _, err := s.addOneMachine(c, constraints.Value{})83 _, err := s.addOneMachine(c, constraints.Value{}, "placement")
80 c.Assert(err, gc.ErrorMatches, "cannot add a new machine: policy returned nil prechecker without an error")84 c.Assert(err, gc.ErrorMatches, "cannot add a new machine: policy returned nil prechecker without an error")
81 precheckerErr = errors.NotImplementedf("Prechecker")85 precheckerErr = errors.NotImplementedf("Prechecker")
82 _, err = s.addOneMachine(c, constraints.Value{})86 _, err = s.addOneMachine(c, constraints.Value{}, "placement")
83 c.Assert(err, gc.IsNil)87 c.Assert(err, gc.IsNil)
84}88}
8589
@@ -89,11 +93,11 @@
89 return nil, nil93 return nil, nil
90 }94 }
91 state.SetPolicy(s.State, nil)95 state.SetPolicy(s.State, nil)
92 _, err := s.addOneMachine(c, constraints.Value{})96 _, err := s.addOneMachine(c, constraints.Value{}, "placement")
93 c.Assert(err, gc.IsNil)97 c.Assert(err, gc.IsNil)
94}98}
9599
96func (s *PrecheckerSuite) addOneMachine(c *gc.C, envCons constraints.Value) (state.MachineTemplate, error) {100func (s *PrecheckerSuite) addOneMachine(c *gc.C, envCons constraints.Value, placement string) (state.MachineTemplate, error) {
97 err := s.State.SetEnvironConstraints(envCons)101 err := s.State.SetEnvironConstraints(envCons)
98 c.Assert(err, gc.IsNil)102 c.Assert(err, gc.IsNil)
99 oneJob := []state.MachineJob{state.JobHostUnits}103 oneJob := []state.MachineJob{state.JobHostUnits}
@@ -102,6 +106,7 @@
102 Series: "precise",106 Series: "precise",
103 Constraints: extraCons,107 Constraints: extraCons,
104 Jobs: oneJob,108 Jobs: oneJob,
109 Placement: placement,
105 }110 }
106 _, err = s.State.AddOneMachine(template)111 _, err = s.State.AddOneMachine(template)
107 return template, err112 return template, err
@@ -113,22 +118,26 @@
113 Series: "precise",118 Series: "precise",
114 Nonce: state.BootstrapNonce,119 Nonce: state.BootstrapNonce,
115 Jobs: []state.MachineJob{state.JobManageEnviron},120 Jobs: []state.MachineJob{state.JobManageEnviron},
121 Placement: "anyoldthing",
116 }122 }
117 _, err := s.State.AddOneMachine(template)123 _, err := s.State.AddOneMachine(template)
118 c.Assert(err, gc.IsNil)124 c.Assert(err, gc.IsNil)
119 // PrecheckInstance should not have been called, as we've125 // PrecheckInstance should not have been called, as we've
120 // injected a machine with an existing instance.126 // injected a machine with an existing instance.
121 c.Assert(s.prechecker.precheckInstanceSeries, gc.Equals, "")127 c.Assert(s.prechecker.precheckInstanceSeries, gc.Equals, "")
128 c.Assert(s.prechecker.precheckInstancePlacement, gc.Equals, "")
122}129}
123130
124func (s *PrecheckerSuite) TestPrecheckContainerNewMachine(c *gc.C) {131func (s *PrecheckerSuite) TestPrecheckContainerNewMachine(c *gc.C) {
125 // Attempting to add a container to a new machine should cause132 // Attempting to add a container to a new machine should cause
126 // PrecheckInstance to be called.133 // PrecheckInstance to be called.
127 template := state.MachineTemplate{134 template := state.MachineTemplate{
128 Series: "precise",135 Series: "precise",
129 Jobs: []state.MachineJob{state.JobHostUnits},136 Jobs: []state.MachineJob{state.JobHostUnits},
137 Placement: "intertubes",
130 }138 }
131 _, err := s.State.AddMachineInsideNewMachine(template, template, instance.LXC)139 _, err := s.State.AddMachineInsideNewMachine(template, template, instance.LXC)
132 c.Assert(err, gc.IsNil)140 c.Assert(err, gc.IsNil)
133 c.Assert(s.prechecker.precheckInstanceSeries, gc.Equals, template.Series)141 c.Assert(s.prechecker.precheckInstanceSeries, gc.Equals, template.Series)
142 c.Assert(s.prechecker.precheckInstancePlacement, gc.Equals, template.Placement)
134}143}
135144
=== modified file 'worker/provisioner/provisioner_task.go'
--- worker/provisioner/provisioner_task.go 2014-04-18 16:37:28 +0000
+++ worker/provisioner/provisioner_task.go 2014-04-23 06:25:18 +0000
@@ -5,6 +5,7 @@
55
6import (6import (
7 "fmt"7 "fmt"
8 "time"
89
9 "launchpad.net/tomb"10 "launchpad.net/tomb"
1011
@@ -450,26 +451,19 @@
450}451}
451452
452func (task *provisionerTask) startMachine(machine *apiprovisioner.Machine) error {453func (task *provisionerTask) startMachine(machine *apiprovisioner.Machine) error {
453 cons, err := machine.Constraints()454 provisioningInfo, err := task.provisioningInfo(machine)
454 if err != nil {455 if err != nil {
455 return err456 return err
456 }457 }
457 series, err := machine.Series()458 possibleTools, err := task.possibleTools(provisioningInfo.Series, provisioningInfo.Constraints)
458 if err != nil {
459 return err
460 }
461 possibleTools, err := task.possibleTools(series, cons)
462 if err != nil {
463 return err
464 }
465 machineConfig, err := task.machineConfig(machine)
466 if err != nil {459 if err != nil {
467 return err460 return err
468 }461 }
469 inst, metadata, networkInfo, err := task.broker.StartInstance(environs.StartInstanceParams{462 inst, metadata, networkInfo, err := task.broker.StartInstance(environs.StartInstanceParams{
470 Constraints: cons,463 Constraints: provisioningInfo.Constraints,
471 Tools: possibleTools,464 Tools: possibleTools,
472 MachineConfig: machineConfig,465 MachineConfig: provisioningInfo.MachineConfig,
466 Placement: provisioningInfo.Placement,
473 DistributionGroup: machine.DistributionGroup,467 DistributionGroup: machine.DistributionGroup,
474 })468 })
475 if err != nil {469 if err != nil {
@@ -478,7 +472,7 @@
478 // error; just keep going with the other machines.472 // error; just keep going with the other machines.
479 return task.setErrorStatus("cannot start instance for machine %q: %v", machine, err)473 return task.setErrorStatus("cannot start instance for machine %q: %v", machine, err)
480 }474 }
481 nonce := machineConfig.MachineNonce475 nonce := provisioningInfo.MachineConfig.MachineNonce
482 networks, ifaces := task.prepareNetworkAndInterfaces(networkInfo)476 networks, ifaces := task.prepareNetworkAndInterfaces(networkInfo)
483477
484 err = machine.SetInstanceInfo(inst.Id(), nonce, metadata, networks, ifaces)478 err = machine.SetInstanceInfo(inst.Id(), nonce, metadata, networks, ifaces)
@@ -512,7 +506,14 @@
512 panic(fmt.Errorf("broker of type %T does not provide any tools", task.broker))506 panic(fmt.Errorf("broker of type %T does not provide any tools", task.broker))
513}507}
514508
515func (task *provisionerTask) machineConfig(machine *apiprovisioner.Machine) (*cloudinit.MachineConfig, error) {509type provisioningInfo struct {
510 Constraints constraints.Value
511 Series string
512 Placement string
513 MachineConfig *cloudinit.MachineConfig
514}
515
516func (task *provisionerTask) provisioningInfo(machine *apiprovisioner.Machine) (*provisioningInfo, error) {
516 stateInfo, apiInfo, err := task.auth.SetupAuthentication(machine)517 stateInfo, apiInfo, err := task.auth.SetupAuthentication(machine)
517 if err != nil {518 if err != nil {
518 logger.Errorf("failed to setup authentication: %v", err)519 logger.Errorf("failed to setup authentication: %v", err)
@@ -525,11 +526,32 @@
525 if err != nil {526 if err != nil {
526 return nil, err527 return nil, err
527 }528 }
528 includeNetworks, excludeNetworks, err := machine.RequestedNetworks()529 // ProvisioningInfo is new in 1.20; wait for the API server to be upgraded
529 if err != nil {530 // so we don't spew errors on upgrade.
531 var pInfo *params.ProvisioningInfo
532 for {
533 if pInfo, err = machine.ProvisioningInfo(); err == nil {
534 break
535 }
536 if params.IsCodeNotImplemented(err) {
537 logger.Infof("waiting for state server to be upgraded")
538 select {
539 case <-task.tomb.Dying():
540 return nil, tomb.ErrDying
541 case <-time.After(15 * time.Second):
542 continue
543 }
544 }
530 return nil, err545 return nil, err
531 }546 }
547 includeNetworks := pInfo.IncludeNetworks
548 excludeNetworks := pInfo.ExcludeNetworks
532 nonce := fmt.Sprintf("%s:%s", task.machineTag, uuid.String())549 nonce := fmt.Sprintf("%s:%s", task.machineTag, uuid.String())
533 machineConfig := environs.NewMachineConfig(machine.Id(), nonce, includeNetworks, excludeNetworks, stateInfo, apiInfo)550 machineConfig := environs.NewMachineConfig(machine.Id(), nonce, includeNetworks, excludeNetworks, stateInfo, apiInfo)
534 return machineConfig, nil551 return &provisioningInfo{
552 Constraints: pInfo.Constraints,
553 Series: pInfo.Series,
554 Placement: pInfo.Placement,
555 MachineConfig: machineConfig,
556 }, nil
535}557}

Subscribers

People subscribed via source and target branches

to status/vote changes: