Merge lp:~jameinel/juju-core/api-set-creds-1199915 into lp:~go-bot/juju-core/trunk

Proposed by John A Meinel
Status: Merged
Approved by: John A Meinel
Approved revision: no longer in the source branch.
Merged at revision: 1480
Proposed branch: lp:~jameinel/juju-core/api-set-creds-1199915
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 591 lines (+461/-10)
6 files modified
cmd/jujud/machine.go (+17/-0)
cmd/jujud/machine_test.go (+1/-1)
cmd/jujud/unit.go (+12/-1)
cmd/jujud/unit_test.go (+11/-8)
cmd/jujud/upgradevalidation.go (+85/-0)
cmd/jujud/upgradevalidation_test.go (+335/-0)
To merge this branch: bzr merge lp:~jameinel/juju-core/api-set-creds-1199915
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+174620@code.launchpad.net

Commit message

cmd/jujud: Ensure valid agent.conf after upgrade

This fixes the agent.conf files for the machine and unit agents after upgrading from 1.10 to 1.11. I ran into a few differences, the basics are:

1) Machine Agent: machine 0 always starts the state worker, when
upgrading it has an APIInfo section, but it
  1a) Didn't have Machine.SetPassword in the DB set so we didn't have a
  way to connect via the api
  1b) Didn't have a password set in apiinfo, and 1.10 had already
  changed the password after connecting to the MongoDB
  1c) Had changed their password already

So to solve this one, we grab the Password from StateInfo.Password,
call Agent.SetPassword(), and set the value in APIInfo.Password.

2) Machine Agent: machine != 0. Were only connecting to the state after
connecting to the API. Because of (1a,b) they couldn't connect to
notice that they had work to do.

So on top of what we did for (1), I added a step of:

  2a) If we are unable to connect to the API, call ensureStateWorker().
  This gives us an opportunity to set our API password and save it in
  agent.conf.

3) Unit agents:

  3a) Didn't even have an APIInfo section.
  3b) Did have a Mongo password, and had reset their Mongo Password

So we copy the state info into the api info section, and change the
port. I only use the default port (17070). I think it would be possible
to query something in state for what the official API is
(state.State.Environ().APIInfo() comes to mind, but I'm not sure what
the correct value is.)

I felt that changing the port would solve all current 1.10 users, and
if we change things in the future, we can change the code. (I also just
discovered that we have Conf.APIPort which may or may not be set in all
the various permutations, I don't have the energy to track down 3
different versions now.)

4) Unit agents bootstrapped with 1.11.2 tools

  4a) Would have a valid APIInfo section
  4b) But would not have StateInfo.Password or APIInfo.Password set.
  This is because changing the Password now occurs when we connect to
  the API, not when we first connect to the Mongo DB.

I added checks for this, to make sure that 1.11.3 doesn't cause us to
call Unit.SetPassword("").

I did a few upgrades from 1.10 => this code, and 1.11.2 => this code,
and straight bootstrapping from this code. It all seems to be up and
running. I don't think I tried bootstrapping 1.10 with this code and
upgrading.

I hopefully captured all of these config edge cases in the test suite,
so we don't have to test all the permutations manually in the future.
We also need to think very carefully when we add stuff about how we
will get upgrade to notice. I'm starting to think we want to tweak how
upgrade works. So instead of just replacing the tools, it has a
"juju upgraded" or some other command that runs the first time you are
upgraded so we can put logic into that place and have it centralized.

https://codereview.appspot.com/11137044/

Description of the change

cmd/jujud: Ensure valid agent.conf after upgrade

This fixes the agent.conf files for the machine and unit agents after upgrading
from 1.10 to 1.11. I ran into a few differences, the basics are:

1) Machine Agent: machine 0 always starts the state worker, when
upgrading it has an APIInfo section, but it
  1a) Didn't have Machine.SetPassword in the DB set so we didn't have a
  way to connect via the api
  1b) Didn't have a password set in apiinfo, and 1.10 had already
  changed the password after connecting to the MongoDB
  1c) Had changed their password already

So to solve this one, we grab the Password from StateInfo.Password,
call Agent.SetPassword(), and set the value in APIInfo.Password.

2) Machine Agent: machine != 0. Were only connecting to the state after
connecting to the API. Because of (1a,b) they couldn't connect to
notice that they had work to do.

So on top of what we did for (1), I added a step of:

  2a) If we are unable to connect to the API, call ensureStateWorker().
  This gives us an opportunity to set our API password and save it in
  agent.conf.

3) Unit agents:

  3a) Didn't even have an APIInfo section.
  3b) Did have a Mongo password, and had reset their Mongo Password

So we copy the state info into the api info section, and change the
port. I only use the default port (17070). I think it would be possible
to query something in state for what the official API is
(state.State.Environ().APIInfo() comes to mind, but I'm not sure what
the correct value is.)

I felt that changing the port would solve all current 1.10 users, and
if we change things in the future, we can change the code. (I also just
discovered that we have Conf.APIPort which may or may not be set in all
the various permutations, I don't have the energy to track down 3
different versions now.)

4) Unit agents bootstrapped with 1.11.2 tools

  4a) Would have a valid APIInfo section
  4b) But would not have StateInfo.Password or APIInfo.Password set.
  This is because changing the Password now occurs when we connect to
  the API, not when we first connect to the Mongo DB.

I added checks for this, to make sure that 1.11.3 doesn't cause us to
call Unit.SetPassword("").

I did a few upgrades from 1.10 => this code, and 1.11.2 => this code,
and straight bootstrapping from this code. It all seems to be up and
running. I don't think I tried bootstrapping 1.10 with this code and
upgrading.

I hopefully captured all of these config edge cases in the test suite,
so we don't have to test all the permutations manually in the future.
We also need to think very carefully when we add stuff about how we
will get upgrade to notice. I'm starting to think we want to tweak how
upgrade works. So instead of just replacing the tools, it has a
"juju upgraded" or some other command that runs the first time you are
upgraded so we can put logic into that place and have it centralized.

https://codereview.appspot.com/11137044/

To post a comment you must log in.
Revision history for this message
John A Meinel (jameinel) wrote :
Download full text (3.3 KiB)

Reviewers: mp+174620_code.launchpad.net,

Message:
Please take a look.

Description:
cmd/jujud: Ensure valid agent.conf after upgrade

This fixes the agent.conf files for the machine and unit agents after
upgrading
from 1.10 to 1.11. I ran into a few differences, the basics are:

1) Machine Agent: machine 0 always starts the state worker, when
upgrading it has an APIInfo section, but it
   1a) Didn't have Machine.SetPassword in the DB set so we didn't have a
   way to connect via the api
   1b) Didn't have a password set in apiinfo, and 1.10 had already
   changed the password after connecting to the MongoDB
   1c) Had changed their password already

So to solve this one, we grab the Password from StateInfo.Password,
call Agent.SetPassword(), and set the value in APIInfo.Password.

2) Machine Agent: machine != 0. Were only connecting to the state after
connecting to the API. Because of (1a,b) they couldn't connect to
notice that they had work to do.

So on top of what we did for (1), I added a step of:

   2a) If we are unable to connect to the API, call ensureStateWorker().
   This gives us an opportunity to set our API password and save it in
   agent.conf.

3) Unit agents:

   3a) Didn't even have an APIInfo section.
   3b) Did have a Mongo password, and had reset their Mongo Password

So we copy the state info into the api info section, and change the
port. I only use the default port (17070). I think it would be possible
to query something in state for what the official API is
(state.State.Environ().APIInfo() comes to mind, but I'm not sure what
the correct value is.)

I felt that changing the port would solve all current 1.10 users, and
if we change things in the future, we can change the code. (I also just
discovered that we have Conf.APIPort which may or may not be set in all
the various permutations, I don't have the energy to track down 3
different versions now.)

4) Unit agents bootstrapped with 1.11.2 tools

   4a) Would have a valid APIInfo section
   4b) But would not have StateInfo.Password or APIInfo.Password set.
   This is because changing the Password now occurs when we connect to
   the API, not when we first connect to the Mongo DB.

I added checks for this, to make sure that 1.11.3 doesn't cause us to
call Unit.SetPassword("").

I did a few upgrades from 1.10 => this code, and 1.11.2 => this code,
and straight bootstrapping from this code. It all seems to be up and
running. I don't think I tried bootstrapping 1.10 with this code and
upgrading.

I hopefully captured all of these config edge cases in the test suite,
so we don't have to test all the permutations manually in the future.
We also need to think very carefully when we add stuff about how we
will get upgrade to notice. I'm starting to think we want to tweak how
upgrade works. So instead of just replacing the tools, it has a
"juju upgraded" or some other command that runs the first time you are
upgraded so we can put logic into that place and have it centralized.

https://code.launchpad.net/~jameinel/juju-core/api-set-creds-1199915/+merge/174620

(do not edit description out of merge proposal)

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

Affe...

Read more...

Revision history for this message
Frank Mueller (themue) wrote :

LGTM, only few remarks.

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/upgradevalidation.go
File cmd/jujud/upgradevalidation.go (right):

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/upgradevalidation.go#newcode91
cmd/jujud/upgradevalidation.go:91: //???
We should at least log the error (which should not happen).

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/upgradevalidation.go#newcode134
cmd/jujud/upgradevalidation.go:134: // This is unexpected as all
AgentState objects (Machine and Unit)
Isn't this worth a panic()? How does the system continue with this
error?

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/upgradevalidation_test.go
File cmd/jujud/upgradevalidation_test.go (right):

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/upgradevalidation_test.go#newcode47
cmd/jujud/upgradevalidation_test.go:47: func (s
*UpgradeValidationMachineSuite) Create1_10Machine(c *gc.C)
(*state.Machine, *agent.Conf) {
Needed a few seconds to interpret 1_10 right. ;) How about
Create1Dot10Machine?

https://codereview.appspot.com/11137044/

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

Looking very nice, thanks for this; I have a couple of questions.

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/machine.go
File cmd/jujud/machine.go (right):

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/machine.go#newcode145
cmd/jujud/machine.go:145: // TODO: Once we can reliably trust that we
have API passwords
Give this a TODO(jam), it's nice to be able to know who to ask even when
blame info is lost through unrelated changes.

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/machine_test.go
File cmd/jujud/machine_test.go (right):

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/machine_test.go#newcode73
cmd/jujud/machine_test.go:73: err = conf.Write()
thanks, good catch

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/upgradevalidation.go
File cmd/jujud/upgradevalidation.go (right):

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/upgradevalidation.go#newcode110
cmd/jujud/upgradevalidation.go:110: }
Can you use State.APIAddresses for this? Or something like that...
defined in state/open.go I think.

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/upgradevalidation.go#newcode134
cmd/jujud/upgradevalidation.go:134: // This is unexpected as all
AgentState objects (Machine and Unit)
On 2013/07/15 13:31:43, mue wrote:
> Isn't this worth a panic()? How does the system continue with this
error?

Yeah, I think this is a panic. Someone completely screwed up, do not
pass go, do not collect $200.

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/upgradevalidation.go#newcode165
cmd/jujud/upgradevalidation.go:165: if err :=
setter.SetPassword(password); err != nil {
Hmm. The original idea IIRC was that we save the oldPassword (for
recovery purposes), write the new password to disk, and then really set
the new password. Not quite sure this mechanism and that one are
properly joined-up?

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/upgradevalidation_test.go
File cmd/jujud/upgradevalidation_test.go (right):

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/upgradevalidation_test.go#newcode47
cmd/jujud/upgradevalidation_test.go:47: func (s
*UpgradeValidationMachineSuite) Create1_10Machine(c *gc.C)
(*state.Machine, *agent.Conf) {
On 2013/07/15 13:31:43, mue wrote:
> Needed a few seconds to interpret 1_10 right. ;) How about
Create1Dot10Machine?

-1 myself, but happy to follow consensus

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/upgradevalidation_test.go#newcode226
cmd/jujud/upgradevalidation_test.go:226: func (s
*UpgradeValidationUnitSuite) TestEnsureAPIInfo111(c *gc.C) {
1_11 for consistency with the above?

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/upgradevalidation_test.go#newcode247
cmd/jujud/upgradevalidation_test.go:247: func (s
*UpgradeValidationUnitSuite) TestEnsureAPIInfo111Noop(c *gc.C) {
ditto

https://codereview.appspot.com/11137044/

Revision history for this message
John A Meinel (jameinel) wrote :

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/upgradevalidation.go
File cmd/jujud/upgradevalidation.go (right):

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/upgradevalidation.go#newcode165
cmd/jujud/upgradevalidation.go:165: if err :=
setter.SetPassword(password); err != nil {
On 2013/07/15 13:53:51, fwereade wrote:
> Hmm. The original idea IIRC was that we save the oldPassword (for
recovery
> purposes), write the new password to disk, and then really set the new
password.
> Not quite sure this mechanism and that one are properly joined-up?

We already have OldPassword available (or StateInfo.Password). We use
the fact that APIInfo.Password isn't set yet to indicate we need to call
state.State.SetPassword().

If we write to disk first, and then crash, the next startup won't call
SetPassword.

I did think carefully about it and had a comment, but if you feel it
needs more clarification I can try to do so.

https://codereview.appspot.com/11137044/

Revision history for this message
John A Meinel (jameinel) wrote :

Please take a look.

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/machine.go
File cmd/jujud/machine.go (right):

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/machine.go#newcode145
cmd/jujud/machine.go:145: // TODO: Once we can reliably trust that we
have API passwords
On 2013/07/15 13:53:51, fwereade wrote:
> Give this a TODO(jam), it's nice to be able to know who to ask even
when blame
> info is lost through unrelated changes.

Done.

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/upgradevalidation.go
File cmd/jujud/upgradevalidation.go (right):

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/upgradevalidation.go#newcode110
cmd/jujud/upgradevalidation.go:110: }
On 2013/07/15 13:53:51, fwereade wrote:
> Can you use State.APIAddresses for this? Or something like that...
defined in
> state/open.go I think.

I don't have easy access to a raw state.State object here (I currently
take something like state.Machine or state.Unit)
Looking at the implementation of state.APIAddresses it is just doing
what I'm doing only without "net". Explicitly searching for ":" and
splitting on it.

So I will pull in conf.APIPort(), but I'd rather avoid reworking what
objects are in play just to get a function that may or may not properly
handle ipv6 in the future.

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/upgradevalidation.go#newcode134
cmd/jujud/upgradevalidation.go:134: // This is unexpected as all
AgentState objects (Machine and Unit)
On 2013/07/15 13:53:51, fwereade wrote:
> On 2013/07/15 13:31:43, mue wrote:
> > Isn't this worth a panic()? How does the system continue with this
error?

> Yeah, I think this is a panic. Someone completely screwed up, do not
pass go, do
> not collect $200.

Done.

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/upgradevalidation_test.go
File cmd/jujud/upgradevalidation_test.go (right):

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/upgradevalidation_test.go#newcode47
cmd/jujud/upgradevalidation_test.go:47: func (s
*UpgradeValidationMachineSuite) Create1_10Machine(c *gc.C)
(*state.Machine, *agent.Conf) {
On 2013/07/15 13:53:51, fwereade wrote:
> On 2013/07/15 13:31:43, mue wrote:
> > Needed a few seconds to interpret 1_10 right. ;) How about
> Create1Dot10Machine?

> -1 myself, but happy to follow consensus

I kept it as 1_10 and matched the 111 to 1_11 later in the file.

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/upgradevalidation_test.go#newcode226
cmd/jujud/upgradevalidation_test.go:226: func (s
*UpgradeValidationUnitSuite) TestEnsureAPIInfo111(c *gc.C) {
On 2013/07/15 13:53:51, fwereade wrote:
> 1_11 for consistency with the above?

Done.

https://codereview.appspot.com/11137044/diff/1/cmd/jujud/upgradevalidation_test.go#newcode247
cmd/jujud/upgradevalidation_test.go:247: func (s
*UpgradeValidationUnitSuite) TestEnsureAPIInfo111Noop(c *gc.C) {
On 2013/07/15 13:53:51, fwereade wrote:
> ditto

Done.

https://codereview.appspot.com/11137044/

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

LGTM, but please do a little bit of investigation to see if there's a
reaosnably convenient way to reuse the state.APIAddresses code. If not I
can live with it assuming we manage to get rid of this workaround sooner
rather than later -- and I can't see why we *would* keep it around for a
long time, so I'm happy accepting your judgment here.

https://codereview.appspot.com/11137044/

Revision history for this message
John A Meinel (jameinel) wrote :
Revision history for this message
John A Meinel (jameinel) wrote :

On 2013/07/16 13:42:49, jameinel wrote:
> Please take a look.

I did end up doing state.APIAddresses.

https://codereview.appspot.com/11137044/

Revision history for this message
Go Bot (go-bot) wrote :

Attempt to merge into lp:juju-core failed due to conflicts:

text conflict in cmd/jujud/upgradevalidation.go

Revision history for this message
Go Bot (go-bot) wrote :
Download full text (95.5 KiB)

The attempt to merge lp:~jameinel/juju-core/api-set-creds-1199915 into lp:juju-core failed. Below is the output from the failed tests.

----------------------------------------------------------------------
PANIC: agent_test.go:0: openSuite.TearDownTest

... Panic: watcher iteration error: unauthorized db:juju ns:juju.txns.log lock type:0 client:127.0.0.1 (PC=0x41175F)

/usr/lib/go/src/pkg/runtime/proc.c:1443
  in panic
/home/tarmac/trees/src/launchpad.net/juju-core/environs/dummy/environs.go:213
  in environState.destroy
/home/tarmac/trees/src/launchpad.net/juju-core/environs/dummy/environs.go:189
  in Reset
/home/tarmac/trees/src/launchpad.net/juju-core/juju/testing/conn.go:235
  in JujuConnSuite.tearDownConn
/home/tarmac/trees/src/launchpad.net/juju-core/juju/testing/conn.go:134
  in JujuConnSuite.TearDownTest

----------------------------------------------------------------------
PANIC: agent_test.go:384: openSuite.TestOpenAPIFallbackPassword

[LOG] 86.66806 INFO juju environs/testing: uploading FAKE tools 1.11.3-precise-amd64
[LOG] 86.66819 INFO juju environs: reading tools with major version 1
[LOG] 86.66821 DEBUG juju.environs.tools reading v1.* tools
[LOG] 86.66823 INFO juju environs: falling back to public bucket
[LOG] 86.66824 DEBUG juju.environs.tools reading v1.* tools
[LOG] 86.66829 DEBUG juju.environs.tools found 1.11.3-precise-amd64
[LOG] 86.66831 INFO juju environs: filtering tools by series: precise
[LOG] 86.66834 INFO juju environs: filtering tools by version: 1.11.3
[LOG] 86.66837 INFO juju environs/dummy: would pick tools from 1.11.3-precise-amd64
[LOG] 86.69442 INFO juju state: opening state; mongo addresses: ["localhost:38047"]; entity ""
[LOG] 86.69843 INFO juju state: connection established
[LOG] 91.48213 INFO juju state: initializing environment
[LOG] 70.72201 INFO juju state/api: listening on "127.0.0.1:50666"
[LOG] 70.75192 INFO juju state: opening state; mongo addresses: ["localhost:38047"]; entity ""
[LOG] 70.75555 INFO juju state: connection established
[LOG] 70.75608 INFO juju juju: authorization error while connecting to state server; retrying
[LOG] 70.75614 INFO juju state: opening state; mongo addresses: ["localhost:38047"]; entity ""
[LOG] 70.75931 INFO juju state: connection established
[LOG] 70.81147 INFO juju state/api: dialing "wss://127.0.0.1:50666/"
[LOG] 70.81616 INFO juju state/api: connection established
[LOG] 70.81640 INFO juju rpc: discarding action method reflect.Method{Name:"apiRootForEntity", PkgPath:"launchpad.net/juju-core/state/apiserver", Type:(*reflect.commonType)(0x800ad8), Func:reflect.Value{typ:(*reflect.commonType)(0x800ad8), val:(unsafe.Pointer)(0x6b547b), flag:0x131}, Index:1}
[LOG] 70.81651 DEBUG juju rpc/jsoncodec: <- {"RequestId":1,"Type":"Admin","Request":"Login","Params":{"AuthTag":"user-admin","Password":"dummy-secret"}}
[LOG] 70.81700 INFO juju rpc: discarding obtainer method reflect.Method{Name:"AuthClient", PkgPath:"", Type:(*reflect.commonType)(0x746b50), Func:reflect.Value{typ:(*reflect.commonType)(0x746b50), val:(unsafe.Pointer)(0x6b68d1), flag:0x130}, Index:1}
[LOG] 70.81703 INFO juju rpc: discarding obtainer method reflect.Method{Name:"AuthEnvironManager", PkgPath:"", ...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cmd/jujud/machine.go'
2--- cmd/jujud/machine.go 2013-07-17 02:38:48 +0000
3+++ cmd/jujud/machine.go 2013-07-17 07:05:30 +0000
4@@ -116,6 +116,7 @@
5 // instances of the API server have been started, we
6 // should follow the normal course of things and ignore
7 // the fact that this was once the bootstrap machine.
8+ log.Infof("Starting StateWorker for machine-0")
9 ensureStateWorker()
10 }
11 a.runner.StartWorker("api", func() (worker.Worker, error) {
12@@ -144,6 +145,17 @@
13 func (a *MachineAgent) APIWorker(ensureStateWorker func()) (worker.Worker, error) {
14 st, entity, err := openAPIState(a.Conf.Conf, a)
15 if err != nil {
16+ // There was an error connecting to the API,
17+ // https://launchpad.net/bugs/1199915 means that we may just
18+ // not have an API password set. So force a state connection at
19+ // this point.
20+ // TODO(jam): Once we can reliably trust that we have API
21+ // passwords set, and we no longer need state
22+ // connections (and possibly agents will be blocked
23+ // from connecting directly to state) we can remove
24+ // this. Currently needed because 1.10 does not set
25+ // the API password and 1.11 requires it
26+ ensureStateWorker()
27 return nil, err
28 }
29 m := entity.(*machineagent.Machine)
30@@ -167,6 +179,11 @@
31 if err != nil {
32 return nil, err
33 }
34+ // If this fails, other bits will fail, so we just log the error, and
35+ // let the other failures actually restart runners
36+ if err := EnsureAPIInfo(a.Conf.Conf, st, entity); err != nil {
37+ log.Warningf("failed to EnsureAPIInfo: %v", err)
38+ }
39 reportOpenedState(st)
40 m := entity.(*state.Machine)
41 // TODO(rog) use more discriminating test for errors
42
43=== modified file 'cmd/jujud/machine_test.go'
44--- cmd/jujud/machine_test.go 2013-07-15 23:01:31 +0000
45+++ cmd/jujud/machine_test.go 2013-07-17 07:05:30 +0000
46@@ -70,7 +70,7 @@
47 c.Assert(err, IsNil)
48 conf, tools := s.agentSuite.primeAgent(c, state.MachineTag(m.Id()), "machine-password")
49 conf.MachineNonce = state.BootstrapNonce
50- conf.Write()
51+ err = conf.Write()
52 c.Assert(err, IsNil)
53 return m, conf, tools
54 }
55
56=== modified file 'cmd/jujud/unit.go'
57--- cmd/jujud/unit.go 2013-06-27 09:26:02 +0000
58+++ cmd/jujud/unit.go 2013-07-17 07:05:30 +0000
59@@ -5,15 +5,20 @@
60
61 import (
62 "fmt"
63+
64 "launchpad.net/gnuflag"
65+ "launchpad.net/loggo"
66+ "launchpad.net/tomb"
67+
68 "launchpad.net/juju-core/cmd"
69 "launchpad.net/juju-core/state"
70 "launchpad.net/juju-core/state/api"
71 "launchpad.net/juju-core/worker"
72 "launchpad.net/juju-core/worker/uniter"
73- "launchpad.net/tomb"
74 )
75
76+var agentLogger = loggo.GetLogger("juju.jujud")
77+
78 // UnitAgent is a cmd.Command responsible for running a unit agent.
79 type UnitAgent struct {
80 cmd.CommandBase
81@@ -63,6 +68,7 @@
82 if err := a.Conf.read(a.Tag()); err != nil {
83 return err
84 }
85+ agentLogger.Infof("unit agent %v start", a.Tag())
86 a.runner.StartWorker("toplevel", func() (worker.Worker, error) {
87 // TODO(rog) go1.1: use method expression
88 return a.Workers()
89@@ -78,6 +84,11 @@
90 if err != nil {
91 return nil, err
92 }
93+ if err := EnsureAPIInfo(a.Conf.Conf, st, entity); err != nil {
94+ // We suppress this error, because it is probably more interesting
95+ // to see other failures, but we log it, in case it is a root cause
96+ agentLogger.Warningf("error while calling EnsureAPIInfo: %v", err)
97+ }
98 unit := entity.(*state.Unit)
99 dataDir := a.Conf.DataDir
100 runner := worker.NewRunner(allFatal, moreImportant)
101
102=== modified file 'cmd/jujud/unit_test.go'
103--- cmd/jujud/unit_test.go 2013-07-09 11:31:00 +0000
104+++ cmd/jujud/unit_test.go 2013-07-17 07:05:30 +0000
105@@ -85,14 +85,9 @@
106 c.Assert(err, ErrorMatches, `unrecognized args: \["thundering typhoons"\]`)
107 }
108
109-func (s *UnitSuite) TestRunStop(c *C) {
110- unit, _, _ := s.primeAgent(c)
111- a := s.newAgent(c, unit)
112- go func() { c.Check(a.Run(nil), IsNil) }()
113- defer func() { c.Check(a.Stop(), IsNil) }()
114+func waitForUnitStarted(stateConn *state.State, unit *state.Unit, c *C) {
115 timeout := time.After(5 * time.Second)
116
117-waitStarted:
118 for {
119 select {
120 case <-timeout:
121@@ -108,9 +103,9 @@
122 continue
123 case params.StatusStarted:
124 c.Logf("started!")
125- break waitStarted
126+ return
127 case params.StatusDown:
128- s.State.StartSync()
129+ stateConn.StartSync()
130 c.Logf("unit is still down")
131 default:
132 c.Fatalf("unexpected status %s %s", st, info)
133@@ -119,6 +114,14 @@
134 }
135 }
136
137+func (s *UnitSuite) TestRunStop(c *C) {
138+ unit, _, _ := s.primeAgent(c)
139+ a := s.newAgent(c, unit)
140+ go func() { c.Check(a.Run(nil), IsNil) }()
141+ defer func() { c.Check(a.Stop(), IsNil) }()
142+ waitForUnitStarted(s.State, unit, c)
143+}
144+
145 func (s *UnitSuite) TestUpgrade(c *C) {
146 unit, _, currentTools := s.primeAgent(c)
147 a := s.newAgent(c, unit)
148
149=== modified file 'cmd/jujud/upgradevalidation.go'
150--- cmd/jujud/upgradevalidation.go 2013-07-17 02:35:07 +0000
151+++ cmd/jujud/upgradevalidation.go 2013-07-17 07:05:30 +0000
152@@ -10,10 +10,12 @@
153
154 "launchpad.net/loggo"
155
156+ "launchpad.net/juju-core/agent"
157 "launchpad.net/juju-core/container/lxc"
158 "launchpad.net/juju-core/environs/provider"
159 "launchpad.net/juju-core/instance"
160 "launchpad.net/juju-core/state"
161+ "launchpad.net/juju-core/state/api"
162 "launchpad.net/juju-core/utils"
163 "launchpad.net/juju-core/utils/fslock"
164 )
165@@ -89,3 +91,86 @@
166 // only supports debian-based anyway
167 return utils.AptGetInstall("lxc")
168 }
169+
170+type passwordSetter interface {
171+ SetPassword(password string) error
172+}
173+
174+// apiAddresser just implements APIAddresses
175+// This is implemented by the standard *state.State object
176+type apiAddresser interface {
177+ APIAddresses() ([]string, error)
178+}
179+
180+func apiInfoFromStateInfo(stInfo *state.Info, stConn apiAddresser) *api.Info {
181+ addrs, err := stConn.APIAddresses()
182+ if err != nil {
183+ validationLogger.Warningf("Unable to get the list of valid API Addresses: %v", err)
184+ }
185+ return &api.Info{
186+ Addrs: addrs,
187+ CACert: stInfo.CACert,
188+ Tag: stInfo.Tag,
189+ Password: stInfo.Password,
190+ }
191+}
192+
193+// EnsureAPIInfo makes sure we can connect as an agent to the API server 1.10
194+// did not set an API password for machine agents, 1.11 sets it the same as the
195+// mongo password. 1.10 also does not set any API Info at all for Unit agents.
196+// conf is the agent.conf for this machine/unit agent. agentConn is the direct
197+// connection to the State database
198+func EnsureAPIInfo(conf *agent.Conf, stConn *state.State, agentConn AgentState) error {
199+ // In 1.10 Machine Agents will have an API Info section, but will not
200+ // have properly configured the Password field, so when upgrading to
201+ // 1.11 we must set that field
202+ // In 1.10 Unit Agents will not have an API Info section at all, so we
203+ // must set everything from the StateInfo (correcting the Addrs for API
204+ // port vs Mongo port)
205+ // A new 1.11 Unit Agent will have an OldPassword but no Password set
206+ // (in StateInfo or APIInfo). Because we only change the password on
207+ // API connect, not on DB connect. We don't want to make extra
208+ // SetPassword calls when we already have a valid password set
209+ if conf.APIInfo != nil && (conf.APIInfo.Password != "" || conf.StateInfo.Password == "") {
210+ // We must have set it earlier
211+ return nil
212+ }
213+ setter, ok := agentConn.(passwordSetter)
214+ if !ok {
215+ panic("AgentState is missing a SetPassword method?")
216+ }
217+ if conf.APIInfo == nil {
218+ // Unit agents didn't get any APIInfo in 1.10
219+ conf.APIInfo = apiInfoFromStateInfo(conf.StateInfo, stConn)
220+ validationLogger.Infof(
221+ "agent.conf APIInfo is not set. Setting to {Addrs: %s, Tag: %s}",
222+ conf.APIInfo.Addrs,
223+ conf.APIInfo.Tag,
224+ )
225+ } else {
226+ validationLogger.Infof("agent.conf APIInfo password is not set. Setting to state password")
227+ conf.APIInfo.Password = conf.StateInfo.Password
228+ }
229+ password := conf.StateInfo.Password
230+ if password == "" {
231+ // In 1.11 we don't set a new password on connect (because it is
232+ // done in the API code, and we don't have any API workers).
233+ // We want to make sure the API user has the correct password
234+ // (OldPassword). We *don't* want to set APIInfo.Password
235+ // because then when we do connect via the API we wouldn't
236+ // change the password. So this is only used for SetPassword
237+ validationLogger.Infof(
238+ "agent.conf StateInfo password is \"\". Setting Agent password for %s to OldPassword",
239+ conf.APIInfo.Tag)
240+ password = conf.OldPassword
241+ }
242+ // We set the actual password before writing it to disk, because
243+ // otherwise we would not set it correctly in the future
244+ if err := setter.SetPassword(password); err != nil {
245+ return err
246+ }
247+ if err := conf.Write(); err != nil {
248+ return err
249+ }
250+ return nil
251+}
252
253=== added file 'cmd/jujud/upgradevalidation_test.go'
254--- cmd/jujud/upgradevalidation_test.go 1970-01-01 00:00:00 +0000
255+++ cmd/jujud/upgradevalidation_test.go 2013-07-17 07:05:30 +0000
256@@ -0,0 +1,335 @@
257+// Copyright 2012, 2013 Canonical Ltd.
258+// Licensed under the AGPLv3, see LICENCE file for details.
259+
260+package main
261+
262+import (
263+ "fmt"
264+ "time"
265+
266+ gc "launchpad.net/gocheck"
267+
268+ "launchpad.net/juju-core/agent"
269+ "launchpad.net/juju-core/constraints"
270+ "launchpad.net/juju-core/container/lxc"
271+ "launchpad.net/juju-core/state"
272+ "launchpad.net/juju-core/state/api"
273+ "launchpad.net/juju-core/testing"
274+)
275+
276+var _ = gc.Suite(&UpgradeValidationMachineSuite{})
277+
278+type UpgradeValidationMachineSuite struct {
279+ agentSuite
280+ lxc.TestSuite
281+}
282+
283+func (s *UpgradeValidationMachineSuite) SetUpSuite(c *gc.C) {
284+ s.agentSuite.SetUpSuite(c)
285+ s.TestSuite.SetUpSuite(c)
286+}
287+
288+func (s *UpgradeValidationMachineSuite) TearDownSuite(c *gc.C) {
289+ s.TestSuite.TearDownSuite(c)
290+ s.agentSuite.TearDownSuite(c)
291+}
292+
293+func (s *UpgradeValidationMachineSuite) SetUpTest(c *gc.C) {
294+ s.agentSuite.SetUpTest(c)
295+ s.TestSuite.SetUpTest(c)
296+}
297+
298+func (s *UpgradeValidationMachineSuite) TearDownTest(c *gc.C) {
299+ s.TestSuite.TearDownTest(c)
300+ s.agentSuite.TearDownTest(c)
301+}
302+
303+func (s *UpgradeValidationMachineSuite) Create1_10Machine(c *gc.C) (*state.Machine, *agent.Conf) {
304+ // Given the current connection to state, create a new machine, and 'reset'
305+ // the configuration so that it looks like how juju 1.10 would have
306+ // configured it
307+ m, err := s.State.InjectMachine("series", constraints.Value{}, "ardbeg-0", state.JobHostUnits)
308+ c.Assert(err, gc.IsNil)
309+ err = m.SetMongoPassword("machine-password")
310+ c.Assert(err, gc.IsNil)
311+ // We intentionally do *not* call m.SetPassword here, as it was not
312+ // done in 1.10, we also intentionally set the APIInfo.Password back to
313+ // the empty string.
314+ conf, _ := s.agentSuite.primeAgent(c, m.Tag(), "machine-password")
315+ conf.MachineNonce = state.BootstrapNonce
316+ conf.APIInfo.Password = ""
317+ conf.Write()
318+ c.Assert(conf.StateInfo.Tag, gc.Equals, m.Tag())
319+ c.Assert(conf.StateInfo.Password, gc.Equals, "machine-password")
320+ c.Assert(err, gc.IsNil)
321+ return m, conf
322+}
323+
324+func (s *UpgradeValidationMachineSuite) TestEnsureAPIInfo(c *gc.C) {
325+ m, conf := s.Create1_10Machine(c)
326+ // Opening the API should fail as is
327+ apiState, newPassword, err := conf.OpenAPI(api.DialOpts{})
328+ c.Assert(apiState, gc.IsNil)
329+ c.Assert(newPassword, gc.Equals, "")
330+ c.Assert(err, gc.NotNil)
331+ c.Assert(err, gc.ErrorMatches, "invalid entity name or password")
332+
333+ err = EnsureAPIInfo(conf, s.State, m)
334+ c.Assert(err, gc.IsNil)
335+ // After EnsureAPIInfo we should be able to connect
336+ apiState, newPassword, err = conf.OpenAPI(api.DialOpts{})
337+ c.Assert(err, gc.IsNil)
338+ c.Assert(apiState, gc.NotNil)
339+ // We shouldn't need to set a new password
340+ c.Assert(newPassword, gc.Equals, "")
341+}
342+
343+func (s *UpgradeValidationMachineSuite) TestEnsureAPIInfoNoOp(c *gc.C) {
344+ m, conf := s.Create1_10Machine(c)
345+ // Set the API password to something, and record it, ensure that
346+ // EnsureAPIInfo doesn't change it on us
347+ m.SetPassword("frobnizzle")
348+ conf.APIInfo.Password = "frobnizzle"
349+ // We matched them, so we should be able to open the API
350+ apiState, newPassword, err := conf.OpenAPI(api.DialOpts{})
351+ c.Assert(apiState, gc.NotNil)
352+ c.Assert(newPassword, gc.Equals, "")
353+ c.Assert(err, gc.IsNil)
354+ apiState.Close()
355+
356+ err = EnsureAPIInfo(conf, s.State, m)
357+ c.Assert(err, gc.IsNil)
358+ // After EnsureAPIInfo we should still be able to connect
359+ apiState, newPassword, err = conf.OpenAPI(api.DialOpts{})
360+ c.Assert(err, gc.IsNil)
361+ c.Assert(apiState, gc.NotNil)
362+ // We shouldn't need to set a new password
363+ c.Assert(newPassword, gc.Equals, "")
364+ // The password hasn't been changed
365+ c.Assert(conf.APIInfo.Password, gc.Equals, "frobnizzle")
366+}
367+
368+// Test that MachineAgent enforces the API password on startup
369+func (s *UpgradeValidationMachineSuite) TestAgentEnsuresAPIInfo(c *gc.C) {
370+ m, _ := s.Create1_10Machine(c)
371+ // This is similar to assertJobWithState, however we need to control
372+ // how the machine is initialized, so it looks like a 1.10 upgrade
373+ a := &MachineAgent{}
374+ s.initAgent(c, a, "--machine-id", m.Id())
375+
376+ agentStates := make(chan *state.State, 1000)
377+ undo := sendOpenedStates(agentStates)
378+ defer undo()
379+
380+ done := make(chan error)
381+ go func() {
382+ done <- a.Run(nil)
383+ }()
384+
385+ select {
386+ case agentState := <-agentStates:
387+ c.Assert(agentState, gc.NotNil)
388+ c.Assert(a.Conf.Conf.APIInfo.Password, gc.Equals, "machine-password")
389+ case <-time.After(testing.LongWait):
390+ c.Fatalf("state not opened")
391+ }
392+ err := a.Stop()
393+ c.Assert(err, gc.IsNil)
394+ c.Assert(<-done, gc.IsNil)
395+}
396+
397+// Test that MachineAgent enforces the API password on startup even for machine>0
398+func (s *UpgradeValidationMachineSuite) TestAgentEnsuresAPIInfoOnWorkers(c *gc.C) {
399+ // create a machine-0, then create a new machine-1
400+ _, _ = s.Create1_10Machine(c)
401+ m1, _ := s.Create1_10Machine(c)
402+
403+ a := &MachineAgent{}
404+ s.initAgent(c, a, "--machine-id", m1.Id())
405+
406+ agentStates := make(chan *state.State, 1000)
407+ undo := sendOpenedStates(agentStates)
408+ defer undo()
409+
410+ done := make(chan error)
411+ go func() {
412+ done <- a.Run(nil)
413+ }()
414+
415+ select {
416+ case agentState := <-agentStates:
417+ c.Assert(agentState, gc.NotNil)
418+ c.Assert(a.Conf.Conf.APIInfo.Password, gc.Equals, "machine-password")
419+ case <-time.After(testing.LongWait):
420+ c.Fatalf("state not opened")
421+ }
422+ err := a.Stop()
423+ c.Assert(err, gc.IsNil)
424+ c.Assert(<-done, gc.IsNil)
425+}
426+
427+var _ = gc.Suite(&UpgradeValidationUnitSuite{})
428+
429+type UpgradeValidationUnitSuite struct {
430+ agentSuite
431+ testing.GitSuite
432+}
433+
434+func (s *UpgradeValidationUnitSuite) SetUpTest(c *gc.C) {
435+ s.agentSuite.SetUpTest(c)
436+ s.GitSuite.SetUpTest(c)
437+}
438+
439+func (s *UpgradeValidationUnitSuite) TearDownTest(c *gc.C) {
440+ s.GitSuite.SetUpTest(c)
441+ s.agentSuite.TearDownTest(c)
442+}
443+
444+func (s *UpgradeValidationUnitSuite) Create1_10Unit(c *gc.C) (*state.Unit, *agent.Conf) {
445+ svc, err := s.State.AddService("wordpress", s.AddTestingCharm(c, "wordpress"))
446+ c.Assert(err, gc.IsNil)
447+ unit, err := svc.AddUnit()
448+ c.Assert(err, gc.IsNil)
449+ err = unit.SetMongoPassword("unit-password")
450+ c.Assert(err, gc.IsNil)
451+ // We do not call SetPassword for the unit agent, and we force the
452+ // APIInfo to be empty
453+ conf, _ := s.agentSuite.primeAgent(c, unit.Tag(), "unit-password")
454+ conf.APIInfo = nil
455+ c.Assert(conf.Write(), gc.IsNil)
456+ return unit, conf
457+}
458+
459+func (s *UpgradeValidationUnitSuite) TestEnsureAPIInfo(c *gc.C) {
460+ u, conf := s.Create1_10Unit(c)
461+ // Opening the API should fail as is
462+ c.Assert(func() { conf.OpenAPI(api.DialOpts{}) }, gc.PanicMatches, ".*nil pointer dereference")
463+
464+ err := EnsureAPIInfo(conf, s.State, u)
465+ c.Assert(err, gc.IsNil)
466+ // The test suite runs the API on non-standard ports. Fix it
467+ apiAddresses, err := s.State.APIAddresses()
468+ c.Assert(err, gc.IsNil)
469+ c.Assert(conf.APIInfo.Addrs, gc.DeepEquals, apiAddresses)
470+ conf.APIInfo.Addrs = s.APIInfo(c).Addrs
471+ apiState, newPassword, err := conf.OpenAPI(api.DialOpts{})
472+ c.Assert(err, gc.IsNil)
473+ c.Assert(apiState, gc.NotNil)
474+ // We shouldn't need to set a new password
475+ c.Assert(newPassword, gc.Equals, "")
476+}
477+
478+func (s *UpgradeValidationUnitSuite) TestEnsureAPIInfo1_11(c *gc.C) {
479+ // In 1.11 State.Password is actually "", and the valid password is
480+ // OldPassword. This is because in 1.11 we only change the password in
481+ // OpenAPI which we don't call until we actually have agent workers
482+ // But we don't want to set the actual entity password to the empty string
483+ u, conf := s.Create1_10Unit(c)
484+ conf.OldPassword = conf.StateInfo.Password
485+ conf.StateInfo.Password = ""
486+
487+ err := EnsureAPIInfo(conf, s.State, u)
488+ c.Assert(err, gc.IsNil)
489+ // The test suite runs the API on non-standard ports. Fix it
490+ apiAddresses, err := s.State.APIAddresses()
491+ c.Assert(err, gc.IsNil)
492+ c.Assert(conf.APIInfo.Addrs, gc.DeepEquals, apiAddresses)
493+ conf.APIInfo.Addrs = s.APIInfo(c).Addrs
494+ apiState, newPassword, err := conf.OpenAPI(api.DialOpts{})
495+ c.Assert(err, gc.IsNil)
496+ c.Assert(apiState, gc.NotNil)
497+ // It should want to set a new Password
498+ c.Assert(newPassword, gc.Not(gc.Equals), "")
499+}
500+
501+func (s *UpgradeValidationUnitSuite) TestEnsureAPIInfo1_11Noop(c *gc.C) {
502+ // We should notice if APIInfo is 'valid' in that it matches StateInfo
503+ // even though in 1.1 both Password fields are empty.
504+ u, conf := s.Create1_10Unit(c)
505+ conf.OldPassword = conf.StateInfo.Password
506+ conf.StateInfo.Password = ""
507+ u.SetPassword(conf.OldPassword)
508+ testAPIInfo := s.APIInfo(c)
509+ conf.APIInfo = &api.Info{
510+ Addrs: testAPIInfo.Addrs,
511+ Tag: u.Tag(),
512+ Password: "",
513+ CACert: testAPIInfo.CACert,
514+ }
515+
516+ err := EnsureAPIInfo(conf, s.State, u)
517+ c.Assert(err, gc.IsNil)
518+ // We should not have changed the API Addrs or Password
519+ c.Assert(conf.APIInfo.Password, gc.Equals, "")
520+ c.Assert(conf.APIInfo.Addrs, gc.DeepEquals, testAPIInfo.Addrs)
521+ apiState, newPassword, err := conf.OpenAPI(api.DialOpts{})
522+ c.Assert(err, gc.IsNil)
523+ c.Assert(apiState, gc.NotNil)
524+ // It should want to set a new Password
525+ c.Assert(newPassword, gc.Not(gc.Equals), "")
526+}
527+
528+// Test that UnitAgent enforces the API password on startup
529+func (s *UpgradeValidationUnitSuite) TestAgentEnsuresAPIInfo(c *gc.C) {
530+ unit, _ := s.Create1_10Unit(c)
531+ a := &UnitAgent{}
532+ s.initAgent(c, a, "--unit-name", unit.Name())
533+ go func() { c.Check(a.Run(nil), gc.IsNil) }()
534+ waitForUnitStarted(s.State, unit, c)
535+ c.Check(a.Stop(), gc.IsNil)
536+ c.Check(a.Conf.APIInfo.Password, gc.Equals, "unit-password")
537+}
538+
539+var _ = gc.Suite(&UpgradeValidationSuite{})
540+
541+type UpgradeValidationSuite struct {
542+ testing.LoggingSuite
543+}
544+
545+type mockAddresser struct {
546+ Addrs []string
547+ Err error
548+}
549+
550+func (m *mockAddresser) APIAddresses() ([]string, error) {
551+ return m.Addrs, m.Err
552+}
553+
554+func (s *UpgradeValidationSuite) TestapiInfoFromStateInfo(c *gc.C) {
555+ cert := []byte("stuff")
556+ stInfo := &state.Info{
557+ Addrs: []string{"example.invalid:37070"},
558+ CACert: cert,
559+ Tag: "machine-0",
560+ Password: "abcdefh",
561+ }
562+ apiAddresses := []string{"example.invalid:17070", "another.invalid:1234"}
563+ apiInfo := apiInfoFromStateInfo(stInfo, &mockAddresser{Addrs: apiAddresses})
564+ c.Assert(*apiInfo, gc.DeepEquals, api.Info{
565+ Addrs: apiAddresses,
566+ CACert: cert,
567+ Tag: "machine-0",
568+ Password: "abcdefh",
569+ })
570+
571+}
572+
573+func (s *UpgradeValidationSuite) TestapiInfoFromStateInfoSwallowsError(c *gc.C) {
574+ // No reason for it to die just because of this
575+ cert := []byte("stuff")
576+ stInfo := &state.Info{
577+ Addrs: []string{"example.invalid:37070"},
578+ CACert: cert,
579+ Tag: "machine-0",
580+ Password: "abcdefh",
581+ }
582+ apiAddresses := []string{}
583+ apiInfo := apiInfoFromStateInfo(stInfo, &mockAddresser{Addrs: apiAddresses, Err: fmt.Errorf("bad")})
584+ c.Assert(*apiInfo, gc.DeepEquals, api.Info{
585+ Addrs: []string{},
586+ CACert: cert,
587+ Tag: "machine-0",
588+ Password: "abcdefh",
589+ })
590+
591+}

Subscribers

People subscribed via source and target branches

to status/vote changes: