Merge lp:~axwalk/juju-core/wire-up-prechecker-take2 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: 2344
Proposed branch: lp:~axwalk/juju-core/wire-up-prechecker-take2
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 1450 lines (+380/-224)
41 files modified
agent/agent.go (+4/-4)
agent/bootstrap.go (+3/-3)
agent/bootstrap_test.go (+6/-5)
cmd/jujud/agent.go (+2/-1)
cmd/jujud/agent_test.go (+3/-2)
cmd/jujud/bootstrap.go (+2/-1)
cmd/jujud/bootstrap_test.go (+8/-7)
cmd/plugins/juju-restore/restore.go (+1/-1)
environs/errors.go (+0/-24)
environs/interface.go (+4/-22)
environs/jujutest/livetests.go (+3/-13)
environs/statepolicy.go (+34/-0)
juju/conn.go (+2/-2)
juju/conn_test.go (+3/-3)
provider/azure/environ.go (+0/-12)
provider/azure/environ_test.go (+0/-9)
provider/dummy/environs.go (+18/-5)
provider/ec2/ec2.go (+0/-12)
provider/ec2/local_test.go (+0/-11)
provider/local/environ.go (+0/-12)
provider/local/environ_test.go (+0/-16)
provider/manual/environ.go (+4/-0)
provider/openstack/local_test.go (+0/-11)
provider/openstack/provider.go (+0/-12)
state/addmachine.go (+14/-0)
state/api/agent/machine_test.go (+2/-1)
state/apiserver/client/api_test.go (+2/-1)
state/compat_test.go (+1/-1)
state/conn_test.go (+16/-1)
state/environ_test.go (+1/-1)
state/export_test.go (+10/-2)
state/initialize_test.go (+8/-8)
state/megawatcher_internal_test.go (+1/-1)
state/open.go (+12/-6)
state/policy.go (+63/-0)
state/prechecker_test.go (+132/-0)
state/settings_test.go (+1/-1)
state/state.go (+1/-0)
state/state_test.go (+10/-10)
state/unit_test.go (+3/-3)
worker/provisioner/provisioner_test.go (+6/-0)
To merge this branch: bzr merge lp:~axwalk/juju-core/wire-up-prechecker-take2
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+205700@code.launchpad.net

Commit message

Wire up prechecker

We introduce the concept of a policy
into state: state.State is given a
state.Policy interface when opened,
which is consulted during certain
operations to validate or modify
behaviour.

The Policy interface contains only
one method, Prechecker, which is called
to obtain a Prechecker. The Prechecker,
if non-nil, is used to ensure that an
instance with the specified parameters
can potentially be instantiated.

There is currently only one implementation
of Prechecker, and that is the manual
provider's Environ. The manual provider
thus prevents add-machine without ssh-
placement.

https://codereview.appspot.com/61520045/

Description of the change

Wire up prechecker

We introduce the concept of a policy
into state: state.State is given a
state.Policy interface when opened,
which is consulted during certain
operations to validate or modify
behaviour.

The Policy interface contains only
one method, Prechecker, which is called
to obtain a Prechecker. The Prechecker,
if non-nil, is used to ensure that an
instance with the specified parameters
can potentially be instantiated.

There is currently only one implementation
of Prechecker, and that is the manual
provider's Environ. The manual provider
thus prevents add-machine without ssh-
placement.

This CL supersedes https://codereview.appspot.com/14032043/
The primary differences are:
 - up-to-date with latest codebase
 - introduction of Policy, and removal of
   raciness/lack of environ config updates
 - policy is not set on State in juju.NewConn
   methods; prechecking simply will not occur for
   old environments
 - Environ does not embed Prechecker, and there
   is no new EnvironBase struct; this may be
   proposed as a follow-up
 - there is no container prechecking, as otherwise
   we have no lightweight means of deploying charms
   that do not require addressability

https://codereview.appspot.com/61520045/

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

Reviewers: mp+205700_code.launchpad.net,

Message:
Please take a look.

Description:
Wire up prechecker

This change is to set a Prechecker on
the State object used by the apiserver
in cmd/jujud. The Environ itself is a
Prechecker, and that is what is assigned
to the State object.

State now calls the prechecker when
deciding to create instance/container
machine entries in state. The prechecker
calls are elided for machines with
instance IDs (i.e. "injected" machines).

All Environs, apart from MAAS, disallow
containers. The null provider disallows
everything (unless done "manually").

This CL supersedes https://codereview.appspot.com/14032043/
The primary differences are:
  - up-to-date with latest codebase
  - prechecker is not set on State in juju.NewConn
    methods; prechecking simply will not occur for
    old environments
  - Environ does not embed Prechecker, and there
    is no new EnvironBase struct; this will be
    proposed as a follow-up

The old CL stalled because previously an environment
config would be invalid at first startup of jujud,
because it would lack secrets. This has changed
with synchronous bootstrap and is no longer an issue.

https://code.launchpad.net/~axwalk/juju-core/wire-up-prechecker-take2/+merge/205700

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/61520045/

Affected files (+251, -34 lines):
   A [revision details]
   M cmd/jujud/agent.go
   M cmd/jujud/machine.go
   M environs/interface.go
   M environs/jujutest/livetests.go
   M provider/azure/environ.go
   M provider/ec2/ec2.go
   M provider/ec2/local_test.go
   M provider/local/environ.go
   M provider/local/environ_test.go
   M provider/manual/environ.go
   M provider/openstack/local_test.go
   M provider/openstack/provider.go
   M state/addmachine.go
   A state/prechecker_test.go
   M state/state.go

Revision history for this message
William Reade (fwereade) wrote :

On 2014/02/11 06:26:20, axw wrote:
> Please take a look.

Couple of preliminary comments: I'm not actually sure it's correct to
disable containers anywhere, because they still have value as proxy
charms: kapil in particular doesn't want to waste an instance on an ELB
charm, and hulk-smash is a pretty weak alternative.

There's definitely value in prechecking environ instances though.

https://codereview.appspot.com/61520045/

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

On 2014/02/14 09:28:53, fwereade wrote:
> On 2014/02/11 06:26:20, axw wrote:
> > Please take a look.

> Couple of preliminary comments: I'm not actually sure it's correct to
disable
> containers anywhere, because they still have value as proxy charms:
kapil in
> particular doesn't want to waste an instance on an ELB charm, and
hulk-smash is
> a pretty weak alternative.

Agreed that hulk smash is a crappy alternative, and I know the use case
is there; I was hoping to address it when the time came (this is all
server side).

It doesn't seem ideal to allow containers for everyone, letting users
shoot themselves, to enable ELB, proxies, whatever. It'd be nice to have
something inside the charm that indicates what it requires (or doesn't
require?) so that we know to let those special cases through. OTOH, are
users likely to "add-machine lxc" if they don't know what they're doing?

In any case, there's work to be done. I can change it to allow
containers to begin with.

> There's definitely value in prechecking environ instances though.

https://codereview.appspot.com/61520045/

Revision history for this message
William Reade (fwereade) wrote :

On 2014/02/14 09:39:44, axw wrote:
> It doesn't seem ideal to allow containers for everyone, letting users
shoot
> themselves, to enable ELB, proxies, whatever. It'd be nice to have
something
> inside the charm that indicates what it requires (or doesn't require?)
so that
> we know to let those special cases through. OTOH, are users likely to
> "add-machine lxc" if they don't know what they're doing?

Yeah, I think a charm flag (i-dont-need-an-address? ;p) is probably the
best approach. Once we have one of those we can do it all internally,
and expose a cmdline flag for add-machine, for those users who really do
know what they're doing.

> In any case, there's work to be done. I can change it to allow
containers to
> begin with.

Ideal, thanks.

> > There's definitely value in prechecking environ instances though.

https://codereview.appspot.com/61520045/

Revision history for this message
William Reade (fwereade) wrote :

possible approach question, please discuss with waigani:

the issue with setting a prechecker, even if it's watched, is raciness.
I was willing to handwave it a bit before, but now we have waigani's use
case which requires that we get *less* racy wrt environ config... how
about setting not a prechecker, but something like a getter: eg
`func(map[string]interface{}) (Prechecker, error)`? This is irrelevant
for your purposes really [0] but critically important for waigani's --
and the only difference is in the type that needs to be returned
(whether it has SetConfig or Precheck). That'd need a bit of finesse but
it'd let you use basically the same implementation for basically the
same task -- *and* mean that you don't need to do the watching followup
(at the cost of an extra db hits when prechecking -- worth it, I think).

*and* we wouldn't need to mess around with locks in state :).

[0] you *could* use it to assert that the environ hasn't changed since
you checked validity of the upcoming op, but the value of that is
somewhat limited I think

https://codereview.appspot.com/61520045/

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

On 2014/02/17 10:00:17, fwereade wrote:
> possible approach question, please discuss with waigani:

> the issue with setting a prechecker, even if it's watched, is
raciness. I was
> willing to handwave it a bit before, but now we have waigani's use
case which
> requires that we get *less* racy wrt environ config... how about
setting not a
> prechecker, but something like a getter: eg
`func(map[string]interface{})
> (Prechecker, error)`? This is irrelevant for your purposes really [0]
but
> critically important for waigani's -- and the only difference is in
the type
> that needs to be returned (whether it has SetConfig or Precheck).
That'd need a
> bit of finesse but it'd let you use basically the same implementation
for
> basically the same task -- *and* mean that you don't need to do the
watching
> followup (at the cost of an extra db hits when prechecking -- worth
it, I
> think).

I might just create one getter type that can do Precheck or Validate,
but otherwise I like this alternative. I'll look into implementing this,
and discuss with waigani in our standup tomorrow.

> *and* we wouldn't need to mess around with locks in state :).

> [0] you *could* use it to assert that the environ hasn't changed since
you
> checked validity of the upcoming op, but the value of that is somewhat
limited I
> think

Yeah, I don't think we need to go that far.

https://codereview.appspot.com/61520045/

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

Looking very nice, basically just quibbles. And I'm not quite clear
about the distinction between a nil policy and a PolicyBase{} -- what
exactly governs which you use in a given situation?

https://codereview.appspot.com/61520045/diff/20001/cmd/jujud/bootstrap_test.go
File cmd/jujud/bootstrap_test.go (right):

https://codereview.appspot.com/61520045/diff/20001/cmd/jujud/bootstrap_test.go#newcode121
cmd/jujud/bootstrap_test.go:121: }, state.DefaultDialOpts(),
state.Policy(nil))
I'm wondering whether it might be good to have a package that implements
environStatePolicy, and includes a state.Open wrapper that always passes
the environ policy in. I'm not sure I can see a use case for alternative
policies at the moment, and if one does show up I think it'll be pretty
easy to add.

As it is I feel it's a bit icky having the nil policies everywhere -- I
think we probably want all our states to work the same in-test and out.
Open to counterarguments though.

https://codereview.appspot.com/61520045/diff/20001/cmd/jujud/statepolicy.go
File cmd/jujud/statepolicy.go (right):

https://codereview.appspot.com/61520045/diff/20001/cmd/jujud/statepolicy.go#newcode25
cmd/jujud/statepolicy.go:25: return p, nil
I don't really like the nil-return-without-error. Can we have a specific
sort of NotImplemented error that we handle explicitly in
precheckInstance, please?

https://codereview.appspot.com/61520045/diff/20001/juju/conn.go
File juju/conn.go (right):

https://codereview.appspot.com/61520045/diff/20001/juju/conn.go#newcode57
juju/conn.go:57: policy := state.PolicyBase{}
This bit doesn't have to use the mooted wrapper, ofc.

*but* I don't quite understand what the benefit is of having a null
policy here. What breaks down if we use a real one?

https://codereview.appspot.com/61520045/diff/20001/state/policy.go
File state/policy.go (right):

https://codereview.appspot.com/61520045/diff/20001/state/policy.go#newcode17
state/policy.go:17: // for any of the interfaces, but should only return
I think you a word.

https://codereview.appspot.com/61520045/diff/20001/state/policy.go#newcode43
state/policy.go:43: return nil, nil
yeah, `nil, nil` scares me. Please change it :).

https://codereview.appspot.com/61520045/

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

Please take a look.

https://codereview.appspot.com/61520045/diff/20001/cmd/jujud/bootstrap_test.go
File cmd/jujud/bootstrap_test.go (right):

https://codereview.appspot.com/61520045/diff/20001/cmd/jujud/bootstrap_test.go#newcode121
cmd/jujud/bootstrap_test.go:121: }, state.DefaultDialOpts(),
state.Policy(nil))
On 2014/02/18 15:30:18, fwereade wrote:
> I'm wondering whether it might be good to have a package that
implements
> environStatePolicy, and includes a state.Open wrapper that always
passes the
> environ policy in. I'm not sure I can see a use case for alternative
policies at
> the moment, and if one does show up I think it'll be pretty easy to
add.

In any case, juju-core/agent can't use it directly. The policy
implementation requires environs, which depends on agent (which seems a
bit unfortunate.)

I'll just move the policy implementation into environs. The wrappers
aren't really all that useful since they can't be used by both
production and testing code.

> As it is I feel it's a bit icky having the nil policies everywhere --
I think we
> probably want all our states to work the same in-test and out. Open to
> counterarguments though.

No problems. I'd prefer to keep the ones in juju-core/state
self-contained though.

https://codereview.appspot.com/61520045/diff/20001/cmd/jujud/statepolicy.go
File cmd/jujud/statepolicy.go (right):

https://codereview.appspot.com/61520045/diff/20001/cmd/jujud/statepolicy.go#newcode25
cmd/jujud/statepolicy.go:25: return p, nil
On 2014/02/18 15:30:18, fwereade wrote:
> I don't really like the nil-return-without-error. Can we have a
specific sort of
> NotImplemented error that we handle explicitly in precheckInstance,
please?

Done.

https://codereview.appspot.com/61520045/diff/20001/juju/conn.go
File juju/conn.go (right):

https://codereview.appspot.com/61520045/diff/20001/juju/conn.go#newcode57
juju/conn.go:57: policy := state.PolicyBase{}
On 2014/02/18 15:30:18, fwereade wrote:
> This bit doesn't have to use the mooted wrapper, ofc.

> *but* I don't quite understand what the benefit is of having a null
policy here.
> What breaks down if we use a real one?

Nothing actually, this is basically a holdover from when Environ had to
be updated.

I'll move the policy implementation to its own package as suggested and
update this.

https://codereview.appspot.com/61520045/diff/20001/state/policy.go
File state/policy.go (right):

https://codereview.appspot.com/61520045/diff/20001/state/policy.go#newcode17
state/policy.go:17: // for any of the interfaces, but should only return
On 2014/02/18 15:30:18, fwereade wrote:
> I think you a word.

Done.

https://codereview.appspot.com/61520045/diff/20001/state/policy.go#newcode43
state/policy.go:43: return nil, nil
On 2014/02/18 15:30:18, fwereade wrote:
> yeah, `nil, nil` scares me. Please change it :).

Done. Must return an error that satisfies errors.IsNotImplementedError.

https://codereview.appspot.com/61520045/

Revision history for this message
William Reade (fwereade) wrote :

LGTM, thanks

https://codereview.appspot.com/61520045/diff/20001/cmd/jujud/bootstrap_test.go
File cmd/jujud/bootstrap_test.go (right):

https://codereview.appspot.com/61520045/diff/20001/cmd/jujud/bootstrap_test.go#newcode121
cmd/jujud/bootstrap_test.go:121: }, state.DefaultDialOpts(),
state.Policy(nil))
On 2014/02/19 07:08:45, axw wrote:
> In any case, juju-core/agent can't use it directly. The policy
implementation
> requires environs, which depends on agent (which seems a bit
unfortunate.)

Grar. Navigate the twisty passages as you must, then. Cheers.

> No problems. I'd prefer to keep the ones in juju-core/state
self-contained
> though.

SGTM

https://codereview.appspot.com/61520045/diff/40001/state/policy.go
File state/policy.go (right):

https://codereview.appspot.com/61520045/diff/40001/state/policy.go#newcode58
state/policy.go:58: logger.Debugf("policy returned nil prechecker,
ignoring")
I think this is really bad behaviour on the policy's part; probably bad
enough to justify erroring out of the AddMachine, because the
implementation is crazy enough that all bets should be considered to be
off.

https://codereview.appspot.com/61520045/

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

Please take a look.

https://codereview.appspot.com/61520045/diff/40001/state/policy.go
File state/policy.go (right):

https://codereview.appspot.com/61520045/diff/40001/state/policy.go#newcode58
state/policy.go:58: logger.Debugf("policy returned nil prechecker,
ignoring")
On 2014/02/19 13:01:39, fwereade wrote:
> I think this is really bad behaviour on the policy's part; probably
bad enough
> to justify erroring out of the AddMachine, because the implementation
is crazy
> enough that all bets should be considered to be off.

Done.

https://codereview.appspot.com/61520045/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'agent/agent.go'
--- agent/agent.go 2014-02-05 08:01:22 +0000
+++ agent/agent.go 2014-02-20 08:24:11 +0000
@@ -75,7 +75,7 @@
7575
76 // OpenState tries to open a direct connection to the state database using76 // OpenState tries to open a direct connection to the state database using
77 // the given Conf.77 // the given Conf.
78 OpenState() (*state.State, error)78 OpenState(policy state.Policy) (*state.State, error)
7979
80 // Write writes the agent configuration.80 // Write writes the agent configuration.
81 Write() error81 Write() error
@@ -429,7 +429,7 @@
429 return st, password, nil429 return st, password, nil
430}430}
431431
432func (c *configInternal) OpenState() (*state.State, error) {432func (c *configInternal) OpenState(policy state.Policy) (*state.State, error) {
433 info := state.Info{433 info := state.Info{
434 Addrs: c.stateDetails.addresses,434 Addrs: c.stateDetails.addresses,
435 Password: c.stateDetails.password,435 Password: c.stateDetails.password,
@@ -437,7 +437,7 @@
437 Tag: c.tag,437 Tag: c.tag,
438 }438 }
439 if info.Password != "" {439 if info.Password != "" {
440 st, err := state.Open(&info, state.DefaultDialOpts())440 st, err := state.Open(&info, state.DefaultDialOpts(), policy)
441 if err == nil {441 if err == nil {
442 return st, nil442 return st, nil
443 }443 }
@@ -448,5 +448,5 @@
448 }448 }
449 }449 }
450 info.Password = c.oldPassword450 info.Password = c.oldPassword
451 return state.Open(&info, state.DefaultDialOpts())451 return state.Open(&info, state.DefaultDialOpts(), policy)
452}452}
453453
=== modified file 'agent/bootstrap.go'
--- agent/bootstrap.go 2014-02-05 08:01:22 +0000
+++ agent/bootstrap.go 2014-02-20 08:24:11 +0000
@@ -29,7 +29,7 @@
29// InitializeState returns the newly initialized state and bootstrap29// InitializeState returns the newly initialized state and bootstrap
30// machine. If it fails, the state may well be irredeemably compromised.30// machine. If it fails, the state may well be irredeemably compromised.
31type StateInitializer interface {31type StateInitializer interface {
32 InitializeState(envCfg *config.Config, machineCfg BootstrapMachineConfig, timeout state.DialOpts) (*state.State, *state.Machine, error)32 InitializeState(envCfg *config.Config, machineCfg BootstrapMachineConfig, timeout state.DialOpts, policy state.Policy) (*state.State, *state.Machine, error)
33}33}
3434
35// MarshalBootstrapJobs may be used to marshal a set of35// MarshalBootstrapJobs may be used to marshal a set of
@@ -67,7 +67,7 @@
6767
68const bootstrapMachineId = "0"68const bootstrapMachineId = "0"
6969
70func (c *configInternal) InitializeState(envCfg *config.Config, machineCfg BootstrapMachineConfig, timeout state.DialOpts) (*state.State, *state.Machine, error) {70func (c *configInternal) InitializeState(envCfg *config.Config, machineCfg BootstrapMachineConfig, timeout state.DialOpts, policy state.Policy) (*state.State, *state.Machine, error) {
71 if c.Tag() != names.MachineTag(bootstrapMachineId) {71 if c.Tag() != names.MachineTag(bootstrapMachineId) {
72 return nil, nil, fmt.Errorf("InitializeState not called with bootstrap machine's configuration")72 return nil, nil, fmt.Errorf("InitializeState not called with bootstrap machine's configuration")
73 }73 }
@@ -76,7 +76,7 @@
76 CACert: c.caCert,76 CACert: c.caCert,
77 }77 }
78 logger.Debugf("initializing address %v", info.Addrs)78 logger.Debugf("initializing address %v", info.Addrs)
79 st, err := state.Initialize(&info, envCfg, timeout)79 st, err := state.Initialize(&info, envCfg, timeout, policy)
80 if err != nil {80 if err != nil {
81 return nil, nil, fmt.Errorf("failed to initialize state: %v", err)81 return nil, nil, fmt.Errorf("failed to initialize state: %v", err)
82 }82 }
8383
=== modified file 'agent/bootstrap_test.go'
--- agent/bootstrap_test.go 2014-02-05 08:01:22 +0000
+++ agent/bootstrap_test.go 2014-02-20 08:24:11 +0000
@@ -8,6 +8,7 @@
88
9 "launchpad.net/juju-core/agent"9 "launchpad.net/juju-core/agent"
10 "launchpad.net/juju-core/constraints"10 "launchpad.net/juju-core/constraints"
11 "launchpad.net/juju-core/environs"
11 "launchpad.net/juju-core/environs/config"12 "launchpad.net/juju-core/environs/config"
12 "launchpad.net/juju-core/instance"13 "launchpad.net/juju-core/instance"
13 "launchpad.net/juju-core/state"14 "launchpad.net/juju-core/state"
@@ -71,7 +72,7 @@
71 envCfg, err := config.New(config.NoDefaults, envAttrs)72 envCfg, err := config.New(config.NoDefaults, envAttrs)
72 c.Assert(err, gc.IsNil)73 c.Assert(err, gc.IsNil)
7374
74 st, m, err := cfg.InitializeState(envCfg, mcfg, state.DialOpts{})75 st, m, err := cfg.InitializeState(envCfg, mcfg, state.DialOpts{}, environs.NewStatePolicy())
75 c.Assert(err, gc.IsNil)76 c.Assert(err, gc.IsNil)
76 defer st.Close()77 defer st.Close()
7778
@@ -106,7 +107,7 @@
106 c.Assert(newCfg.Tag(), gc.Equals, "machine-0")107 c.Assert(newCfg.Tag(), gc.Equals, "machine-0")
107 c.Assert(agent.Password(newCfg), gc.Not(gc.Equals), pwHash)108 c.Assert(agent.Password(newCfg), gc.Not(gc.Equals), pwHash)
108 c.Assert(agent.Password(newCfg), gc.Not(gc.Equals), testing.DefaultMongoPassword)109 c.Assert(agent.Password(newCfg), gc.Not(gc.Equals), testing.DefaultMongoPassword)
109 st1, err := cfg.OpenState()110 st1, err := cfg.OpenState(environs.NewStatePolicy())
110 c.Assert(err, gc.IsNil)111 c.Assert(err, gc.IsNil)
111 defer st1.Close()112 defer st1.Close()
112}113}
@@ -136,13 +137,13 @@
136 envCfg, err := config.New(config.NoDefaults, envAttrs)137 envCfg, err := config.New(config.NoDefaults, envAttrs)
137 c.Assert(err, gc.IsNil)138 c.Assert(err, gc.IsNil)
138139
139 st, _, err := cfg.InitializeState(envCfg, mcfg, state.DialOpts{})140 st, _, err := cfg.InitializeState(envCfg, mcfg, state.DialOpts{}, environs.NewStatePolicy())
140 c.Assert(err, gc.IsNil)141 c.Assert(err, gc.IsNil)
141 err = st.SetAdminMongoPassword("")142 err = st.SetAdminMongoPassword("")
142 c.Check(err, gc.IsNil)143 c.Check(err, gc.IsNil)
143 st.Close()144 st.Close()
144145
145 st, _, err = cfg.InitializeState(envCfg, mcfg, state.DialOpts{})146 st, _, err = cfg.InitializeState(envCfg, mcfg, state.DialOpts{}, environs.NewStatePolicy())
146 if err == nil {147 if err == nil {
147 st.Close()148 st.Close()
148 }149 }
@@ -156,7 +157,7 @@
156 Tag: "",157 Tag: "",
157 Password: password,158 Password: password,
158 }159 }
159 st, err := state.Open(info, state.DialOpts{})160 st, err := state.Open(info, state.DialOpts{}, environs.NewStatePolicy())
160 c.Assert(err, gc.IsNil)161 c.Assert(err, gc.IsNil)
161 defer st.Close()162 defer st.Close()
162 _, err = st.Machine("0")163 _, err = st.Machine("0")
163164
=== modified file 'cmd/jujud/agent.go'
--- cmd/jujud/agent.go 2014-01-28 14:31:58 +0000
+++ cmd/jujud/agent.go 2014-02-20 08:24:11 +0000
@@ -12,6 +12,7 @@
1212
13 "launchpad.net/juju-core/agent"13 "launchpad.net/juju-core/agent"
14 "launchpad.net/juju-core/cmd"14 "launchpad.net/juju-core/cmd"
15 "launchpad.net/juju-core/environs"
15 "launchpad.net/juju-core/errors"16 "launchpad.net/juju-core/errors"
16 "launchpad.net/juju-core/state"17 "launchpad.net/juju-core/state"
17 "launchpad.net/juju-core/state/api"18 "launchpad.net/juju-core/state/api"
@@ -146,7 +147,7 @@
146}147}
147148
148func openState(agentConfig agent.Config, a Agent) (*state.State, AgentState, error) {149func openState(agentConfig agent.Config, a Agent) (*state.State, AgentState, error) {
149 st, err := agentConfig.OpenState()150 st, err := agentConfig.OpenState(environs.NewStatePolicy())
150 if err != nil {151 if err != nil {
151 return nil, nil, err152 return nil, nil, err
152 }153 }
153154
=== modified file 'cmd/jujud/agent_test.go'
--- cmd/jujud/agent_test.go 2014-02-19 02:25:30 +0000
+++ cmd/jujud/agent_test.go 2014-02-20 08:24:11 +0000
@@ -13,6 +13,7 @@
13 "launchpad.net/juju-core/agent"13 "launchpad.net/juju-core/agent"
14 agenttools "launchpad.net/juju-core/agent/tools"14 agenttools "launchpad.net/juju-core/agent/tools"
15 "launchpad.net/juju-core/cmd"15 "launchpad.net/juju-core/cmd"
16 "launchpad.net/juju-core/environs"
16 envtesting "launchpad.net/juju-core/environs/testing"17 envtesting "launchpad.net/juju-core/environs/testing"
17 envtools "launchpad.net/juju-core/environs/tools"18 envtools "launchpad.net/juju-core/environs/tools"
18 "launchpad.net/juju-core/juju/testing"19 "launchpad.net/juju-core/juju/testing"
@@ -323,7 +324,7 @@
323func (s *agentSuite) assertCanOpenState(c *gc.C, tag, dataDir string) {324func (s *agentSuite) assertCanOpenState(c *gc.C, tag, dataDir string) {
324 config, err := agent.ReadConf(dataDir, tag)325 config, err := agent.ReadConf(dataDir, tag)
325 c.Assert(err, gc.IsNil)326 c.Assert(err, gc.IsNil)
326 st, err := config.OpenState()327 st, err := config.OpenState(environs.NewStatePolicy())
327 c.Assert(err, gc.IsNil)328 c.Assert(err, gc.IsNil)
328 st.Close()329 st.Close()
329}330}
@@ -331,7 +332,7 @@
331func (s *agentSuite) assertCannotOpenState(c *gc.C, tag, dataDir string) {332func (s *agentSuite) assertCannotOpenState(c *gc.C, tag, dataDir string) {
332 config, err := agent.ReadConf(dataDir, tag)333 config, err := agent.ReadConf(dataDir, tag)
333 c.Assert(err, gc.IsNil)334 c.Assert(err, gc.IsNil)
334 _, err = config.OpenState()335 _, err = config.OpenState(environs.NewStatePolicy())
335 expectErr := fmt.Sprintf("cannot log in to juju database as %q: unauthorized mongo access: auth fails", tag)336 expectErr := fmt.Sprintf("cannot log in to juju database as %q: unauthorized mongo access: auth fails", tag)
336 c.Assert(err, gc.ErrorMatches, expectErr)337 c.Assert(err, gc.ErrorMatches, expectErr)
337}338}
338339
=== modified file 'cmd/jujud/bootstrap.go'
--- cmd/jujud/bootstrap.go 2014-02-05 08:01:22 +0000
+++ cmd/jujud/bootstrap.go 2014-02-20 08:24:11 +0000
@@ -15,6 +15,7 @@
15 "launchpad.net/juju-core/agent"15 "launchpad.net/juju-core/agent"
16 "launchpad.net/juju-core/cmd"16 "launchpad.net/juju-core/cmd"
17 "launchpad.net/juju-core/constraints"17 "launchpad.net/juju-core/constraints"
18 "launchpad.net/juju-core/environs"
18 "launchpad.net/juju-core/environs/bootstrap"19 "launchpad.net/juju-core/environs/bootstrap"
19 "launchpad.net/juju-core/environs/cloudinit"20 "launchpad.net/juju-core/environs/cloudinit"
20 "launchpad.net/juju-core/environs/config"21 "launchpad.net/juju-core/environs/config"
@@ -96,7 +97,7 @@
96 Jobs: jobs,97 Jobs: jobs,
97 InstanceId: bsState.StateInstances[0],98 InstanceId: bsState.StateInstances[0],
98 Characteristics: characteristics,99 Characteristics: characteristics,
99 }, state.DefaultDialOpts())100 }, state.DefaultDialOpts(), environs.NewStatePolicy())
100 if err != nil {101 if err != nil {
101 return err102 return err
102 }103 }
103104
=== modified file 'cmd/jujud/bootstrap_test.go'
--- cmd/jujud/bootstrap_test.go 2014-02-05 08:01:22 +0000
+++ cmd/jujud/bootstrap_test.go 2014-02-20 08:24:11 +0000
@@ -13,6 +13,7 @@
1313
14 "launchpad.net/juju-core/agent"14 "launchpad.net/juju-core/agent"
15 "launchpad.net/juju-core/constraints"15 "launchpad.net/juju-core/constraints"
16 "launchpad.net/juju-core/environs"
16 "launchpad.net/juju-core/environs/bootstrap"17 "launchpad.net/juju-core/environs/bootstrap"
17 "launchpad.net/juju-core/environs/jujutest"18 "launchpad.net/juju-core/environs/jujutest"
18 "launchpad.net/juju-core/errors"19 "launchpad.net/juju-core/errors"
@@ -118,7 +119,7 @@
118 Addrs: []string{testing.MgoServer.Addr()},119 Addrs: []string{testing.MgoServer.Addr()},
119 CACert: []byte(testing.CACert),120 CACert: []byte(testing.CACert),
120 Password: testPasswordHash(),121 Password: testPasswordHash(),
121 }, state.DefaultDialOpts())122 }, state.DefaultDialOpts(), environs.NewStatePolicy())
122 c.Assert(err, gc.IsNil)123 c.Assert(err, gc.IsNil)
123 defer st.Close()124 defer st.Close()
124 machines, err := st.AllMachines()125 machines, err := st.AllMachines()
@@ -145,7 +146,7 @@
145 Addrs: []string{testing.MgoServer.Addr()},146 Addrs: []string{testing.MgoServer.Addr()},
146 CACert: []byte(testing.CACert),147 CACert: []byte(testing.CACert),
147 Password: testPasswordHash(),148 Password: testPasswordHash(),
148 }, state.DefaultDialOpts())149 }, state.DefaultDialOpts(), environs.NewStatePolicy())
149 c.Assert(err, gc.IsNil)150 c.Assert(err, gc.IsNil)
150 defer st.Close()151 defer st.Close()
151 cons, err := st.EnvironConstraints()152 cons, err := st.EnvironConstraints()
@@ -174,7 +175,7 @@
174 Addrs: []string{testing.MgoServer.Addr()},175 Addrs: []string{testing.MgoServer.Addr()},
175 CACert: []byte(testing.CACert),176 CACert: []byte(testing.CACert),
176 Password: testPasswordHash(),177 Password: testPasswordHash(),
177 }, state.DefaultDialOpts())178 }, state.DefaultDialOpts(), environs.NewStatePolicy())
178 c.Assert(err, gc.IsNil)179 c.Assert(err, gc.IsNil)
179 defer st.Close()180 defer st.Close()
180 m, err := st.Machine("0")181 m, err := st.Machine("0")
@@ -199,7 +200,7 @@
199 Addrs: []string{testing.MgoServer.Addr()},200 Addrs: []string{testing.MgoServer.Addr()},
200 CACert: []byte(testing.CACert),201 CACert: []byte(testing.CACert),
201 Password: testPasswordHash(),202 Password: testPasswordHash(),
202 }, state.DefaultDialOpts())203 }, state.DefaultDialOpts(), environs.NewStatePolicy())
203 c.Assert(err, gc.IsNil)204 c.Assert(err, gc.IsNil)
204 defer st.Close()205 defer st.Close()
205 m, err := st.Machine("0")206 m, err := st.Machine("0")
@@ -208,7 +209,7 @@
208}209}
209210
210func testOpenState(c *gc.C, info *state.Info, expectErrType error) {211func testOpenState(c *gc.C, info *state.Info, expectErrType error) {
211 st, err := state.Open(info, state.DefaultDialOpts())212 st, err := state.Open(info, state.DefaultDialOpts(), environs.NewStatePolicy())
212 if st != nil {213 if st != nil {
213 st.Close()214 st.Close()
214 }215 }
@@ -236,7 +237,7 @@
236237
237 // Check we can log in to mongo as admin.238 // Check we can log in to mongo as admin.
238 info.Tag, info.Password = "", testPasswordHash()239 info.Tag, info.Password = "", testPasswordHash()
239 st, err := state.Open(info, state.DefaultDialOpts())240 st, err := state.Open(info, state.DefaultDialOpts(), environs.NewStatePolicy())
240 c.Assert(err, gc.IsNil)241 c.Assert(err, gc.IsNil)
241 // Reset password so the tests can continue to use the same server.242 // Reset password so the tests can continue to use the same server.
242 defer st.Close()243 defer st.Close()
@@ -254,7 +255,7 @@
254 machineConf1, err := agent.ReadConf(machineConf.DataDir(), "machine-0")255 machineConf1, err := agent.ReadConf(machineConf.DataDir(), "machine-0")
255 c.Assert(err, gc.IsNil)256 c.Assert(err, gc.IsNil)
256257
257 st, err = machineConf1.OpenState()258 st, err = machineConf1.OpenState(environs.NewStatePolicy())
258 c.Assert(err, gc.IsNil)259 c.Assert(err, gc.IsNil)
259 defer st.Close()260 defer st.Close()
260}261}
261262
=== modified file 'cmd/plugins/juju-restore/restore.go'
--- cmd/plugins/juju-restore/restore.go 2014-02-13 02:46:58 +0000
+++ cmd/plugins/juju-restore/restore.go 2014-02-20 08:24:11 +0000
@@ -187,7 +187,7 @@
187 CACert: caCert,187 CACert: caCert,
188 Tag: creds.Tag,188 Tag: creds.Tag,
189 Password: creds.Password,189 Password: creds.Password,
190 }, state.DefaultDialOpts())190 }, state.DefaultDialOpts(), environs.NewStatePolicy())
191 if err != nil {191 if err != nil {
192 return fmt.Errorf("cannot open state: %v", err)192 return fmt.Errorf("cannot open state: %v", err)
193 }193 }
194194
=== modified file 'environs/errors.go'
--- environs/errors.go 2013-09-27 08:14:36 +0000
+++ environs/errors.go 2014-02-20 08:24:11 +0000
@@ -12,27 +12,3 @@
12 ErrNoInstances = errors.New("no instances found")12 ErrNoInstances = errors.New("no instances found")
13 ErrPartialInstances = errors.New("only some instances were found")13 ErrPartialInstances = errors.New("only some instances were found")
14)14)
15
16// containersUnsupportedError indicates that the environment does not support
17// creation of containers.
18type containersUnsupportedError struct {
19 msg string
20}
21
22func (e *containersUnsupportedError) Error() string {
23 return e.msg
24}
25
26// IsContainersUnsupportedError reports whether the error
27// was created by NewContainersUnsupportedError.
28func IsContainersUnsupportedError(err error) bool {
29 _, ok := err.(*containersUnsupportedError)
30 return ok
31}
32
33// NewContainersUnsupportedError returns a new error
34// which satisfies IsContainersUnsupported and reports
35// the given message.
36func NewContainersUnsupported(msg string) error {
37 return &containersUnsupportedError{msg: msg}
38}
3915
=== modified file 'environs/interface.go'
--- environs/interface.go 2014-02-13 03:16:09 +0000
+++ environs/interface.go 2014-02-20 08:24:11 +0000
@@ -71,28 +71,6 @@
71 Config() *config.Config71 Config() *config.Config
72}72}
7373
74// Prechecker is an optional interface that an Environ may implement,
75// in order to support pre-flight checking of instance/container creation.
76//
77// Prechecker's methods are best effort, and not guaranteed to eliminate
78// all invalid parameters. If a precheck method returns nil, it is not
79// guaranteed that the constraints are valid; if a non-nil error is
80// returned, then the constraints are definitely invalid.
81type Prechecker interface {
82 // PrecheckInstance performs a preflight check on the specified
83 // series and constraints, ensuring that they are possibly valid for
84 // creating an instance in this environment.
85 PrecheckInstance(series string, cons constraints.Value) error
86
87 // PrecheckContainer performs a preflight check on the container type,
88 // ensuring that the environment is possibly capable of creating a
89 // container of the specified type and series.
90 //
91 // The container type must be a valid ContainerType as specified
92 // in the instance package, and != instance.NONE.
93 PrecheckContainer(series string, kind instance.ContainerType) error
94}
95
96// An Environ represents a juju environment as specified74// An Environ represents a juju environment as specified
97// in the environments.yaml file.75// in the environments.yaml file.
98//76//
@@ -180,6 +158,10 @@
180158
181 // Provider returns the EnvironProvider that created this Environ.159 // Provider returns the EnvironProvider that created this Environ.
182 Provider() EnvironProvider160 Provider() EnvironProvider
161
162 // TODO(axw) 2014-02-11 #pending-review
163 // Embed state.Prechecker, and introduce an EnvironBase
164 // that embeds a no-op prechecker implementation.
183}165}
184166
185// BootstrapContext is an interface that is passed to167// BootstrapContext is an interface that is passed to
186168
=== modified file 'environs/jujutest/livetests.go'
--- environs/jujutest/livetests.go 2014-02-13 02:46:58 +0000
+++ environs/jujutest/livetests.go 2014-02-20 08:24:11 +0000
@@ -159,22 +159,12 @@
159func (t *LiveTests) TestPrechecker(c *gc.C) {159func (t *LiveTests) TestPrechecker(c *gc.C) {
160 // Providers may implement Prechecker. If they do, then they should160 // Providers may implement Prechecker. If they do, then they should
161 // return nil for empty constraints (excluding the null provider).161 // return nil for empty constraints (excluding the null provider).
162 prechecker, ok := t.Env.(environs.Prechecker)162 prechecker, ok := t.Env.(state.Prechecker)
163 if !ok {163 if !ok {
164 return164 return
165 }165 }
166166 err := prechecker.PrecheckInstance("precise", constraints.Value{})
167 const series = "precise"167 c.Assert(err, gc.IsNil)
168 var cons constraints.Value
169 c.Check(prechecker.PrecheckInstance(series, cons), gc.IsNil)
170
171 err := prechecker.PrecheckContainer(series, instance.LXC)
172 // If err is nil, that is fine, some providers support containers.
173 if err != nil {
174 // But for ones that don't, they should have a standard error format.
175 c.Check(err, gc.ErrorMatches, ".*provider does not support .*containers")
176 c.Check(err, jc.Satisfies, environs.IsContainersUnsupportedError)
177 }
178}168}
179169
180// TestStartStop is similar to Tests.TestStartStop except170// TestStartStop is similar to Tests.TestStartStop except
181171
=== added file 'environs/statepolicy.go'
--- environs/statepolicy.go 1970-01-01 00:00:00 +0000
+++ environs/statepolicy.go 2014-02-20 08:24:11 +0000
@@ -0,0 +1,34 @@
1// Copyright 2014 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package environs
5
6import (
7 "launchpad.net/juju-core/environs/config"
8 "launchpad.net/juju-core/errors"
9 "launchpad.net/juju-core/state"
10)
11
12// environStatePolicy implements state.Policy in
13// terms of environs.Environ and related types.
14type environStatePolicy struct{}
15
16var _ state.Policy = environStatePolicy{}
17
18// NewStatePolicy returns a state.Policy that is
19// implemented in terms of Environ and related
20// types.
21func NewStatePolicy() state.Policy {
22 return environStatePolicy{}
23}
24
25func (environStatePolicy) Prechecker(cfg *config.Config) (state.Prechecker, error) {
26 env, err := New(cfg)
27 if err != nil {
28 return nil, err
29 }
30 if p, ok := env.(state.Prechecker); ok {
31 return p, nil
32 }
33 return nil, errors.NewNotImplementedError("Prechecker")
34}
035
=== modified file 'juju/conn.go'
--- juju/conn.go 2014-01-28 17:07:55 +0000
+++ juju/conn.go 2014-02-20 08:24:11 +0000
@@ -54,7 +54,7 @@
5454
55 info.Password = password55 info.Password = password
56 opts := state.DefaultDialOpts()56 opts := state.DefaultDialOpts()
57 st, err := state.Open(info, opts)57 st, err := state.Open(info, opts, environs.NewStatePolicy())
58 if errors.IsUnauthorizedError(err) {58 if errors.IsUnauthorizedError(err) {
59 log.Noticef("juju: authorization error while connecting to state server; retrying")59 log.Noticef("juju: authorization error while connecting to state server; retrying")
60 // We can't connect with the administrator password,;60 // We can't connect with the administrator password,;
@@ -66,7 +66,7 @@
66 // connecting to mongo before the state has been66 // connecting to mongo before the state has been
67 // initialized and the initial password set.67 // initialized and the initial password set.
68 for a := redialStrategy.Start(); a.Next(); {68 for a := redialStrategy.Start(); a.Next(); {
69 st, err = state.Open(info, opts)69 st, err = state.Open(info, opts, environs.NewStatePolicy())
70 if !errors.IsUnauthorizedError(err) {70 if !errors.IsUnauthorizedError(err) {
71 break71 break
72 }72 }
7373
=== modified file 'juju/conn_test.go'
--- juju/conn_test.go 2014-02-13 02:46:58 +0000
+++ juju/conn_test.go 2014-02-20 08:24:11 +0000
@@ -140,7 +140,7 @@
140 info, _, err := env.StateInfo()140 info, _, err := env.StateInfo()
141 c.Assert(err, gc.IsNil)141 c.Assert(err, gc.IsNil)
142 info.Password = utils.UserPasswordHash("side-effect secret", utils.CompatSalt)142 info.Password = utils.UserPasswordHash("side-effect secret", utils.CompatSalt)
143 st, err := state.Open(info, state.DefaultDialOpts())143 st, err := state.Open(info, state.DefaultDialOpts(), environs.NewStatePolicy())
144 c.Assert(err, gc.IsNil)144 c.Assert(err, gc.IsNil)
145 defer assertClose(c, st)145 defer assertClose(c, st)
146146
@@ -230,7 +230,7 @@
230 info, _, err := env.StateInfo()230 info, _, err := env.StateInfo()
231 c.Assert(err, gc.IsNil)231 c.Assert(err, gc.IsNil)
232 info.Password = utils.UserPasswordHash("nutkin", utils.CompatSalt)232 info.Password = utils.UserPasswordHash("nutkin", utils.CompatSalt)
233 st, err := state.Open(info, state.DefaultDialOpts())233 st, err := state.Open(info, state.DefaultDialOpts(), environs.NewStatePolicy())
234 c.Assert(err, gc.IsNil)234 c.Assert(err, gc.IsNil)
235 assertClose(c, st)235 assertClose(c, st)
236236
@@ -242,7 +242,7 @@
242 // Check that the password has now been changed to the original242 // Check that the password has now been changed to the original
243 // admin password.243 // admin password.
244 info.Password = "nutkin"244 info.Password = "nutkin"
245 st1, err := state.Open(info, state.DefaultDialOpts())245 st1, err := state.Open(info, state.DefaultDialOpts(), environs.NewStatePolicy())
246 c.Assert(err, gc.IsNil)246 c.Assert(err, gc.IsNil)
247 assertClose(c, st1)247 assertClose(c, st1)
248248
249249
=== modified file 'provider/azure/environ.go'
--- provider/azure/environ.go 2014-01-29 06:45:16 +0000
+++ provider/azure/environ.go 2014-02-20 08:24:11 +0000
@@ -123,18 +123,6 @@
123 return key, nil123 return key, nil
124}124}
125125
126// PrecheckInstance is specified in the environs.Prechecker interface.
127func (*azureEnviron) PrecheckInstance(series string, cons constraints.Value) error {
128 return nil
129}
130
131// PrecheckContainer is specified in the environs.Prechecker interface.
132func (*azureEnviron) PrecheckContainer(series string, kind instance.ContainerType) error {
133 // This check can either go away or be relaxed when the azure
134 // provider manages container addressibility.
135 return environs.NewContainersUnsupported("azure provider does not support containers")
136}
137
138// Name is specified in the Environ interface.126// Name is specified in the Environ interface.
139func (env *azureEnviron) Name() string {127func (env *azureEnviron) Name() string {
140 return env.name128 return env.name
141129
=== modified file 'provider/azure/environ_test.go'
--- provider/azure/environ_test.go 2014-01-29 06:45:16 +0000
+++ provider/azure/environ_test.go 2014-02-20 08:24:11 +0000
@@ -93,15 +93,6 @@
93 c.Check(env.Name(), gc.Equals, env.name)93 c.Check(env.Name(), gc.Equals, env.name)
94}94}
9595
96func (*environSuite) TestPrecheck(c *gc.C) {
97 env := azureEnviron{name: "foo"}
98 var cons constraints.Value
99 err := env.PrecheckInstance("saucy", cons)
100 c.Check(err, gc.IsNil)
101 err = env.PrecheckContainer("saucy", instance.LXC)
102 c.Check(err, gc.ErrorMatches, "azure provider does not support containers")
103}
104
105func (*environSuite) TestConfigReturnsConfig(c *gc.C) {96func (*environSuite) TestConfigReturnsConfig(c *gc.C) {
106 cfg := new(config.Config)97 cfg := new(config.Config)
107 ecfg := azureEnvironConfig{Config: cfg}98 ecfg := azureEnvironConfig{Config: cfg}
10899
=== modified file 'provider/dummy/environs.go'
--- provider/dummy/environs.go 2014-02-20 04:59:31 +0000
+++ provider/dummy/environs.go 2014-02-20 08:24:11 +0000
@@ -146,8 +146,9 @@
146// environProvider represents the dummy provider. There is only ever one146// environProvider represents the dummy provider. There is only ever one
147// instance of this type (providerInstance)147// instance of this type (providerInstance)
148type environProvider struct {148type environProvider struct {
149 mu sync.Mutex149 mu sync.Mutex
150 ops chan<- Operation150 ops chan<- Operation
151 statePolicy state.Policy
151 // We have one state for each environment name152 // We have one state for each environment name
152 state map[int]*environState153 state map[int]*environState
153 maxStateId int154 maxStateId int
@@ -164,6 +165,7 @@
164 id int165 id int
165 name string166 name string
166 ops chan<- Operation167 ops chan<- Operation
168 statePolicy state.Policy
167 mu sync.Mutex169 mu sync.Mutex
168 maxId int // maximum instance id allocated so far.170 maxId int // maximum instance id allocated so far.
169 insts map[instance.Id]*dummyInstance171 insts map[instance.Id]*dummyInstance
@@ -225,6 +227,7 @@
225 if testing.MgoServer.Addr() != "" {227 if testing.MgoServer.Addr() != "" {
226 testing.MgoServer.Reset()228 testing.MgoServer.Reset()
227 }229 }
230 providerInstance.statePolicy = environs.NewStatePolicy()
228}231}
229232
230func (state *environState) destroy() {233func (state *environState) destroy() {
@@ -262,10 +265,11 @@
262// newState creates the state for a new environment with the265// newState creates the state for a new environment with the
263// given name and starts an http server listening for266// given name and starts an http server listening for
264// storage requests.267// storage requests.
265func newState(name string, ops chan<- Operation) *environState {268func newState(name string, ops chan<- Operation, policy state.Policy) *environState {
266 s := &environState{269 s := &environState{
267 name: name,270 name: name,
268 ops: ops,271 ops: ops,
272 statePolicy: policy,
269 insts: make(map[instance.Id]*dummyInstance),273 insts: make(map[instance.Id]*dummyInstance),
270 globalPorts: make(map[instance.Port]bool),274 globalPorts: make(map[instance.Port]bool),
271 }275 }
@@ -287,6 +291,15 @@
287 go http.Serve(l, mux)291 go http.Serve(l, mux)
288}292}
289293
294// SetStatePolicy sets the state.Policy to use when a
295// state server is initialised by dummy.
296func SetStatePolicy(policy state.Policy) {
297 p := &providerInstance
298 p.mu.Lock()
299 defer p.mu.Unlock()
300 p.statePolicy = policy
301}
302
290// Listen closes the previously registered listener (if any).303// Listen closes the previously registered listener (if any).
291// Subsequent operations on any dummy environment can be received on c304// Subsequent operations on any dummy environment can be received on c
292// (if not nil).305// (if not nil).
@@ -450,7 +463,7 @@
450 panic(fmt.Errorf("cannot share a state between two dummy environs; old %q; new %q", old.name, name))463 panic(fmt.Errorf("cannot share a state between two dummy environs; old %q; new %q", old.name, name))
451 }464 }
452 }465 }
453 state := newState(name, p.ops)466 state := newState(name, p.ops, p.statePolicy)
454 p.maxStateId++467 p.maxStateId++
455 state.id = p.maxStateId468 state.id = p.maxStateId
456 p.state[state.id] = state469 p.state[state.id] = state
@@ -574,7 +587,7 @@
574 // so that we can call it here.587 // so that we can call it here.
575588
576 info := stateInfo()589 info := stateInfo()
577 st, err := state.Initialize(info, cfg, state.DefaultDialOpts())590 st, err := state.Initialize(info, cfg, state.DefaultDialOpts(), estate.statePolicy)
578 if err != nil {591 if err != nil {
579 panic(err)592 panic(err)
580 }593 }
581594
=== modified file 'provider/ec2/ec2.go'
--- provider/ec2/ec2.go 2014-02-13 02:46:58 +0000
+++ provider/ec2/ec2.go 2014-02-20 08:24:11 +0000
@@ -306,18 +306,6 @@
306 return s3306 return s3
307}307}
308308
309// PrecheckInstance is specified in the environs.Prechecker interface.
310func (e *environ) PrecheckInstance(series string, cons constraints.Value) error {
311 return nil
312}
313
314// PrecheckContainer is specified in the environs.Prechecker interface.
315func (e *environ) PrecheckContainer(series string, kind instance.ContainerType) error {
316 // This check can either go away or be relaxed when the ec2
317 // provider manages container addressibility.
318 return environs.NewContainersUnsupported("ec2 provider does not support containers")
319}
320
321func (e *environ) Name() string {309func (e *environ) Name() string {
322 return e.name310 return e.name
323}311}
324312
=== modified file 'provider/ec2/local_test.go'
--- provider/ec2/local_test.go 2014-02-13 02:46:58 +0000
+++ provider/ec2/local_test.go 2014-02-20 08:24:11 +0000
@@ -229,17 +229,6 @@
229 t.srv.stopServer(c)229 t.srv.stopServer(c)
230}230}
231231
232func (t *localServerSuite) TestPrecheck(c *gc.C) {
233 env := t.Prepare(c)
234 prechecker, ok := env.(environs.Prechecker)
235 c.Assert(ok, jc.IsTrue)
236 var cons constraints.Value
237 err := prechecker.PrecheckInstance("precise", cons)
238 c.Check(err, gc.IsNil)
239 err = prechecker.PrecheckContainer("precise", instance.LXC)
240 c.Check(err, gc.ErrorMatches, "ec2 provider does not support containers")
241}
242
243func (t *localServerSuite) TestBootstrapInstanceUserDataAndState(c *gc.C) {232func (t *localServerSuite) TestBootstrapInstanceUserDataAndState(c *gc.C) {
244 env := t.Prepare(c)233 env := t.Prepare(c)
245 envtesting.UploadFakeTools(c, env.Storage())234 envtesting.UploadFakeTools(c, env.Storage())
246235
=== modified file 'provider/local/environ.go'
--- provider/local/environ.go 2014-02-18 01:29:47 +0000
+++ provider/local/environ.go 2014-02-20 08:24:11 +0000
@@ -82,18 +82,6 @@
82 return fmt.Sprintf("/etc/rsyslog.d/25-juju-%s.conf", env.config.namespace())82 return fmt.Sprintf("/etc/rsyslog.d/25-juju-%s.conf", env.config.namespace())
83}83}
8484
85// PrecheckInstance is specified in the environs.Prechecker interface.
86func (*localEnviron) PrecheckInstance(series string, cons constraints.Value) error {
87 return nil
88}
89
90// PrecheckContainer is specified in the environs.Prechecker interface.
91func (*localEnviron) PrecheckContainer(series string, kind instance.ContainerType) error {
92 // This check can either go away or be relaxed when the local
93 // provider can do nested containers.
94 return environs.NewContainersUnsupported("local provider does not support nested containers")
95}
96
97func ensureNotRoot() error {85func ensureNotRoot() error {
98 if checkIfRoot() {86 if checkIfRoot() {
99 return fmt.Errorf("bootstrapping a local environment must not be done as root")87 return fmt.Errorf("bootstrapping a local environment must not be done as root")
10088
=== modified file 'provider/local/environ_test.go'
--- provider/local/environ_test.go 2014-02-18 17:29:25 +0000
+++ provider/local/environ_test.go 2014-02-20 08:24:11 +0000
@@ -20,7 +20,6 @@
20 "launchpad.net/juju-core/environs/jujutest"20 "launchpad.net/juju-core/environs/jujutest"
21 envtesting "launchpad.net/juju-core/environs/testing"21 envtesting "launchpad.net/juju-core/environs/testing"
22 "launchpad.net/juju-core/environs/tools"22 "launchpad.net/juju-core/environs/tools"
23 "launchpad.net/juju-core/instance"
24 "launchpad.net/juju-core/provider/local"23 "launchpad.net/juju-core/provider/local"
25 "launchpad.net/juju-core/state"24 "launchpad.net/juju-core/state"
26 coretesting "launchpad.net/juju-core/testing"25 coretesting "launchpad.net/juju-core/testing"
@@ -76,21 +75,6 @@
76 c.Assert(strings.Contains(url, "/tools"), jc.IsTrue)75 c.Assert(strings.Contains(url, "/tools"), jc.IsTrue)
77}76}
7877
79func (s *environSuite) TestPrecheck(c *gc.C) {
80 testConfig := minimalConfig(c)
81 environ, err := local.Provider.Open(testConfig)
82 c.Assert(err, gc.IsNil)
83 var cons constraints.Value
84 prechecker, ok := environ.(environs.Prechecker)
85 c.Assert(ok, jc.IsTrue)
86
87 err = prechecker.PrecheckInstance("precise", cons)
88 c.Check(err, gc.IsNil)
89
90 err = prechecker.PrecheckContainer("precise", instance.LXC)
91 c.Check(err, gc.ErrorMatches, "local provider does not support nested containers")
92}
93
94type localJujuTestSuite struct {78type localJujuTestSuite struct {
95 baseProviderSuite79 baseProviderSuite
96 jujutest.Tests80 jujutest.Tests
9781
=== modified file 'provider/manual/environ.go'
--- provider/manual/environ.go 2014-02-13 03:16:09 +0000
+++ provider/manual/environ.go 2014-02-20 08:24:11 +0000
@@ -231,6 +231,10 @@
231 return err231 return err
232}232}
233233
234func (*manualEnviron) PrecheckInstance(series string, cons constraints.Value) error {
235 return errors.New(`use "juju add-machine ssh:[user@]<host>" to provision machines`)
236}
237
234func (e *manualEnviron) OpenPorts(ports []instance.Port) error {238func (e *manualEnviron) OpenPorts(ports []instance.Port) error {
235 return nil239 return nil
236}240}
237241
=== modified file 'provider/openstack/local_test.go'
--- provider/openstack/local_test.go 2014-02-18 01:29:47 +0000
+++ provider/openstack/local_test.go 2014-02-20 08:24:11 +0000
@@ -252,17 +252,6 @@
252 s.LoggingSuite.TearDownTest(c)252 s.LoggingSuite.TearDownTest(c)
253}253}
254254
255func (s *localServerSuite) TestPrecheck(c *gc.C) {
256 var cons constraints.Value
257 env := s.Prepare(c)
258 prechecker, ok := env.(environs.Prechecker)
259 c.Assert(ok, jc.IsTrue)
260 err := prechecker.PrecheckInstance("precise", cons)
261 c.Check(err, gc.IsNil)
262 err = prechecker.PrecheckContainer("precise", instance.LXC)
263 c.Check(err, gc.ErrorMatches, "openstack provider does not support containers")
264}
265
266// If the bootstrap node is configured to require a public IP address,255// If the bootstrap node is configured to require a public IP address,
267// bootstrapping fails if an address cannot be allocated.256// bootstrapping fails if an address cannot be allocated.
268func (s *localServerSuite) TestBootstrapFailsWhenPublicIPError(c *gc.C) {257func (s *localServerSuite) TestBootstrapFailsWhenPublicIPError(c *gc.C) {
269258
=== modified file 'provider/openstack/provider.go'
--- provider/openstack/provider.go 2014-02-13 02:46:58 +0000
+++ provider/openstack/provider.go 2014-02-20 08:24:11 +0000
@@ -483,18 +483,6 @@
483 return nova483 return nova
484}484}
485485
486// PrecheckInstance is specified in the environs.Prechecker interface.
487func (*environ) PrecheckInstance(series string, cons constraints.Value) error {
488 return nil
489}
490
491// PrecheckContainer is specified in the environs.Prechecker interface.
492func (*environ) PrecheckContainer(series string, kind instance.ContainerType) error {
493 // This check can either go away or be relaxed when the openstack
494 // provider manages container addressibility.
495 return environs.NewContainersUnsupported("openstack provider does not support containers")
496}
497
498func (e *environ) Name() string {486func (e *environ) Name() string {
499 return e.name487 return e.name
500}488}
501489
=== modified file 'state/addmachine.go'
--- state/addmachine.go 2014-01-25 11:45:42 +0000
+++ state/addmachine.go 2014-02-20 08:24:11 +0000
@@ -218,6 +218,11 @@
218 if err != nil {218 if err != nil {
219 return nil, nil, err219 return nil, nil, err
220 }220 }
221 if template.InstanceId == "" {
222 if err := st.precheckInstance(template.Series, template.Constraints); err != nil {
223 return nil, nil, err
224 }
225 }
221 seq, err := st.sequence("machine")226 seq, err := st.sequence("machine")
222 if err != nil {227 if err != nil {
223 return nil, nil, err228 return nil, nil, err
@@ -331,6 +336,15 @@
331 if err != nil {336 if err != nil {
332 return nil, nil, err337 return nil, nil, err
333 }338 }
339 if containerType == "" {
340 return nil, nil, fmt.Errorf("no container type specified")
341 }
342 if parentTemplate.InstanceId == "" {
343 if err := st.precheckInstance(parentTemplate.Series, parentTemplate.Constraints); err != nil {
344 return nil, nil, err
345 }
346 }
347
334 parentDoc := machineDocForTemplate(parentTemplate, strconv.Itoa(seq))348 parentDoc := machineDocForTemplate(parentTemplate, strconv.Itoa(seq))
335 newId, err := st.newContainerId(parentDoc.Id, containerType)349 newId, err := st.newContainerId(parentDoc.Id, containerType)
336 if err != nil {350 if err != nil {
337351
=== modified file 'state/api/agent/machine_test.go'
--- state/api/agent/machine_test.go 2013-11-05 10:25:28 +0000
+++ state/api/agent/machine_test.go 2014-02-20 08:24:11 +0000
@@ -9,6 +9,7 @@
99
10 gc "launchpad.net/gocheck"10 gc "launchpad.net/gocheck"
1111
12 "launchpad.net/juju-core/environs"
12 "launchpad.net/juju-core/errors"13 "launchpad.net/juju-core/errors"
13 "launchpad.net/juju-core/juju/testing"14 "launchpad.net/juju-core/juju/testing"
14 "launchpad.net/juju-core/state"15 "launchpad.net/juju-core/state"
@@ -83,7 +84,7 @@
83}84}
8485
85func tryOpenState(info *state.Info) error {86func tryOpenState(info *state.Info) error {
86 st, err := state.Open(info, state.DialOpts{})87 st, err := state.Open(info, state.DialOpts{}, environs.NewStatePolicy())
87 if err == nil {88 if err == nil {
88 st.Close()89 st.Close()
89 }90 }
9091
=== modified file 'state/apiserver/client/api_test.go'
--- state/apiserver/client/api_test.go 2014-01-23 20:30:35 +0000
+++ state/apiserver/client/api_test.go 2014-02-20 08:24:11 +0000
@@ -11,6 +11,7 @@
11 gc "launchpad.net/gocheck"11 gc "launchpad.net/gocheck"
1212
13 "launchpad.net/juju-core/constraints"13 "launchpad.net/juju-core/constraints"
14 "launchpad.net/juju-core/environs"
14 "launchpad.net/juju-core/environs/config"15 "launchpad.net/juju-core/environs/config"
15 "launchpad.net/juju-core/errors"16 "launchpad.net/juju-core/errors"
16 "launchpad.net/juju-core/instance"17 "launchpad.net/juju-core/instance"
@@ -111,7 +112,7 @@
111 stateInfo.Password = password112 stateInfo.Password = password
112 st, err := state.Open(stateInfo, state.DialOpts{113 st, err := state.Open(stateInfo, state.DialOpts{
113 Timeout: 25 * time.Millisecond,114 Timeout: 25 * time.Millisecond,
114 })115 }, environs.NewStatePolicy())
115 if err == nil {116 if err == nil {
116 st.Close()117 st.Close()
117 }118 }
118119
=== modified file 'state/compat_test.go'
--- state/compat_test.go 2014-01-09 10:37:28 +0000
+++ state/compat_test.go 2014-02-20 08:24:11 +0000
@@ -36,7 +36,7 @@
36func (s *compatSuite) SetUpTest(c *gc.C) {36func (s *compatSuite) SetUpTest(c *gc.C) {
37 s.LoggingSuite.SetUpTest(c)37 s.LoggingSuite.SetUpTest(c)
38 s.MgoSuite.SetUpTest(c)38 s.MgoSuite.SetUpTest(c)
39 s.state = TestingInitialize(c, nil)39 s.state = TestingInitialize(c, nil, Policy(nil))
40 env, err := s.state.Environment()40 env, err := s.state.Environment()
41 c.Assert(err, gc.IsNil)41 c.Assert(err, gc.IsNil)
42 s.env = env42 s.env = env
4343
=== modified file 'state/conn_test.go'
--- state/conn_test.go 2013-12-03 14:05:19 +0000
+++ state/conn_test.go 2014-02-20 08:24:11 +0000
@@ -9,6 +9,8 @@
9 "labix.org/v2/mgo"9 "labix.org/v2/mgo"
10 gc "launchpad.net/gocheck"10 gc "launchpad.net/gocheck"
1111
12 "launchpad.net/juju-core/environs/config"
13 "launchpad.net/juju-core/errors"
12 "launchpad.net/juju-core/state"14 "launchpad.net/juju-core/state"
13 "launchpad.net/juju-core/testing"15 "launchpad.net/juju-core/testing"
14 "launchpad.net/juju-core/testing/testbase"16 "launchpad.net/juju-core/testing/testbase"
@@ -32,6 +34,7 @@
32 units *mgo.Collection34 units *mgo.Collection
33 stateServers *mgo.Collection35 stateServers *mgo.Collection
34 State *state.State36 State *state.State
37 policy mockPolicy
35}38}
3639
37func (cs *ConnSuite) SetUpSuite(c *gc.C) {40func (cs *ConnSuite) SetUpSuite(c *gc.C) {
@@ -47,7 +50,8 @@
47func (cs *ConnSuite) SetUpTest(c *gc.C) {50func (cs *ConnSuite) SetUpTest(c *gc.C) {
48 cs.LoggingSuite.SetUpTest(c)51 cs.LoggingSuite.SetUpTest(c)
49 cs.MgoSuite.SetUpTest(c)52 cs.MgoSuite.SetUpTest(c)
50 cs.State = state.TestingInitialize(c, nil)53 cs.policy = mockPolicy{}
54 cs.State = state.TestingInitialize(c, nil, &cs.policy)
51 cs.annotations = cs.MgoSuite.Session.DB("juju").C("annotations")55 cs.annotations = cs.MgoSuite.Session.DB("juju").C("annotations")
52 cs.charms = cs.MgoSuite.Session.DB("juju").C("charms")56 cs.charms = cs.MgoSuite.Session.DB("juju").C("charms")
53 cs.machines = cs.MgoSuite.Session.DB("juju").C("machines")57 cs.machines = cs.MgoSuite.Session.DB("juju").C("machines")
@@ -88,3 +92,14 @@
88func (s *ConnSuite) AddMetaCharm(c *gc.C, name, metaYaml string, revsion int) *state.Charm {92func (s *ConnSuite) AddMetaCharm(c *gc.C, name, metaYaml string, revsion int) *state.Charm {
89 return state.AddCustomCharm(c, s.State, name, "metadata.yaml", metaYaml, "quantal", revsion)93 return state.AddCustomCharm(c, s.State, name, "metadata.yaml", metaYaml, "quantal", revsion)
90}94}
95
96type mockPolicy struct {
97 getPrechecker func(*config.Config) (state.Prechecker, error)
98}
99
100func (p *mockPolicy) Prechecker(cfg *config.Config) (state.Prechecker, error) {
101 if p.getPrechecker != nil {
102 return p.getPrechecker(cfg)
103 }
104 return nil, errors.NewNotImplementedError("Prechecker")
105}
91106
=== modified file 'state/environ_test.go'
--- state/environ_test.go 2014-02-03 09:44:12 +0000
+++ state/environ_test.go 2014-02-20 08:24:11 +0000
@@ -40,7 +40,7 @@
40 s.State.Close()40 s.State.Close()
41 s.MgoSuite.TearDownTest(c)41 s.MgoSuite.TearDownTest(c)
42 s.MgoSuite.SetUpTest(c)42 s.MgoSuite.SetUpTest(c)
43 s.State = state.TestingInitialize(c, nil)43 s.State = state.TestingInitialize(c, nil, state.Policy(nil))
44 env, err := s.State.Environment()44 env, err := s.State.Environment()
45 c.Assert(err, gc.IsNil)45 c.Assert(err, gc.IsNil)
46 uuidB := env.UUID()46 uuidB := env.UUID()
4747
=== modified file 'state/export_test.go'
--- state/export_test.go 2014-02-17 12:57:06 +0000
+++ state/export_test.go 2014-02-20 08:24:11 +0000
@@ -94,14 +94,22 @@
94 })94 })
95}95}
9696
97// SetPolicy updates the State's policy field to the
98// given Policy, and returns the old value.
99func SetPolicy(st *State, p Policy) Policy {
100 old := st.policy
101 st.policy = p
102 return old
103}
104
97// TestingInitialize initializes the state and returns it. If state was not105// TestingInitialize initializes the state and returns it. If state was not
98// already initialized, and cfg is nil, the minimal default environment106// already initialized, and cfg is nil, the minimal default environment
99// configuration will be used.107// configuration will be used.
100func TestingInitialize(c *gc.C, cfg *config.Config) *State {108func TestingInitialize(c *gc.C, cfg *config.Config, policy Policy) *State {
101 if cfg == nil {109 if cfg == nil {
102 cfg = testing.EnvironConfig(c)110 cfg = testing.EnvironConfig(c)
103 }111 }
104 st, err := Initialize(TestingStateInfo(), cfg, TestingDialOpts())112 st, err := Initialize(TestingStateInfo(), cfg, TestingDialOpts(), policy)
105 c.Assert(err, gc.IsNil)113 c.Assert(err, gc.IsNil)
106 return st114 return st
107}115}
108116
=== modified file 'state/initialize_test.go'
--- state/initialize_test.go 2013-12-11 09:14:12 +0000
+++ state/initialize_test.go 2014-02-20 08:24:11 +0000
@@ -37,7 +37,7 @@
37 s.LoggingSuite.SetUpTest(c)37 s.LoggingSuite.SetUpTest(c)
38 s.MgoSuite.SetUpTest(c)38 s.MgoSuite.SetUpTest(c)
39 var err error39 var err error
40 s.State, err = state.Open(state.TestingStateInfo(), state.TestingDialOpts())40 s.State, err = state.Open(state.TestingStateInfo(), state.TestingDialOpts(), state.Policy(nil))
41 c.Assert(err, gc.IsNil)41 c.Assert(err, gc.IsNil)
42}42}
4343
@@ -59,7 +59,7 @@
5959
60 cfg := testing.EnvironConfig(c)60 cfg := testing.EnvironConfig(c)
61 initial := cfg.AllAttrs()61 initial := cfg.AllAttrs()
62 st, err := state.Initialize(state.TestingStateInfo(), cfg, state.TestingDialOpts())62 st, err := state.Initialize(state.TestingStateInfo(), cfg, state.TestingDialOpts(), state.Policy(nil))
63 c.Assert(err, gc.IsNil)63 c.Assert(err, gc.IsNil)
64 c.Assert(st, gc.NotNil)64 c.Assert(st, gc.NotNil)
65 err = st.Close()65 err = st.Close()
@@ -85,7 +85,7 @@
85func (s *InitializeSuite) TestDoubleInitializeConfig(c *gc.C) {85func (s *InitializeSuite) TestDoubleInitializeConfig(c *gc.C) {
86 cfg := testing.EnvironConfig(c)86 cfg := testing.EnvironConfig(c)
87 initial := cfg.AllAttrs()87 initial := cfg.AllAttrs()
88 st := state.TestingInitialize(c, cfg)88 st := state.TestingInitialize(c, cfg, state.Policy(nil))
89 st.Close()89 st.Close()
9090
91 // A second initialize returns an open *State, but ignores its params.91 // A second initialize returns an open *State, but ignores its params.
@@ -93,7 +93,7 @@
93 // for originally...93 // for originally...
94 cfg, err := cfg.Apply(map[string]interface{}{"authorized-keys": "something-else"})94 cfg, err := cfg.Apply(map[string]interface{}{"authorized-keys": "something-else"})
95 c.Assert(err, gc.IsNil)95 c.Assert(err, gc.IsNil)
96 st, err = state.Initialize(state.TestingStateInfo(), cfg, state.TestingDialOpts())96 st, err = state.Initialize(state.TestingStateInfo(), cfg, state.TestingDialOpts(), state.Policy(nil))
97 c.Assert(err, gc.IsNil)97 c.Assert(err, gc.IsNil)
98 c.Assert(st, gc.NotNil)98 c.Assert(st, gc.NotNil)
99 st.Close()99 st.Close()
@@ -108,11 +108,11 @@
108 good := testing.EnvironConfig(c)108 good := testing.EnvironConfig(c)
109 bad, err := good.Apply(map[string]interface{}{"admin-secret": "foo"})109 bad, err := good.Apply(map[string]interface{}{"admin-secret": "foo"})
110110
111 _, err = state.Initialize(state.TestingStateInfo(), bad, state.TestingDialOpts())111 _, err = state.Initialize(state.TestingStateInfo(), bad, state.TestingDialOpts(), state.Policy(nil))
112 c.Assert(err, gc.ErrorMatches, "admin-secret should never be written to the state")112 c.Assert(err, gc.ErrorMatches, "admin-secret should never be written to the state")
113113
114 // admin-secret blocks SetEnvironConfig.114 // admin-secret blocks SetEnvironConfig.
115 st := state.TestingInitialize(c, good)115 st := state.TestingInitialize(c, good, state.Policy(nil))
116 st.Close()116 st.Close()
117 err = s.State.SetEnvironConfig(bad, good)117 err = s.State.SetEnvironConfig(bad, good)
118 c.Assert(err, gc.ErrorMatches, "admin-secret should never be written to the state")118 c.Assert(err, gc.ErrorMatches, "admin-secret should never be written to the state")
@@ -131,11 +131,11 @@
131 bad, err := config.New(config.NoDefaults, attrs)131 bad, err := config.New(config.NoDefaults, attrs)
132 c.Assert(err, gc.IsNil)132 c.Assert(err, gc.IsNil)
133133
134 _, err = state.Initialize(state.TestingStateInfo(), bad, state.TestingDialOpts())134 _, err = state.Initialize(state.TestingStateInfo(), bad, state.TestingDialOpts(), state.Policy(nil))
135 c.Assert(err, gc.ErrorMatches, "agent-version must always be set in state")135 c.Assert(err, gc.ErrorMatches, "agent-version must always be set in state")
136136
137 // Bad agent-version blocks SetEnvironConfig.137 // Bad agent-version blocks SetEnvironConfig.
138 st := state.TestingInitialize(c, good)138 st := state.TestingInitialize(c, good, state.Policy(nil))
139 st.Close()139 st.Close()
140 err = s.State.SetEnvironConfig(bad, good)140 err = s.State.SetEnvironConfig(bad, good)
141 c.Assert(err, gc.ErrorMatches, "agent-version must always be set in state")141 c.Assert(err, gc.ErrorMatches, "agent-version must always be set in state")
142142
=== modified file 'state/megawatcher_internal_test.go'
--- state/megawatcher_internal_test.go 2014-01-24 13:28:02 +0000
+++ state/megawatcher_internal_test.go 2014-02-20 08:24:11 +0000
@@ -47,7 +47,7 @@
47func (s *storeManagerStateSuite) SetUpTest(c *gc.C) {47func (s *storeManagerStateSuite) SetUpTest(c *gc.C) {
48 s.LoggingSuite.SetUpTest(c)48 s.LoggingSuite.SetUpTest(c)
49 s.MgoSuite.SetUpTest(c)49 s.MgoSuite.SetUpTest(c)
50 s.State = TestingInitialize(c, nil)50 s.State = TestingInitialize(c, nil, Policy(nil))
51 s.State.AddUser("admin", "pass")51 s.State.AddUser("admin", "pass")
52}52}
5353
5454
=== modified file 'state/open.go'
--- state/open.go 2014-02-07 18:38:02 +0000
+++ state/open.go 2014-02-20 08:24:11 +0000
@@ -69,8 +69,13 @@
69// Open connects to the server described by the given69// Open connects to the server described by the given
70// info, waits for it to be initialized, and returns a new State70// info, waits for it to be initialized, and returns a new State
71// representing the environment connected to.71// representing the environment connected to.
72// It returns unauthorizedError if access is unauthorized.72//
73func Open(info *Info, opts DialOpts) (*State, error) {73// A policy may be provided, which will be used to validate and
74// modify behaviour of certain operations in state. A nil policy
75// may be provided.
76//
77// Open returns unauthorizedError if access is unauthorized.
78func Open(info *Info, opts DialOpts, policy Policy) (*State, error) {
74 logger.Infof("opening state; mongo addresses: %q; entity %q", info.Addrs, info.Tag)79 logger.Infof("opening state; mongo addresses: %q; entity %q", info.Addrs, info.Tag)
75 if len(info.Addrs) == 0 {80 if len(info.Addrs) == 0 {
76 return nil, stderrors.New("no mongo addresses")81 return nil, stderrors.New("no mongo addresses")
@@ -110,7 +115,7 @@
110 return nil, err115 return nil, err
111 }116 }
112 logger.Infof("connection established")117 logger.Infof("connection established")
113 st, err := newState(session, info)118 st, err := newState(session, info, policy)
114 if err != nil {119 if err != nil {
115 session.Close()120 session.Close()
116 return nil, err121 return nil, err
@@ -122,8 +127,8 @@
122// Initialize sets up an initial empty state and returns it.127// Initialize sets up an initial empty state and returns it.
123// This needs to be performed only once for a given environment.128// This needs to be performed only once for a given environment.
124// It returns unauthorizedError if access is unauthorized.129// It returns unauthorizedError if access is unauthorized.
125func Initialize(info *Info, cfg *config.Config, opts DialOpts) (rst *State, err error) {130func Initialize(info *Info, cfg *config.Config, opts DialOpts, policy Policy) (rst *State, err error) {
126 st, err := Open(info, opts)131 st, err := Open(info, opts, policy)
127 if err != nil {132 if err != nil {
128 return nil, err133 return nil, err
129 }134 }
@@ -217,7 +222,7 @@
217 return false222 return false
218}223}
219224
220func newState(session *mgo.Session, info *Info) (*State, error) {225func newState(session *mgo.Session, info *Info, policy Policy) (*State, error) {
221 db := session.DB("juju")226 db := session.DB("juju")
222 pdb := session.DB("presence")227 pdb := session.DB("presence")
223 if info.Tag != "" {228 if info.Tag != "" {
@@ -235,6 +240,7 @@
235 }240 }
236 st := &State{241 st := &State{
237 info: info,242 info: info,
243 policy: policy,
238 db: db,244 db: db,
239 environments: db.C("environments"),245 environments: db.C("environments"),
240 charms: db.C("charms"),246 charms: db.C("charms"),
241247
=== added file 'state/policy.go'
--- state/policy.go 1970-01-01 00:00:00 +0000
+++ state/policy.go 2014-02-20 08:24:11 +0000
@@ -0,0 +1,63 @@
1// Copyright 2014 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package state
5
6import (
7 "fmt"
8
9 "launchpad.net/juju-core/constraints"
10 "launchpad.net/juju-core/environs/config"
11 "launchpad.net/juju-core/errors"
12)
13
14// Policy is an interface provided to State that may
15// be consulted by State to validate or modify the
16// behaviour of certain operations.
17//
18// If a Policy implementation does not implement one
19// of the methods, it must return an error that
20// satisfies errors.IsNotImplemented, and will thus
21// be ignored. Any other error will cause an error
22// in the use of the policy.
23type Policy interface {
24 // Prechecker takes a *config.Config and returns
25 // a (possibly nil) Prechecker or an error.
26 Prechecker(*config.Config) (Prechecker, error)
27}
28
29// Prechecker is a policy interface that is provided to State
30// to perform pre-flight checking of instance creation.
31type Prechecker interface {
32 // PrecheckInstance performs a preflight check on the specified
33 // series and constraints, ensuring that they are possibly valid for
34 // creating an instance in this environment.
35 //
36 // PrecheckInstance is best effort, and not guaranteed to eliminate
37 // all invalid parameters. If PrecheckInstance returns nil, it is not
38 // guaranteed that the constraints are valid; if a non-nil error is
39 // returned, then the constraints are definitely invalid.
40 PrecheckInstance(series string, cons constraints.Value) error
41}
42
43// precheckInstance calls the state's assigned policy, if non-nil, to obtain
44// a Prechecker, and calls PrecheckInstance if a non-nil Prechecker is returned.
45func (st *State) precheckInstance(series string, cons constraints.Value) error {
46 if st.policy == nil {
47 return nil
48 }
49 cfg, err := st.EnvironConfig()
50 if err != nil {
51 return err
52 }
53 prechecker, err := st.policy.Prechecker(cfg)
54 if errors.IsNotImplementedError(err) {
55 return nil
56 } else if err != nil {
57 return err
58 }
59 if prechecker == nil {
60 return fmt.Errorf("policy returned nil prechecker without an error")
61 }
62 return prechecker.PrecheckInstance(series, cons)
63}
064
=== added file 'state/prechecker_test.go'
--- state/prechecker_test.go 1970-01-01 00:00:00 +0000
+++ state/prechecker_test.go 2014-02-20 08:24:11 +0000
@@ -0,0 +1,132 @@
1// Copyright 2014 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package state_test
5
6import (
7 "fmt"
8
9 gc "launchpad.net/gocheck"
10
11 "launchpad.net/juju-core/constraints"
12 "launchpad.net/juju-core/environs/config"
13 "launchpad.net/juju-core/errors"
14 "launchpad.net/juju-core/instance"
15 "launchpad.net/juju-core/state"
16)
17
18type PrecheckerSuite struct {
19 ConnSuite
20 prechecker mockPrechecker
21}
22
23var _ = gc.Suite(&PrecheckerSuite{})
24
25type mockPrechecker struct {
26 precheckInstanceError error
27 precheckInstanceSeries string
28 precheckInstanceConstraints constraints.Value
29}
30
31func (p *mockPrechecker) PrecheckInstance(series string, cons constraints.Value) error {
32 p.precheckInstanceSeries = series
33 p.precheckInstanceConstraints = cons
34 return p.precheckInstanceError
35}
36
37func (s *PrecheckerSuite) SetUpTest(c *gc.C) {
38 s.ConnSuite.SetUpTest(c)
39 s.prechecker = mockPrechecker{}
40 s.policy.getPrechecker = func(*config.Config) (state.Prechecker, error) {
41 return &s.prechecker, nil
42 }
43}
44
45func (s *PrecheckerSuite) TestPrecheckInstance(c *gc.C) {
46 // PrecheckInstance should be called with the specified
47 // series, and the specified constraints merged with the
48 // environment constraints, when attempting to create an
49 // instance.
50 envCons := constraints.MustParse("mem=4G")
51 template, err := s.addOneMachine(c, envCons)
52 c.Assert(err, gc.IsNil)
53 c.Assert(s.prechecker.precheckInstanceSeries, gc.Equals, template.Series)
54 cons := template.Constraints.WithFallbacks(envCons)
55 c.Assert(s.prechecker.precheckInstanceConstraints, gc.DeepEquals, cons)
56}
57
58func (s *PrecheckerSuite) TestPrecheckErrors(c *gc.C) {
59 // Ensure that AddOneMachine fails when PrecheckInstance returns an error.
60 s.prechecker.precheckInstanceError = fmt.Errorf("no instance for you")
61 _, err := s.addOneMachine(c, constraints.Value{})
62 c.Assert(err, gc.ErrorMatches, ".*no instance for you")
63
64 // If the policy's Prechecker method fails, that will be returned first.
65 s.policy.getPrechecker = func(*config.Config) (state.Prechecker, error) {
66 return nil, fmt.Errorf("no prechecker for you")
67 }
68 _, err = s.addOneMachine(c, constraints.Value{})
69 c.Assert(err, gc.ErrorMatches, ".*no prechecker for you")
70}
71
72func (s *PrecheckerSuite) TestPrecheckPrecheckerUnimplemented(c *gc.C) {
73 var precheckerErr error
74 s.policy.getPrechecker = func(*config.Config) (state.Prechecker, error) {
75 return nil, precheckerErr
76 }
77 _, err := s.addOneMachine(c, constraints.Value{})
78 c.Assert(err, gc.ErrorMatches, "cannot add a new machine: policy returned nil prechecker without an error")
79 precheckerErr = errors.NewNotImplementedError("Prechecker")
80 _, err = s.addOneMachine(c, constraints.Value{})
81 c.Assert(err, gc.IsNil)
82}
83
84func (s *PrecheckerSuite) TestPrecheckNoPolicy(c *gc.C) {
85 s.policy.getPrechecker = func(*config.Config) (state.Prechecker, error) {
86 c.Errorf("should not have been invoked")
87 return nil, nil
88 }
89 state.SetPolicy(s.State, nil)
90 _, err := s.addOneMachine(c, constraints.Value{})
91 c.Assert(err, gc.IsNil)
92}
93
94func (s *PrecheckerSuite) addOneMachine(c *gc.C, envCons constraints.Value) (state.MachineTemplate, error) {
95 err := s.State.SetEnvironConstraints(envCons)
96 c.Assert(err, gc.IsNil)
97 oneJob := []state.MachineJob{state.JobHostUnits}
98 extraCons := constraints.MustParse("cpu-cores=4")
99 template := state.MachineTemplate{
100 Series: "precise",
101 Constraints: extraCons,
102 Jobs: oneJob,
103 }
104 _, err = s.State.AddOneMachine(template)
105 return template, err
106}
107
108func (s *PrecheckerSuite) TestPrecheckInstanceInjectMachine(c *gc.C) {
109 template := state.MachineTemplate{
110 InstanceId: instance.Id("bootstrap"),
111 Series: "precise",
112 Nonce: state.BootstrapNonce,
113 Jobs: []state.MachineJob{state.JobManageEnviron},
114 }
115 _, err := s.State.AddOneMachine(template)
116 c.Assert(err, gc.IsNil)
117 // PrecheckInstance should not have been called, as we've
118 // injected a machine with an existing instance.
119 c.Assert(s.prechecker.precheckInstanceSeries, gc.Equals, "")
120}
121
122func (s *PrecheckerSuite) TestPrecheckContainerNewMachine(c *gc.C) {
123 // Attempting to add a container to a new machine should cause
124 // PrecheckInstance to be called.
125 template := state.MachineTemplate{
126 Series: "precise",
127 Jobs: []state.MachineJob{state.JobHostUnits},
128 }
129 _, err := s.State.AddMachineInsideNewMachine(template, template, instance.LXC)
130 c.Assert(err, gc.IsNil)
131 c.Assert(s.prechecker.precheckInstanceSeries, gc.Equals, template.Series)
132}
0133
=== modified file 'state/settings_test.go'
--- state/settings_test.go 2014-02-07 16:32:23 +0000
+++ state/settings_test.go 2014-02-20 08:24:11 +0000
@@ -53,7 +53,7 @@
53 s.LoggingSuite.SetUpTest(c)53 s.LoggingSuite.SetUpTest(c)
54 s.MgoSuite.SetUpTest(c)54 s.MgoSuite.SetUpTest(c)
55 // TODO(dfc) this logic is duplicated with the metawatcher_test.55 // TODO(dfc) this logic is duplicated with the metawatcher_test.
56 state, err := Open(TestingStateInfo(), TestingDialOpts())56 state, err := Open(TestingStateInfo(), TestingDialOpts(), Policy(nil))
57 c.Assert(err, gc.IsNil)57 c.Assert(err, gc.IsNil)
5858
59 s.state = state59 s.state = state
6060
=== modified file 'state/state.go'
--- state/state.go 2014-02-05 17:46:02 +0000
+++ state/state.go 2014-02-20 08:24:11 +0000
@@ -44,6 +44,7 @@
44// managed by juju.44// managed by juju.
45type State struct {45type State struct {
46 info *Info46 info *Info
47 policy Policy
47 db *mgo.Database48 db *mgo.Database
48 environments *mgo.Collection49 environments *mgo.Collection
49 charms *mgo.Collection50 charms *mgo.Collection
5051
=== modified file 'state/state_test.go'
--- state/state_test.go 2014-02-14 11:39:37 +0000
+++ state/state_test.go 2014-02-20 08:24:11 +0000
@@ -54,7 +54,7 @@
54func (s *StateSuite) TestDialAgain(c *gc.C) {54func (s *StateSuite) TestDialAgain(c *gc.C) {
55 // Ensure idempotent operations on Dial are working fine.55 // Ensure idempotent operations on Dial are working fine.
56 for i := 0; i < 2; i++ {56 for i := 0; i < 2; i++ {
57 st, err := state.Open(state.TestingStateInfo(), state.TestingDialOpts())57 st, err := state.Open(state.TestingStateInfo(), state.TestingDialOpts(), state.Policy(nil))
58 c.Assert(err, gc.IsNil)58 c.Assert(err, gc.IsNil)
59 c.Assert(st.Close(), gc.IsNil)59 c.Assert(st.Close(), gc.IsNil)
60 }60 }
@@ -1854,7 +1854,7 @@
1854}1854}
18551855
1856func tryOpenState(info *state.Info) error {1856func tryOpenState(info *state.Info) error {
1857 st, err := state.Open(info, state.TestingDialOpts())1857 st, err := state.Open(info, state.TestingDialOpts(), state.Policy(nil))
1858 if err == nil {1858 if err == nil {
1859 st.Close()1859 st.Close()
1860 }1860 }
@@ -1881,7 +1881,7 @@
1881 info.Addrs = []string{"0.1.2.3:1234"}1881 info.Addrs = []string{"0.1.2.3:1234"}
1882 st, err := state.Open(info, state.DialOpts{1882 st, err := state.Open(info, state.DialOpts{
1883 Timeout: 1 * time.Millisecond,1883 Timeout: 1 * time.Millisecond,
1884 })1884 }, state.Policy(nil))
1885 if err == nil {1885 if err == nil {
1886 st.Close()1886 st.Close()
1887 }1887 }
@@ -1897,7 +1897,7 @@
1897 t0 := time.Now()1897 t0 := time.Now()
1898 st, err := state.Open(info, state.DialOpts{1898 st, err := state.Open(info, state.DialOpts{
1899 Timeout: 1 * time.Millisecond,1899 Timeout: 1 * time.Millisecond,
1900 })1900 }, state.Policy(nil))
1901 if err == nil {1901 if err == nil {
1902 st.Close()1902 st.Close()
1903 }1903 }
@@ -1993,7 +1993,7 @@
19931993
1994func testSetMongoPassword(c *gc.C, getEntity func(st *state.State) (entity, error)) {1994func testSetMongoPassword(c *gc.C, getEntity func(st *state.State) (entity, error)) {
1995 info := state.TestingStateInfo()1995 info := state.TestingStateInfo()
1996 st, err := state.Open(info, state.TestingDialOpts())1996 st, err := state.Open(info, state.TestingDialOpts(), state.Policy(nil))
1997 c.Assert(err, gc.IsNil)1997 c.Assert(err, gc.IsNil)
1998 defer st.Close()1998 defer st.Close()
1999 // Turn on fully-authenticated mode.1999 // Turn on fully-authenticated mode.
@@ -2014,7 +2014,7 @@
20142014
2015 // Check that we can log in with the correct password.2015 // Check that we can log in with the correct password.
2016 info.Password = "foo"2016 info.Password = "foo"
2017 st1, err := state.Open(info, state.TestingDialOpts())2017 st1, err := state.Open(info, state.TestingDialOpts(), state.Policy(nil))
2018 c.Assert(err, gc.IsNil)2018 c.Assert(err, gc.IsNil)
2019 defer st1.Close()2019 defer st1.Close()
20202020
@@ -2634,7 +2634,7 @@
2634// interact with the closed state, causing it to return an2634// interact with the closed state, causing it to return an
2635// unexpected error (often "Closed explictly").2635// unexpected error (often "Closed explictly").
2636func testWatcherDiesWhenStateCloses(c *gc.C, startWatcher func(c *gc.C, st *state.State) waiter) {2636func testWatcherDiesWhenStateCloses(c *gc.C, startWatcher func(c *gc.C, st *state.State) waiter) {
2637 st, err := state.Open(state.TestingStateInfo(), state.TestingDialOpts())2637 st, err := state.Open(state.TestingStateInfo(), state.TestingDialOpts(), state.Policy(nil))
2638 c.Assert(err, gc.IsNil)2638 c.Assert(err, gc.IsNil)
2639 watcher := startWatcher(c, st)2639 watcher := startWatcher(c, st)
2640 err = st.Close()2640 err = st.Close()
@@ -2676,7 +2676,7 @@
2676 c.Assert(err, gc.NotNil)2676 c.Assert(err, gc.NotNil)
2677 c.Assert(info, gc.IsNil)2677 c.Assert(info, gc.IsNil)
26782678
2679 st, err := state.Open(state.TestingStateInfo(), state.TestingDialOpts())2679 st, err := state.Open(state.TestingStateInfo(), state.TestingDialOpts(), state.Policy(nil))
2680 c.Assert(err, gc.IsNil)2680 c.Assert(err, gc.IsNil)
2681 defer st.Close()2681 defer st.Close()
26822682
@@ -2695,7 +2695,7 @@
2695 c.Assert(err, gc.IsNil)2695 c.Assert(err, gc.IsNil)
2696 c.Assert(info, jc.DeepEquals, &state.StateServerInfo{})2696 c.Assert(info, jc.DeepEquals, &state.StateServerInfo{})
26972697
2698 st, err := state.Open(state.TestingStateInfo(), state.TestingDialOpts())2698 st, err := state.Open(state.TestingStateInfo(), state.TestingDialOpts(), state.Policy(nil))
2699 c.Assert(err, gc.IsNil)2699 c.Assert(err, gc.IsNil)
2700 defer st.Close()2700 defer st.Close()
27012701
@@ -2726,7 +2726,7 @@
2726 c.Assert(info.MachineIds, gc.HasLen, 0)2726 c.Assert(info.MachineIds, gc.HasLen, 0)
2727 c.Assert(info.VotingMachineIds, gc.HasLen, 0)2727 c.Assert(info.VotingMachineIds, gc.HasLen, 0)
27282728
2729 st, err := state.Open(state.TestingStateInfo(), state.TestingDialOpts())2729 st, err := state.Open(state.TestingStateInfo(), state.TestingDialOpts(), state.Policy(nil))
2730 c.Assert(err, gc.IsNil)2730 c.Assert(err, gc.IsNil)
2731 defer st.Close()2731 defer st.Close()
27322732
27332733
=== modified file 'state/unit_test.go'
--- state/unit_test.go 2014-01-15 06:52:56 +0000
+++ state/unit_test.go 2014-02-20 08:24:11 +0000
@@ -633,7 +633,7 @@
633 c.Assert(err, gc.IsNil)633 c.Assert(err, gc.IsNil)
634634
635 info := state.TestingStateInfo()635 info := state.TestingStateInfo()
636 st, err := state.Open(info, state.TestingDialOpts())636 st, err := state.Open(info, state.TestingDialOpts(), state.Policy(nil))
637 c.Assert(err, gc.IsNil)637 c.Assert(err, gc.IsNil)
638 defer st.Close()638 defer st.Close()
639 // Turn on fully-authenticated mode.639 // Turn on fully-authenticated mode.
@@ -663,7 +663,7 @@
663 // Connect as the machine entity.663 // Connect as the machine entity.
664 info.Tag = m.Tag()664 info.Tag = m.Tag()
665 info.Password = "foo"665 info.Password = "foo"
666 st1, err := state.Open(info, state.TestingDialOpts())666 st1, err := state.Open(info, state.TestingDialOpts(), state.Policy(nil))
667 c.Assert(err, gc.IsNil)667 c.Assert(err, gc.IsNil)
668 defer st1.Close()668 defer st1.Close()
669669
@@ -678,7 +678,7 @@
678 // that entity, change the password for a new unit.678 // that entity, change the password for a new unit.
679 info.Tag = unit.Tag()679 info.Tag = unit.Tag()
680 info.Password = "bar"680 info.Password = "bar"
681 st2, err := state.Open(info, state.TestingDialOpts())681 st2, err := state.Open(info, state.TestingDialOpts(), state.Policy(nil))
682 c.Assert(err, gc.IsNil)682 c.Assert(err, gc.IsNil)
683 defer st2.Close()683 defer st2.Close()
684684
685685
=== modified file 'worker/provisioner/provisioner_test.go'
--- worker/provisioner/provisioner_test.go 2014-01-28 12:29:38 +0000
+++ worker/provisioner/provisioner_test.go 2014-02-20 08:24:11 +0000
@@ -57,6 +57,12 @@
57}57}
5858
59func (s *CommonProvisionerSuite) SetUpTest(c *gc.C) {59func (s *CommonProvisionerSuite) SetUpTest(c *gc.C) {
60 // Disable the default state policy, because the
61 // provisioner needs to be able to test pathological
62 // scenarios where a machine exists in state with
63 // invalid environment config.
64 dummy.SetStatePolicy(nil)
65
60 s.JujuConnSuite.SetUpTest(c)66 s.JujuConnSuite.SetUpTest(c)
61 // Create the operations channel with more than enough space67 // Create the operations channel with more than enough space
62 // for those tests that don't listen on it.68 // for those tests that don't listen on it.

Subscribers

People subscribed via source and target branches

to status/vote changes: