Merge lp:~axwalk/juju-core/sanity-check-constraints into lp:~go-bot/juju-core/trunk
- sanity-check-constraints
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Andrew Wilkins |
Approved revision: | no longer in the source branch. |
Merged at revision: | 1890 |
Proposed branch: | lp:~axwalk/juju-core/sanity-check-constraints |
Merge into: | lp:~go-bot/juju-core/trunk |
Diff against target: |
522 lines (+291/-13) 13 files modified
environs/interface.go (+22/-0) environs/jujutest/livetests.go (+22/-0) errors/errors.go (+37/-13) errors/errors_test.go (+107/-0) provider/azure/environ.go (+13/-0) provider/azure/environ_test.go (+9/-0) provider/ec2/ec2.go (+13/-0) provider/ec2/local_test.go (+11/-0) provider/local/environ.go (+13/-0) provider/local/environ_test.go (+19/-0) provider/maas/environ.go (+1/-0) provider/openstack/local_test.go (+11/-0) provider/openstack/provider.go (+13/-0) |
To merge this branch: | bzr merge lp:~axwalk/juju-core/sanity-check-constraints |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Tim Penhey (community) | Needs Information | ||
William Reade | Pending | ||
Review via email: mp+185015@code.launchpad.net |
Commit message
all: introduce environs.Prechecker
This introduces a new interface, environs.
which is an optional interface that an environs.Environ
may implement.
Prechecker has two methods: PrecheckInstance and
PrecheckContainer. These methods will check if
constraints can possibly be met by the environment.
They will be used to check if an environment is capable
of creating an instance with the specified series and
constraints, or container with the specified type.
If the methods return nil, that does not necessarily
mean that the constraints are satisfied; if a non-nil
error is returned, then it means that the constraints
are definitely not satisfiable.
The local, ec2, azure and openstack Environ implementatons
will now disallow creating containers. When the work to
support addressability is done, this needs to be changed.
Currently, maas lets everything through.
The interface is not currently used anywhere. This will
be done in a followup.
Description of the change
all: introduce environs.Prechecker
This introduces a new interface, environs.
which is an optional interface that an environs.Environ
may implement.
Prechecker has two methods: PrecheckInstance and
PrecheckContainer. These methods will check if
constraints can possibly be met by the environment.
They will be used to check if an environment is capable
of creating an instance with the specified series and
constraints, or container with the specified type.
If the methods return nil, that does not necessarily
mean that the constraints are satisfied; if a non-nil
error is returned, then it means that the constraints
are definitely not satisfiable.
The local, ec2, azure and openstack Environ implementatons
will now disallow creating containers. When the work to
support addressability is done, this needs to be changed.
Currently, maas lets everything through.
The interface is not currently used anywhere. This will
be done in a followup.
Andrew Wilkins (axwalk) wrote : | # |
Andrew Wilkins (axwalk) wrote : | # |
Please take a look.
John A Meinel (jameinel) wrote : | # |
Because this is being added to the environs interface we should have a
test added in environs/
".../tests.go" but I'd like to see the former. That gives us an
interface-level test against all things that implement Environs. (Except
Azure, because it hasn't been hooked into the live testing
infrastructure yet.)
Offhand this could just be that constraints that we will definitely
support (like all values of nil) are checked to not return an error. And
maybe testing an LXC sort of constraint and test that either it succeeds
(err == nil) or it fails in a known way.
Given the functions you've written, something like:
func (s *LiveSuite) TestSanityCheck
var cons constraints.Value
c.
cons.Container = new(instance.
*cons.
err := s.Env.SanityChe
// If err is nil, that is fine, some providers support containers
if err != nil {
// But for ones that don't, they should have a standard syntax
for erroring
containers")
}
}
https:/
File cmd/juju/
https:/
cmd/juju/
errors.
You don't clean up after setting this value, which will give errors in
strange ways for other tests (I think).
You need something like:
defer func() { dummy.SanityChe
https:/
File cmd/juju/
https:/
cmd/juju/
errors.
Similarly here, you should be resetting the error when the test
finishes.
https:/
File cmd/juju/
https:/
cmd/juju/
stderrors.
and here
Maybe turn SanityCheckCons
cleanup func, rather than reproducing that in every test that wants to
override it.
So you would have:
cleanup := dummy.SetSanity
defer cleanup()
https:/
File juju/conn.go (right):
https:/
juju/conn.go:342: }
My first thought on reading this was that you don't need to validate
containerType if you got "3/lxc/2" because that machine must already
exist.
But looking closer, I'm a bit confused by the logic here. It looks like
you get really weird behavior if you do: lxc:2:3
It actually looks like you could do: lxc:2:lxc:3 to create a new lxc on
machine 2/lxc/3, thoug...
William Reade (fwereade) wrote : | # |
General +1 to jam's comments.
It looks to me like deploy/add-unit and add-machine go through different
code paths that both involve manipulating placement directives. That
doesn't necessarily need to be fixed this branch, but it must be
addressed in a followup; it's hard to trust that they're still doing the
same things in the same ways.
https:/
File cmd/juju/
https:/
cmd/juju/
conn.Environ.
We really shouldn't use conn.Environ. We should probably unexport it,
even.
https:/
File cmd/juju/
https:/
cmd/juju/
errors.
On 2013/09/11 12:05:59, jameinel wrote:
> You don't clean up after setting this value, which will give errors in
strange
> ways for other tests (I think).
> You need something like:
> defer func() { dummy.SanityChe
Oddly enough I just approved rog's jc.Set, which works in exactly this
situation.
https:/
File juju/conn.go (right):
https:/
juju/conn.go:329: machineId = strings.
Yeah, I'm pretty sure we don't want this. :s later in the string should
not be turned into /s.
And I remain really nervous about having this logic right here,
regardless... can we break it out a little, and write it in terms of
"placement directives", in which the bit before the first : determines
the handler for the bit afterwards? And thus have an explicit lxc
handler, ready to add the others?
I do appreciate it's just a code move, but even if you don't fix it
today please be sure to document that it's a good-enough-for-now hack at
the above functionality.
https:/
File provider/
https:/
provider/
On 2013/09/11 12:05:59, jameinel wrote:
> I don't think we actually support nesting LXC's in the local provider.
So we
> should probably check and return an error here, rather than allowing
it.
+1
William Reade (fwereade) wrote : | # |
Hmm, further to my previous comments... we shouldn't be calling
SanityCheckCons
statecmd (or wherever the implementation is currently shared by the API)
rather than ever doing it directly in the command.
William Reade (fwereade) wrote : | # |
On 2013/09/11 16:17:54, fwereade wrote:
> Hmm, further to my previous comments... we shouldn't be calling
> SanityCheckCons
statecmd (or
> wherever the implementation is currently shared by the API) rather
than ever
> doing it directly in the command.
And... hmm. I think it's a bit broken for nested containers -- the
environ may not support a particular container type, but it may be able
to host a different sort of container that can. We don't support nesting
today but I would prefer to avoid making assumptions based on this state
of affairs.
Andrew Wilkins (axwalk) wrote : | # |
On 2013/09/11 12:05:59, jameinel wrote:
> Because this is being added to the environs interface we should have a
test
> added in environs/
".../tests.go" but I'd
> like to see the former. That gives us an interface-level test against
all things
> that implement Environs. (Except Azure, because it hasn't been hooked
into the
> live testing infrastructure yet.)
Thanks, I wasn't familiar with this common test code. Done.
> Offhand this could just be that constraints that we will definitely
support
> (like all values of nil) are checked to not return an error. And maybe
testing
> an LXC sort of constraint and test that either it succeeds (err ==
nil) or it
> fails in a known way.
> Given the functions you've written, something like:
> func (s *LiveSuite) TestSanityCheck
> var cons constraints.Value
> c.Check(
> cons.Container = new(instance.
> *cons.Container = instance.LXC
> err := s.Env.SanityChe
> // If err is nil, that is fine, some providers support containers
> if err != nil {
> // But for ones that don't, they should have a standard syntax
for
> erroring
> c.Check(err, gc.ErrorMatches, ".*provider does not support
containers")
> }
> }
Thanks, added that in. I tidied up the imports while I was at it.
https:/
> File cmd/juju/
https:/
> cmd/juju/
> errors.
> You don't clean up after setting this value, which will give errors in
strange
> ways for other tests (I think).
> You need something like:
> defer func() { dummy.SanityChe
https:/
> File cmd/juju/
https:/
> cmd/juju/
> errors.
> Similarly here, you should be resetting the error when the test
finishes.
https:/
> File cmd/juju/
https:/
> cmd/juju/
> stderrors.
> and here
> Maybe turn SanityCheckCons
cleanup
> func, rather than reproducing that in every test that wants to
override it.
> So you would have:
> cleanup := dummy.SetSanity
> defer cleanup()
> https:/
> File juju/conn.go (right):
https:/
> juju/conn.go:342: }
> My first thought on reading this was that you don't need to validate
> containerType if you got...
Andrew Wilkins (axwalk) wrote : | # |
On 2013/09/11 16:17:54, fwereade wrote:
> Hmm, further to my previous comments... we shouldn't be calling
> SanityCheckCons
statecmd (or
> wherever the implementation is currently shared by the API) rather
than ever
> doing it directly in the command.
That would be good, but add-machine, add-unit and deploy all currently
go directly to state. State doesn't work with Environs.
I can't see a better place for these to go right now.
Andrew Wilkins (axwalk) wrote : | # |
On 2013/09/11 16:20:46, fwereade wrote:
> On 2013/09/11 16:17:54, fwereade wrote:
> > Hmm, further to my previous comments... we shouldn't be calling
> > SanityCheckCons
statecmd (or
> > wherever the implementation is currently shared by the API) rather
than ever
> > doing it directly in the command.
> And... hmm. I think it's a bit broken for nested containers -- the
environ may
> not support a particular container type, but it may be able to host a
different
> sort of container that can. We don't support nesting today but I would
prefer to
> avoid making assumptions based on this state of affairs.
SanityCheckCons
container X in top-level machines", it's saying, nowhere in this
environment can container X exist (e.g. due to lack of addressability).
To handle that scenario, we need the second piece that thumper
mentioned: something on the Instance interface that says whether or not
that instance supports creating a container of type X.
I feel like I might be missing something here.
Andrew Wilkins (axwalk) wrote : | # |
https:/
File cmd/juju/
https:/
cmd/juju/
conn.Environ.
On 2013/09/11 14:43:35, fwereade wrote:
> We really shouldn't use conn.Environ. We should probably unexport it,
even.
Sigh, sorry (again). Will fix.
https:/
File cmd/juju/
https:/
cmd/juju/
errors.
On 2013/09/11 12:05:59, jameinel wrote:
> You don't clean up after setting this value, which will give errors in
strange
> ways for other tests (I think).
> You need something like:
> defer func() { dummy.SanityChe
I do, it's just not obvious from here. It's cleaned up in dummy.Reset,
which gets called in the JujuConnSuite's TearDownTest. This is similar
to how dummy.Listen is used.
https:/
File juju/conn.go (right):
https:/
juju/conn.go:329: machineId = strings.
On 2013/09/11 14:43:35, fwereade wrote:
> Yeah, I'm pretty sure we don't want this. :s later in the string
should not be
> turned into /s.
> And I remain really nervous about having this logic right here,
regardless...
> can we break it out a little, and write it in terms of "placement
directives",
> in which the bit before the first : determines the handler for the bit
> afterwards? And thus have an explicit lxc handler, ready to add the
others?
> I do appreciate it's just a code move, but even if you don't fix it
today please
> be sure to document that it's a good-enough-for-now hack at the above
> functionality.
I have added a comment stating this is due for refactoring into
placement directives, and changes in logic should consider that first.
When I've got my current work done, I'll have a look into it.
https:/
juju/conn.go:342: }
On 2013/09/11 12:05:59, jameinel wrote:
> My first thought on reading this was that you don't need to validate
> containerType if you got "3/lxc/2" because that machine must already
exist.
> But looking closer, I'm a bit confused by the logic here. It looks
like you get
> really weird behavior if you do: lxc:2:3
> It actually looks like you could do: lxc:2:lxc:3 to create a new lxc
on machine
> 2/lxc/3, though if we really want to support that, we'd use the syntax
> lxc:2/lxc/3
> (I don't really understand why we have the strings.
> Anyway, I realize you just moved the code. Though I don't fully know
why you had
> to move it.
I didn't have to, I just die a little bit inside when I see code in
loops that doesn't need to be.
https:/
File provider/
Andrew Wilkins (axwalk) wrote : | # |
Please take a look.
William Reade (fwereade) wrote : | # |
OK, I'm sorry I didn't express this well before, but it only just
clicked: this is happening at the wrong level. See
state/state.
the constraints we actually use.
But... threading the environ through to the place we actually need to
use it may be a problem, because state knows naught of the environ; we
can stick SanityCheckCons
still not entirely clear how to get it on target (pass one in AddMachine
and AddMachineWithC
a SanityChecker on the state object itself? neither prospect fills me
with glee).
But I don't think we can usefully do it at this level, so this approach
does not LGTM as is.
FWIW, I think that we may be missing a trick with the
constraints-
wondering whether we should do something like:
package instance
type Preflighter interface {
Preflight(
}
...and implementations thereof on the environs -- which should be
entirely uncontroversial -- and get that landed so we can focus
separately on the problem of applying it in the right place. Sane?
Andrew Wilkins (axwalk) wrote : | # |
On 2013/09/13 08:17:48, fwereade wrote:
> OK, I'm sorry I didn't express this well before, but it only just
clicked: this
> is happening at the wrong level. See state/state.
constraints
> you're checking do not match the constraints we actually use.
Ah, I see now. Thanks for explaining.
> But... threading the environ through to the place we actually need to
use it may
> be a problem, because state knows naught of the environ; we can stick
> SanityCheckCons
entirely
> clear how to get it on target (pass one in AddMachine and
> AddMachineWithC
> SanityChecker on the state object itself? neither prospect fills me
with glee).
> But I don't think we can usefully do it at this level, so this
approach does not
> LGTM as is.
> FWIW, I think that we may be missing a trick with the
constraints-
> here. Series is also sanely preflightable; I'm wondering whether we
should do
> something like:
> package instance
> type Preflighter interface {
> Preflight(series string, cons constraints.Value)
> }
Interesting name! That's a bit more memorable than "sanity check".
> ...and implementations thereof on the environs -- which should be
entirely
> uncontroversial -- and get that landed so we can focus separately on
the problem
> of applying it in the right place. Sane?
Yes, that sounds like a fine approach. I'll back out the juju/conn &
cmd/juju bits while pondering how best to do it.
Andrew Wilkins (axwalk) wrote : | # |
Please take a look.
Andrew Wilkins (axwalk) wrote : | # |
Please take a look.
Tim Penhey (thumper) wrote : | # |
On 2013/09/13 08:35:28, axw1 wrote:
> On 2013/09/13 08:17:48, fwereade wrote:
> > package instance
> >
> > type Preflighter interface {
> > Preflight(series string, cons constraints.Value)
> > }
> Interesting name! That's a bit more memorable than "sanity check".
Yes it is an interesting name, but I'm -1 on it as it is neither in the
problem nor solution domain.
I'll go through and review now and see if a better name comes to mind.
Tim Penhey (thumper) wrote : | # |
If we consider what the preflight check is for, it is to validate or vet
the series and constraints for machine creation.
If we look through a thesaurus here, other ideas:
vet, probe, examine, inspect, precheck, review
ProbeMachineCre
PrecheckCreateM
VetCreateMachine
ReviewMachineCr
A few ideas there.
https:/
File environs/
https:/
environs/
string, cons constraints.Value) error
This is a small thing, but if there are two parameters that are always
sent and one that is sometimes sent, I have a preference that the
sometimes sent one is last.
https:/
File environs/
https:/
environs/
should have a standard error format.
I think we should go one step further than a standard error format, and
have a standard error type.
https:/
File provider/
https:/
provider/
instance.NONE is a valid container constraint, so we should probably
allow that too.
https:/
File provider/
https:/
provider/
No Preflight equivalent check here?
Tim Penhey (thumper) : | # |
Andrew Wilkins (axwalk) wrote : | # |
I have renamed the interface to Prechecker, and created separate methods
for checking machine and container creation.
https:/
File environs/
https:/
environs/
string, cons constraints.Value) error
On 2013/09/18 23:04:08, thumper wrote:
> This is a small thing, but if there are two parameters that are always
sent and
> one that is sometimes sent, I have a preference that the sometimes
sent one is
> last.
As discussed on IRC, I'm going to split it out into two methods: one for
prechecking machine creation, one for container creation.
https:/
File environs/
https:/
environs/
should have a standard error format.
On 2013/09/18 23:04:08, thumper wrote:
> I think we should go one step further than a standard error format,
and have a
> standard error type.
Done. Added "containersUnsu
juju-core/errors, and added a jc.Satisfies check here.
https:/
File provider/
https:/
provider/
On 2013/09/18 23:04:08, thumper wrote:
> instance.NONE is a valid container constraint, so we should probably
allow that
> too.
Done.
https:/
File provider/
https:/
provider/
On 2013/09/18 23:04:08, thumper wrote:
> No Preflight equivalent check here?
Is there anything that's currently unsupported?
Andrew Wilkins (axwalk) wrote : | # |
Please take a look.
Tim Penhey (thumper) wrote : | # |
On 2013/09/19 02:44:05, axw wrote:
> Please take a look.
LGTM, although I do wonder if we should wait for fwereade to override
his not.
Andrew Wilkins (axwalk) wrote : | # |
On 2013/09/20 03:19:24, thumper wrote:
> On 2013/09/19 02:44:05, axw wrote:
> > Please take a look.
> LGTM, although I do wonder if we should wait for fwereade to override
his not.
Thanks. I will wait. There's a followup needed for this to be turned on,
anyway.
William Reade (fwereade) wrote : | # |
Sorry for the delay... my eyes somehow slid right past it several times,
I think.
Assuming this is still intended for use by state, to check final
constraints for sanity before creating the mongo documents, I think my
criticisms below are on-target. If it's intended for something else, in
light of your discussions with thumper, I'm still pretty uncertain about
using a constraints value to deliver the container-type information...
but I could possibly be convinced that it's useful for the environ to
know what instance is being asked about.
https:/
File environs/
https:/
environs/
not guaranteed to eliminated
s/eliminated/
https:/
environs/
constraints.Value) error
I don't think the environ should be talking machines, it should be
talking instances. I know it's called before CreateMachine, but it feels
to me more like it's a heuristic for whether *StartInstance* will work
and that that's what it should be named after.
https:/
environs/
constraints.Value, host instance.Instance) error
I wasn't there for the talks with thumper, so maybe the plan already
addresses this, but if this is meant to be used inside the state package
you'll need an instance.Id rather than an Instance (). I think using an
Id is kinda nice anyway, because you clearly don't always need an actual
Instance -- many providers just say "no".
https:/
File provider/
https:/
provider/
prechecker.
This nil instance doesn't sit quite right with me. Not enough to block,
but enough to whine a little.
https:/
File provider/
https:/
provider/
prechecker.
Hmm. I'm thinking... what does the environ particularly know about what
*host* will be able to run a container? By passing it, we kinda imply
that the constraints will be checked against the host's hardware... is
that sensible? Can we maybe drop the instance param entirely? Anything
machine-specific that we can discover should surely be stored in state
anyway -- eg the hardware characteristics that we record at provisioning
time (and against which we do indeed do the aforementioned checking, I
think).
I guess the environment *is* in a position to know about available
series, because the environment sup...
Andrew Wilkins (axwalk) wrote : | # |
Thanks for the review.
I've removed the series from PrecheckContainer, too. Let me know if you
think that's actually needed (it certainly won't be checked in my
initial cut).
https:/
File environs/
https:/
environs/
not guaranteed to eliminated
On 2013/09/23 14:36:11, fwereade wrote:
> s/eliminated/
Done.
https:/
environs/
constraints.Value) error
On 2013/09/23 14:36:11, fwereade wrote:
> I don't think the environ should be talking machines, it should be
talking
> instances. I know it's called before CreateMachine, but it feels to me
more like
> it's a heuristic for whether *StartInstance* will work and that that's
what it
> should be named after.
Fair enough. PrecheckInstanc
https:/
environs/
constraints.Value, host instance.Instance) error
On 2013/09/23 14:36:11, fwereade wrote:
> I wasn't there for the talks with thumper, so maybe the plan already
addresses
> this, but if this is meant to be used inside the state package you'll
need an
> instance.Id rather than an Instance (). I think using an Id is kinda
nice
> anyway, because you clearly don't always need an actual Instance --
many
> providers just say "no".
Oops, of course, thanks. Doesn't matter for now, as I'll remove the host
parameter altogether.
https:/
File provider/
https:/
provider/
prechecker.
On 2013/09/23 14:36:11, fwereade wrote:
> This nil instance doesn't sit quite right with me. Not enough to
block, but
> enough to whine a little.
I agree. I figured the amount of work to get a real instance wasn't
worthwhile. It's moot now, since the parameter is disappearing.
https:/
File provider/
https:/
provider/
prechecker.
On 2013/09/23 14:36:11, fwereade wrote:
> Hmm. I'm thinking... what does the environ particularly know about
what *host*
> will be able to run a container? By passing it, we kinda imply that
the
> constraints will be checked against the host's hardware... is that
sensible? Can
> we maybe drop the instance param entirely? Anything machine-specific
that we can
> discover should surely be stored in state anyway -- eg the hardware
> characteristics that we record at provisioning time (and against which
w...
Andrew Wilkins (axwalk) wrote : | # |
Please take a look.
Andrew Wilkins (axwalk) wrote : | # |
Please take a look.
William Reade (fwereade) wrote : | # |
LGTM, thanks.
https:/
File environs/
https:/
environs/
instance.
I'm easy wrt series. I think we will one day want to check it, but our
best effort doe not yet include that; I worry a tiny bit that we'll also
want to be checking arch (people might have a legitimate reason to ask
for an i386 kvm machine...) but that's a bit less certain and not worth
the churn. Let it stand as it is.
https:/
File errors/errors.go (right):
https:/
errors/
containersUnsup
Hey, are these tested?
Andrew Wilkins (axwalk) wrote : | # |
Thank you. I'll add direct tests for the coreerrors changes.
https:/
File errors/errors.go (right):
https:/
errors/
containersUnsup
On 2013/09/26 07:37:53, fwereade wrote:
> Hey, are these tested?
Tested in jujutest/
Andrew Wilkins (axwalk) wrote : | # |
Please take a look.
Andrew Wilkins (axwalk) wrote : | # |
On 2013/09/26 08:49:40, axw wrote:
> Please take a look.
Added some tests, and fixed a bug in an existing error constructor ;)
Will land now.
Go Bot (go-bot) wrote : | # |
The attempt to merge lp:~axwalk/juju-core/sanity-check-constraints into lp:juju-core failed. Below is the output from the failed tests.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
? launchpad.
? launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
FAIL launchpad.
ok launchpad.
FAIL launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
FAIL launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
Go Bot (go-bot) wrote : | # |
There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.
Preview Diff
1 | === modified file 'environs/interface.go' |
2 | --- environs/interface.go 2013-09-25 17:30:09 +0000 |
3 | +++ environs/interface.go 2013-09-26 12:28:55 +0000 |
4 | @@ -88,6 +88,28 @@ |
5 | Config() *config.Config |
6 | } |
7 | |
8 | +// Prechecker is an optional interface that an Environ may implement, |
9 | +// in order to support pre-flight checking of instance/container creation. |
10 | +// |
11 | +// Prechecker's methods are best effort, and not guaranteed to eliminate |
12 | +// all invalid parameters. If a precheck method returns nil, it is not |
13 | +// guaranteed that the constraints are valid; if a non-nil error is |
14 | +// returned, then the constraints are definitely invalid. |
15 | +type Prechecker interface { |
16 | + // PrecheckInstance performs a preflight check on the specified |
17 | + // series and constraints, ensuring that they are possibly valid for |
18 | + // creating an instance in this environment. |
19 | + PrecheckInstance(series string, cons constraints.Value) error |
20 | + |
21 | + // PrecheckContainer performs a preflight check on the container type, |
22 | + // ensuring that the environment is possibly capable of creating a |
23 | + // container of the specified type and series. |
24 | + // |
25 | + // The container type must be a valid ContainerType as specified |
26 | + // in the instance package, and != instance.NONE. |
27 | + PrecheckContainer(series string, kind instance.ContainerType) error |
28 | +} |
29 | + |
30 | // An Environ represents a juju environment as specified |
31 | // in the environments.yaml file. |
32 | // |
33 | |
34 | === modified file 'environs/jujutest/livetests.go' |
35 | --- environs/jujutest/livetests.go 2013-09-24 16:07:20 +0000 |
36 | +++ environs/jujutest/livetests.go 2013-09-26 12:28:55 +0000 |
37 | @@ -23,6 +23,7 @@ |
38 | "launchpad.net/juju-core/environs/sync" |
39 | envtesting "launchpad.net/juju-core/environs/testing" |
40 | envtools "launchpad.net/juju-core/environs/tools" |
41 | + coreerrors "launchpad.net/juju-core/errors" |
42 | "launchpad.net/juju-core/errors" |
43 | "launchpad.net/juju-core/instance" |
44 | "launchpad.net/juju-core/juju" |
45 | @@ -153,6 +154,27 @@ |
46 | t.Env = nil |
47 | } |
48 | |
49 | +func (t *LiveTests) TestPrechecker(c *gc.C) { |
50 | + // Providers may implement Prechecker. If they do, then they should |
51 | + // return nil for empty constraints (excluding the null provider). |
52 | + prechecker, ok := t.Env.(environs.Prechecker) |
53 | + if !ok { |
54 | + return |
55 | + } |
56 | + |
57 | + const series = "precise" |
58 | + var cons constraints.Value |
59 | + c.Check(prechecker.PrecheckInstance(series, cons), gc.IsNil) |
60 | + |
61 | + err := prechecker.PrecheckContainer(series, instance.LXC) |
62 | + // If err is nil, that is fine, some providers support containers. |
63 | + if err != nil { |
64 | + // But for ones that don't, they should have a standard error format. |
65 | + c.Check(err, gc.ErrorMatches, ".*provider does not support containers") |
66 | + c.Check(err, jc.Satisfies, coreerrors.IsContainersUnsupported) |
67 | + } |
68 | +} |
69 | + |
70 | // TestStartStop is similar to Tests.TestStartStop except |
71 | // that it does not assume a pristine environment. |
72 | func (t *LiveTests) TestStartStop(c *gc.C) { |
73 | |
74 | === modified file 'errors/errors.go' |
75 | --- errors/errors.go 2013-08-13 19:07:35 +0000 |
76 | +++ errors/errors.go 2013-09-26 12:28:55 +0000 |
77 | @@ -30,8 +30,8 @@ |
78 | *errorWrapper |
79 | } |
80 | |
81 | -// IsNotFoundError is satisfied by errors created by this package representing resources that can't |
82 | -// be found. |
83 | +// IsNotFoundError is satisfied by errors created by this package representing |
84 | +// resources that can't be found. |
85 | func IsNotFoundError(err error) bool { |
86 | if _, ok := err.(notFoundError); ok { |
87 | return true |
88 | @@ -51,19 +51,21 @@ |
89 | } |
90 | } |
91 | |
92 | -// NewNotFoundError returns a new error wrapping err that satisfies IsNotFoundError(). |
93 | +// NewNotFoundError returns a new error wrapping err that satisfies |
94 | +// IsNotFoundError(). |
95 | func NewNotFoundError(err error, msg string) error { |
96 | return notFoundError{&errorWrapper{Err: err, Msg: msg}} |
97 | } |
98 | |
99 | // unauthorizedError represents the error that an operation is unauthorized. |
100 | -// Use IsUnauthorized() to determine if the error was related to authorization failure. |
101 | +// Use IsUnauthorized() to determine if the error was related to authorization |
102 | +// failure. |
103 | type unauthorizedError struct { |
104 | *errorWrapper |
105 | } |
106 | |
107 | -// IsUnauthorizedError is satisfied by errors created by this package representing |
108 | -// authorization failures. |
109 | +// IsUnauthorizedError is satisfied by errors created by this package |
110 | +// representing authorization failures. |
111 | func IsUnauthorizedError(err error) bool { |
112 | _, ok := err.(unauthorizedError) |
113 | return ok |
114 | @@ -78,19 +80,20 @@ |
115 | } |
116 | } |
117 | |
118 | -// NewUnauthorizedError returns an error which wraps err and satisfies IsUnauthorized(). |
119 | +// NewUnauthorizedError returns an error which wraps err and satisfies |
120 | +// IsUnauthorized(). |
121 | func NewUnauthorizedError(err error, msg string) error { |
122 | - return unauthorizedError{&errorWrapper{Msg: msg}} |
123 | + return unauthorizedError{&errorWrapper{Err: err, Msg: msg}} |
124 | } |
125 | |
126 | -// notBootstrappedError indicates that the environment can't be used because it hasn't been |
127 | -// bootstrapped yet. |
128 | +// notBootstrappedError indicates that the environment can't be used because it |
129 | +// hasn't been bootstrapped yet. |
130 | type notBootstrappedError struct { |
131 | *errorWrapper |
132 | } |
133 | |
134 | -// IsNotBootstrapped is satisfied by errors created by this package representing an environment |
135 | -// that isn't bootstrapped. |
136 | +// IsNotBootstrapped is satisfied by errors created by this package |
137 | +// representing an environment that isn't bootstrapped. |
138 | func IsNotBootstrapped(err error) bool { |
139 | if _, ok := err.(notBootstrappedError); ok { |
140 | return true |
141 | @@ -98,7 +101,28 @@ |
142 | return false |
143 | } |
144 | |
145 | -// NewNotBootstrappedError returns a new error which wraps err and satisfies IsNotBootstrapped(). |
146 | +// NewNotBootstrappedError returns a new error which wraps err and satisfies |
147 | +// IsNotBootstrapped(). |
148 | func NewNotBootstrappedError(err error, msg string) error { |
149 | return notBootstrappedError{&errorWrapper{Err: err, Msg: msg}} |
150 | } |
151 | + |
152 | +// containersUnsupportedError indicates that the environment does not support |
153 | +// creation of containers. |
154 | +type containersUnsupportedError struct { |
155 | + *errorWrapper |
156 | +} |
157 | + |
158 | +// IsContainersUnsupported is satisfied by errors, created by this package, |
159 | +// representing an attempt to create a container that failed on account of |
160 | +// containers not being supported by the environment. |
161 | +func IsContainersUnsupported(err error) bool { |
162 | + _, ok := err.(containersUnsupportedError) |
163 | + return ok |
164 | +} |
165 | + |
166 | +// NewContainersUnsupportedError returns a new error which wraps err and |
167 | +// satisfies IsContainersUnsupported(). |
168 | +func NewContainersUnsupported(err error, msg string) error { |
169 | + return containersUnsupportedError{&errorWrapper{Err: err, Msg: msg}} |
170 | +} |
171 | |
172 | === added file 'errors/errors_test.go' |
173 | --- errors/errors_test.go 1970-01-01 00:00:00 +0000 |
174 | +++ errors/errors_test.go 2013-09-26 12:28:55 +0000 |
175 | @@ -0,0 +1,107 @@ |
176 | +// Copyright 2013 Canonical Ltd. |
177 | +// Licensed under the AGPLv3, see LICENCE file for details. |
178 | + |
179 | +package errors_test |
180 | + |
181 | +import ( |
182 | + stderrors "errors" |
183 | + "reflect" |
184 | + "runtime" |
185 | + "testing" |
186 | + |
187 | + gc "launchpad.net/gocheck" |
188 | + |
189 | + "launchpad.net/juju-core/errors" |
190 | + jc "launchpad.net/juju-core/testing/checkers" |
191 | +) |
192 | + |
193 | +type errorsSuite struct{} |
194 | + |
195 | +var _ = gc.Suite(&errorsSuite{}) |
196 | + |
197 | +func Test(t *testing.T) { |
198 | + gc.TestingT(t) |
199 | +} |
200 | + |
201 | +type errorSatisfier struct { |
202 | + f func(error) bool |
203 | +} |
204 | + |
205 | +func (s *errorSatisfier) String() string { |
206 | + value := reflect.ValueOf(s.f) |
207 | + f := runtime.FuncForPC(value.Pointer()) |
208 | + return f.Name() |
209 | +} |
210 | + |
211 | +func (*errorsSuite) TestNotFoundError(c *gc.C) { |
212 | + isNotFoundError := &errorSatisfier{errors.IsNotFoundError} |
213 | + isUnauthorizedError := &errorSatisfier{errors.IsUnauthorizedError} |
214 | + isNotBootstrapped := &errorSatisfier{errors.IsNotBootstrapped} |
215 | + isContainersUnsupported := &errorSatisfier{errors.IsContainersUnsupported} |
216 | + satisfiers := []*errorSatisfier{ |
217 | + isNotFoundError, |
218 | + isUnauthorizedError, |
219 | + isNotBootstrapped, |
220 | + isContainersUnsupported, |
221 | + } |
222 | + |
223 | + // make some errors, and record the errorSatsifier |
224 | + // that should satisfy the error. |
225 | + type errorTest struct { |
226 | + err error |
227 | + message string |
228 | + satisfier *errorSatisfier |
229 | + } |
230 | + errorTests := []errorTest{{ |
231 | + errors.NotFoundf("woop %d", 123), |
232 | + "woop 123 not found", |
233 | + isNotFoundError, |
234 | + }, { |
235 | + errors.NewNotFoundError(stderrors.New("woo"), "msg"), |
236 | + "msg: woo", |
237 | + isNotFoundError, |
238 | + }, { |
239 | + errors.NewNotFoundError(stderrors.New("woo"), ""), |
240 | + "woo", |
241 | + isNotFoundError, |
242 | + }, { |
243 | + errors.NewNotFoundError(nil, "msg"), |
244 | + "msg", |
245 | + isNotFoundError, |
246 | + }, { |
247 | + errors.NewNotFoundError(nil, ""), |
248 | + "", |
249 | + isNotFoundError, |
250 | + }, { |
251 | + errors.Unauthorizedf("woo %s", "hoo"), |
252 | + "woo hoo", |
253 | + isUnauthorizedError, |
254 | + }, { |
255 | + errors.NewUnauthorizedError(stderrors.New("hoo"), "woo"), |
256 | + "woo: hoo", |
257 | + isUnauthorizedError, |
258 | + }, { |
259 | + errors.NewNotBootstrappedError(stderrors.New("hoo"), "woo"), |
260 | + "woo: hoo", |
261 | + isNotBootstrapped, |
262 | + }, { |
263 | + errors.NewContainersUnsupported(stderrors.New("hoo"), "woo"), |
264 | + "woo: hoo", |
265 | + isContainersUnsupported, |
266 | + }} |
267 | + |
268 | + for i, t := range errorTests { |
269 | + c.Logf("test #%d: %v", i, t.err) |
270 | + c.Assert(t.err, gc.ErrorMatches, t.message) |
271 | + c.Assert(t.err, jc.Satisfies, t.satisfier.f) |
272 | + for _, satisfier := range satisfiers { |
273 | + // Not using jc.Satisfier here, because it doesn't give |
274 | + // a nice string representation of the function. Also, |
275 | + // you can't take the address of a func, but you can |
276 | + // store it and take the address of the struct. |
277 | + if satisfier != t.satisfier && satisfier.f(t.err) { |
278 | + c.Errorf("%#v satisfies %v", t.err, satisfier) |
279 | + } |
280 | + } |
281 | + } |
282 | +} |
283 | |
284 | === modified file 'provider/azure/environ.go' |
285 | --- provider/azure/environ.go 2013-09-25 11:19:35 +0000 |
286 | +++ provider/azure/environ.go 2013-09-26 12:28:55 +0000 |
287 | @@ -20,6 +20,7 @@ |
288 | "launchpad.net/juju-core/environs/simplestreams" |
289 | "launchpad.net/juju-core/environs/storage" |
290 | envtools "launchpad.net/juju-core/environs/tools" |
291 | + coreerrors "launchpad.net/juju-core/errors" |
292 | "launchpad.net/juju-core/instance" |
293 | "launchpad.net/juju-core/provider" |
294 | "launchpad.net/juju-core/state" |
295 | @@ -137,6 +138,18 @@ |
296 | return key, nil |
297 | } |
298 | |
299 | +// PrecheckInstance is specified in the environs.Prechecker interface. |
300 | +func (*azureEnviron) PrecheckInstance(series string, cons constraints.Value) error { |
301 | + return nil |
302 | +} |
303 | + |
304 | +// PrecheckContainer is specified in the environs.Prechecker interface. |
305 | +func (*azureEnviron) PrecheckContainer(series string, kind instance.ContainerType) error { |
306 | + // This check can either go away or be relaxed when the azure |
307 | + // provider manages container addressibility. |
308 | + return coreerrors.NewContainersUnsupported(nil, "azure provider does not support containers") |
309 | +} |
310 | + |
311 | // Name is specified in the Environ interface. |
312 | func (env *azureEnviron) Name() string { |
313 | return env.name |
314 | |
315 | === modified file 'provider/azure/environ_test.go' |
316 | --- provider/azure/environ_test.go 2013-09-24 09:42:10 +0000 |
317 | +++ provider/azure/environ_test.go 2013-09-26 12:28:55 +0000 |
318 | @@ -89,6 +89,15 @@ |
319 | c.Check(env.Name(), gc.Equals, env.name) |
320 | } |
321 | |
322 | +func (*environSuite) TestPrecheck(c *gc.C) { |
323 | + env := azureEnviron{name: "foo"} |
324 | + var cons constraints.Value |
325 | + err := env.PrecheckInstance("saucy", cons) |
326 | + c.Check(err, gc.IsNil) |
327 | + err = env.PrecheckContainer("saucy", instance.LXC) |
328 | + c.Check(err, gc.ErrorMatches, "azure provider does not support containers") |
329 | +} |
330 | + |
331 | func (*environSuite) TestConfigReturnsConfig(c *gc.C) { |
332 | cfg := new(config.Config) |
333 | ecfg := azureEnvironConfig{Config: cfg} |
334 | |
335 | === modified file 'provider/ec2/ec2.go' |
336 | --- provider/ec2/ec2.go 2013-09-25 17:30:09 +0000 |
337 | +++ provider/ec2/ec2.go 2013-09-26 12:28:55 +0000 |
338 | @@ -25,6 +25,7 @@ |
339 | "launchpad.net/juju-core/environs/simplestreams" |
340 | "launchpad.net/juju-core/environs/storage" |
341 | envtools "launchpad.net/juju-core/environs/tools" |
342 | + coreerrors "launchpad.net/juju-core/errors" |
343 | "launchpad.net/juju-core/instance" |
344 | "launchpad.net/juju-core/provider" |
345 | "launchpad.net/juju-core/state" |
346 | @@ -307,6 +308,18 @@ |
347 | return s3 |
348 | } |
349 | |
350 | +// PrecheckInstance is specified in the environs.Prechecker interface. |
351 | +func (e *environ) PrecheckInstance(series string, cons constraints.Value) error { |
352 | + return nil |
353 | +} |
354 | + |
355 | +// PrecheckContainer is specified in the environs.Prechecker interface. |
356 | +func (e *environ) PrecheckContainer(series string, kind instance.ContainerType) error { |
357 | + // This check can either go away or be relaxed when the ec2 |
358 | + // provider manages container addressibility. |
359 | + return coreerrors.NewContainersUnsupported(nil, "ec2 provider does not support containers") |
360 | +} |
361 | + |
362 | func (e *environ) Name() string { |
363 | return e.name |
364 | } |
365 | |
366 | === modified file 'provider/ec2/local_test.go' |
367 | --- provider/ec2/local_test.go 2013-09-24 10:08:33 +0000 |
368 | +++ provider/ec2/local_test.go 2013-09-26 12:28:55 +0000 |
369 | @@ -190,6 +190,17 @@ |
370 | t.srv.stopServer(c) |
371 | } |
372 | |
373 | +func (t *localServerSuite) TestPrecheck(c *gc.C) { |
374 | + env := t.Prepare(c) |
375 | + prechecker, ok := env.(environs.Prechecker) |
376 | + c.Assert(ok, jc.IsTrue) |
377 | + var cons constraints.Value |
378 | + err := prechecker.PrecheckInstance("precise", cons) |
379 | + c.Check(err, gc.IsNil) |
380 | + err = prechecker.PrecheckContainer("precise", instance.LXC) |
381 | + c.Check(err, gc.ErrorMatches, "ec2 provider does not support containers") |
382 | +} |
383 | + |
384 | func (t *localServerSuite) TestBootstrapInstanceUserDataAndState(c *gc.C) { |
385 | env := t.Prepare(c) |
386 | envtesting.UploadFakeTools(c, env.Storage()) |
387 | |
388 | === modified file 'provider/local/environ.go' |
389 | --- provider/local/environ.go 2013-09-24 09:42:10 +0000 |
390 | +++ provider/local/environ.go 2013-09-26 12:28:55 +0000 |
391 | @@ -24,6 +24,7 @@ |
392 | "launchpad.net/juju-core/environs/filestorage" |
393 | "launchpad.net/juju-core/environs/httpstorage" |
394 | "launchpad.net/juju-core/environs/storage" |
395 | + coreerrors "launchpad.net/juju-core/errors" |
396 | "launchpad.net/juju-core/instance" |
397 | "launchpad.net/juju-core/juju/osenv" |
398 | "launchpad.net/juju-core/names" |
399 | @@ -97,6 +98,18 @@ |
400 | return nil |
401 | } |
402 | |
403 | +// PrecheckInstance is specified in the environs.Prechecker interface. |
404 | +func (*localEnviron) PrecheckInstance(series string, cons constraints.Value) error { |
405 | + return nil |
406 | +} |
407 | + |
408 | +// PrecheckContainer is specified in the environs.Prechecker interface. |
409 | +func (*localEnviron) PrecheckContainer(series string, kind instance.ContainerType) error { |
410 | + // This check can either go away or be relaxed when the local |
411 | + // provider can do nested containers. |
412 | + return coreerrors.NewContainersUnsupported(nil, "local provider does not support nested containers") |
413 | +} |
414 | + |
415 | // Bootstrap is specified in the Environ interface. |
416 | func (env *localEnviron) Bootstrap(cons constraints.Value, possibleTools tools.List, machineID string) error { |
417 | if !env.config.runningAsRoot { |
418 | |
419 | === modified file 'provider/local/environ_test.go' |
420 | --- provider/local/environ_test.go 2013-09-23 17:55:12 +0000 |
421 | +++ provider/local/environ_test.go 2013-09-26 12:28:55 +0000 |
422 | @@ -10,9 +10,13 @@ |
423 | |
424 | gc "launchpad.net/gocheck" |
425 | |
426 | + "launchpad.net/juju-core/constraints" |
427 | + "launchpad.net/juju-core/environs" |
428 | "launchpad.net/juju-core/environs/config" |
429 | "launchpad.net/juju-core/environs/jujutest" |
430 | + "launchpad.net/juju-core/instance" |
431 | "launchpad.net/juju-core/provider/local" |
432 | + jc "launchpad.net/juju-core/testing/checkers" |
433 | ) |
434 | |
435 | type environSuite struct { |
436 | @@ -42,6 +46,21 @@ |
437 | c.Assert(environ.PublicStorage(), gc.NotNil) |
438 | } |
439 | |
440 | +func (s *environSuite) TestPrecheck(c *gc.C) { |
441 | + testConfig := minimalConfig(c) |
442 | + environ, err := local.Provider.Open(testConfig) |
443 | + c.Assert(err, gc.IsNil) |
444 | + var cons constraints.Value |
445 | + prechecker, ok := environ.(environs.Prechecker) |
446 | + c.Assert(ok, jc.IsTrue) |
447 | + |
448 | + err = prechecker.PrecheckInstance("precise", cons) |
449 | + c.Check(err, gc.IsNil) |
450 | + |
451 | + err = prechecker.PrecheckContainer("precise", instance.LXC) |
452 | + c.Check(err, gc.ErrorMatches, "local provider does not support nested containers") |
453 | +} |
454 | + |
455 | type localJujuTestSuite struct { |
456 | baseProviderSuite |
457 | jujutest.Tests |
458 | |
459 | === modified file 'provider/maas/environ.go' |
460 | --- provider/maas/environ.go 2013-09-24 09:42:10 +0000 |
461 | +++ provider/maas/environ.go 2013-09-26 12:28:55 +0000 |
462 | @@ -70,6 +70,7 @@ |
463 | return env, nil |
464 | } |
465 | |
466 | +// Name is specified in the Environ interface. |
467 | func (env *maasEnviron) Name() string { |
468 | return env.name |
469 | } |
470 | |
471 | === modified file 'provider/openstack/local_test.go' |
472 | --- provider/openstack/local_test.go 2013-09-25 12:22:10 +0000 |
473 | +++ provider/openstack/local_test.go 2013-09-26 12:28:55 +0000 |
474 | @@ -244,6 +244,17 @@ |
475 | s.LoggingSuite.TearDownTest(c) |
476 | } |
477 | |
478 | +func (s *localServerSuite) TestPrecheck(c *gc.C) { |
479 | + var cons constraints.Value |
480 | + env := s.Prepare(c) |
481 | + prechecker, ok := env.(environs.Prechecker) |
482 | + c.Assert(ok, jc.IsTrue) |
483 | + err := prechecker.PrecheckInstance("precise", cons) |
484 | + c.Check(err, gc.IsNil) |
485 | + err = prechecker.PrecheckContainer("precise", instance.LXC) |
486 | + c.Check(err, gc.ErrorMatches, "openstack provider does not support containers") |
487 | +} |
488 | + |
489 | // If the bootstrap node is configured to require a public IP address, |
490 | // bootstrapping fails if an address cannot be allocated. |
491 | func (s *localServerSuite) TestBootstrapFailsWhenPublicIPError(c *gc.C) { |
492 | |
493 | === modified file 'provider/openstack/provider.go' |
494 | --- provider/openstack/provider.go 2013-09-26 07:47:47 +0000 |
495 | +++ provider/openstack/provider.go 2013-09-26 12:28:55 +0000 |
496 | @@ -30,6 +30,7 @@ |
497 | "launchpad.net/juju-core/environs/simplestreams" |
498 | "launchpad.net/juju-core/environs/storage" |
499 | envtools "launchpad.net/juju-core/environs/tools" |
500 | + coreerrors "launchpad.net/juju-core/errors" |
501 | "launchpad.net/juju-core/instance" |
502 | "launchpad.net/juju-core/names" |
503 | "launchpad.net/juju-core/provider" |
504 | @@ -395,6 +396,18 @@ |
505 | return nova |
506 | } |
507 | |
508 | +// PrecheckInstance is specified in the environs.Prechecker interface. |
509 | +func (*environ) PrecheckInstance(series string, cons constraints.Value) error { |
510 | + return nil |
511 | +} |
512 | + |
513 | +// PrecheckContainer is specified in the environs.Prechecker interface. |
514 | +func (*environ) PrecheckContainer(series string, kind instance.ContainerType) error { |
515 | + // This check can either go away or be relaxed when the openstack |
516 | + // provider manages container addressibility. |
517 | + return coreerrors.NewContainersUnsupported(nil, "openstack provider does not support containers") |
518 | +} |
519 | + |
520 | func (e *environ) Name() string { |
521 | return e.name |
522 | } |
Reviewers: mp+185015_ code.launchpad. net,
Message:
Please take a look.
Description: SanityCheckCons traints
all: implements Environ.
This introduces a new method on environs.Environ, traints. This method will check
SanityCheckCons
if constraints can possibly be met by the environment.
This will be used to check if an environment is
capable of creating an instance with the specified
constraints.
If the method returns nil, that does not necessarily
mean that the constraints are satisfied; if a non-nil
error is returned, then it means that the constraints
are definitely not satisfiable.
The ec2, azure and openstack Environ implementatons
will now disallow creating containers. When the work
to support addressability is done, this needs to be
changed. Currently, maas and local let everything
through.
cmd/juju is updated so that add-machine, and traints
deploy/add-unit --to call the new SanityCheckCons
method.
https:/ /code.launchpad .net/~axwalk/ juju-core/ sanity- check-constrain ts/+merge/ 185015
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/13632046/
Affected files (+166, -25 lines): addmachine. go addmachine_ test.go addunit_ test.go deploy_ test.go interface. go azure/environ. go azure/environ_ test.go dummy/environs. go ec2/local_ test.go local/environ. go maas/environ. go openstack/ local_test. go openstack/ provider. go
A [revision details]
M cmd/juju/
M cmd/juju/
M cmd/juju/
M cmd/juju/
M environs/
M juju/conn.go
M provider/
M provider/
M provider/
M provider/ec2/ec2.go
M provider/
M provider/
M provider/
M provider/
M provider/