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

Subscribers

People subscribed via source and target branches

to status/vote changes: