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
=== modified file 'cmd/jujud/machine.go'
--- cmd/jujud/machine.go 2013-07-17 02:38:48 +0000
+++ cmd/jujud/machine.go 2013-07-17 07:05:30 +0000
@@ -116,6 +116,7 @@
116 // instances of the API server have been started, we116 // instances of the API server have been started, we
117 // should follow the normal course of things and ignore117 // should follow the normal course of things and ignore
118 // the fact that this was once the bootstrap machine.118 // the fact that this was once the bootstrap machine.
119 log.Infof("Starting StateWorker for machine-0")
119 ensureStateWorker()120 ensureStateWorker()
120 }121 }
121 a.runner.StartWorker("api", func() (worker.Worker, error) {122 a.runner.StartWorker("api", func() (worker.Worker, error) {
@@ -144,6 +145,17 @@
144func (a *MachineAgent) APIWorker(ensureStateWorker func()) (worker.Worker, error) {145func (a *MachineAgent) APIWorker(ensureStateWorker func()) (worker.Worker, error) {
145 st, entity, err := openAPIState(a.Conf.Conf, a)146 st, entity, err := openAPIState(a.Conf.Conf, a)
146 if err != nil {147 if err != nil {
148 // There was an error connecting to the API,
149 // https://launchpad.net/bugs/1199915 means that we may just
150 // not have an API password set. So force a state connection at
151 // this point.
152 // TODO(jam): Once we can reliably trust that we have API
153 // passwords set, and we no longer need state
154 // connections (and possibly agents will be blocked
155 // from connecting directly to state) we can remove
156 // this. Currently needed because 1.10 does not set
157 // the API password and 1.11 requires it
158 ensureStateWorker()
147 return nil, err159 return nil, err
148 }160 }
149 m := entity.(*machineagent.Machine)161 m := entity.(*machineagent.Machine)
@@ -167,6 +179,11 @@
167 if err != nil {179 if err != nil {
168 return nil, err180 return nil, err
169 }181 }
182 // If this fails, other bits will fail, so we just log the error, and
183 // let the other failures actually restart runners
184 if err := EnsureAPIInfo(a.Conf.Conf, st, entity); err != nil {
185 log.Warningf("failed to EnsureAPIInfo: %v", err)
186 }
170 reportOpenedState(st)187 reportOpenedState(st)
171 m := entity.(*state.Machine)188 m := entity.(*state.Machine)
172 // TODO(rog) use more discriminating test for errors189 // TODO(rog) use more discriminating test for errors
173190
=== modified file 'cmd/jujud/machine_test.go'
--- cmd/jujud/machine_test.go 2013-07-15 23:01:31 +0000
+++ cmd/jujud/machine_test.go 2013-07-17 07:05:30 +0000
@@ -70,7 +70,7 @@
70 c.Assert(err, IsNil)70 c.Assert(err, IsNil)
71 conf, tools := s.agentSuite.primeAgent(c, state.MachineTag(m.Id()), "machine-password")71 conf, tools := s.agentSuite.primeAgent(c, state.MachineTag(m.Id()), "machine-password")
72 conf.MachineNonce = state.BootstrapNonce72 conf.MachineNonce = state.BootstrapNonce
73 conf.Write()73 err = conf.Write()
74 c.Assert(err, IsNil)74 c.Assert(err, IsNil)
75 return m, conf, tools75 return m, conf, tools
76}76}
7777
=== modified file 'cmd/jujud/unit.go'
--- cmd/jujud/unit.go 2013-06-27 09:26:02 +0000
+++ cmd/jujud/unit.go 2013-07-17 07:05:30 +0000
@@ -5,15 +5,20 @@
55
6import (6import (
7 "fmt"7 "fmt"
8
8 "launchpad.net/gnuflag"9 "launchpad.net/gnuflag"
10 "launchpad.net/loggo"
11 "launchpad.net/tomb"
12
9 "launchpad.net/juju-core/cmd"13 "launchpad.net/juju-core/cmd"
10 "launchpad.net/juju-core/state"14 "launchpad.net/juju-core/state"
11 "launchpad.net/juju-core/state/api"15 "launchpad.net/juju-core/state/api"
12 "launchpad.net/juju-core/worker"16 "launchpad.net/juju-core/worker"
13 "launchpad.net/juju-core/worker/uniter"17 "launchpad.net/juju-core/worker/uniter"
14 "launchpad.net/tomb"
15)18)
1619
20var agentLogger = loggo.GetLogger("juju.jujud")
21
17// UnitAgent is a cmd.Command responsible for running a unit agent.22// UnitAgent is a cmd.Command responsible for running a unit agent.
18type UnitAgent struct {23type UnitAgent struct {
19 cmd.CommandBase24 cmd.CommandBase
@@ -63,6 +68,7 @@
63 if err := a.Conf.read(a.Tag()); err != nil {68 if err := a.Conf.read(a.Tag()); err != nil {
64 return err69 return err
65 }70 }
71 agentLogger.Infof("unit agent %v start", a.Tag())
66 a.runner.StartWorker("toplevel", func() (worker.Worker, error) {72 a.runner.StartWorker("toplevel", func() (worker.Worker, error) {
67 // TODO(rog) go1.1: use method expression73 // TODO(rog) go1.1: use method expression
68 return a.Workers()74 return a.Workers()
@@ -78,6 +84,11 @@
78 if err != nil {84 if err != nil {
79 return nil, err85 return nil, err
80 }86 }
87 if err := EnsureAPIInfo(a.Conf.Conf, st, entity); err != nil {
88 // We suppress this error, because it is probably more interesting
89 // to see other failures, but we log it, in case it is a root cause
90 agentLogger.Warningf("error while calling EnsureAPIInfo: %v", err)
91 }
81 unit := entity.(*state.Unit)92 unit := entity.(*state.Unit)
82 dataDir := a.Conf.DataDir93 dataDir := a.Conf.DataDir
83 runner := worker.NewRunner(allFatal, moreImportant)94 runner := worker.NewRunner(allFatal, moreImportant)
8495
=== modified file 'cmd/jujud/unit_test.go'
--- cmd/jujud/unit_test.go 2013-07-09 11:31:00 +0000
+++ cmd/jujud/unit_test.go 2013-07-17 07:05:30 +0000
@@ -85,14 +85,9 @@
85 c.Assert(err, ErrorMatches, `unrecognized args: \["thundering typhoons"\]`)85 c.Assert(err, ErrorMatches, `unrecognized args: \["thundering typhoons"\]`)
86}86}
8787
88func (s *UnitSuite) TestRunStop(c *C) {88func waitForUnitStarted(stateConn *state.State, unit *state.Unit, c *C) {
89 unit, _, _ := s.primeAgent(c)
90 a := s.newAgent(c, unit)
91 go func() { c.Check(a.Run(nil), IsNil) }()
92 defer func() { c.Check(a.Stop(), IsNil) }()
93 timeout := time.After(5 * time.Second)89 timeout := time.After(5 * time.Second)
9490
95waitStarted:
96 for {91 for {
97 select {92 select {
98 case <-timeout:93 case <-timeout:
@@ -108,9 +103,9 @@
108 continue103 continue
109 case params.StatusStarted:104 case params.StatusStarted:
110 c.Logf("started!")105 c.Logf("started!")
111 break waitStarted106 return
112 case params.StatusDown:107 case params.StatusDown:
113 s.State.StartSync()108 stateConn.StartSync()
114 c.Logf("unit is still down")109 c.Logf("unit is still down")
115 default:110 default:
116 c.Fatalf("unexpected status %s %s", st, info)111 c.Fatalf("unexpected status %s %s", st, info)
@@ -119,6 +114,14 @@
119 }114 }
120}115}
121116
117func (s *UnitSuite) TestRunStop(c *C) {
118 unit, _, _ := s.primeAgent(c)
119 a := s.newAgent(c, unit)
120 go func() { c.Check(a.Run(nil), IsNil) }()
121 defer func() { c.Check(a.Stop(), IsNil) }()
122 waitForUnitStarted(s.State, unit, c)
123}
124
122func (s *UnitSuite) TestUpgrade(c *C) {125func (s *UnitSuite) TestUpgrade(c *C) {
123 unit, _, currentTools := s.primeAgent(c)126 unit, _, currentTools := s.primeAgent(c)
124 a := s.newAgent(c, unit)127 a := s.newAgent(c, unit)
125128
=== modified file 'cmd/jujud/upgradevalidation.go'
--- cmd/jujud/upgradevalidation.go 2013-07-17 02:35:07 +0000
+++ cmd/jujud/upgradevalidation.go 2013-07-17 07:05:30 +0000
@@ -10,10 +10,12 @@
1010
11 "launchpad.net/loggo"11 "launchpad.net/loggo"
1212
13 "launchpad.net/juju-core/agent"
13 "launchpad.net/juju-core/container/lxc"14 "launchpad.net/juju-core/container/lxc"
14 "launchpad.net/juju-core/environs/provider"15 "launchpad.net/juju-core/environs/provider"
15 "launchpad.net/juju-core/instance"16 "launchpad.net/juju-core/instance"
16 "launchpad.net/juju-core/state"17 "launchpad.net/juju-core/state"
18 "launchpad.net/juju-core/state/api"
17 "launchpad.net/juju-core/utils"19 "launchpad.net/juju-core/utils"
18 "launchpad.net/juju-core/utils/fslock"20 "launchpad.net/juju-core/utils/fslock"
19)21)
@@ -89,3 +91,86 @@
89 // only supports debian-based anyway91 // only supports debian-based anyway
90 return utils.AptGetInstall("lxc")92 return utils.AptGetInstall("lxc")
91}93}
94
95type passwordSetter interface {
96 SetPassword(password string) error
97}
98
99// apiAddresser just implements APIAddresses
100// This is implemented by the standard *state.State object
101type apiAddresser interface {
102 APIAddresses() ([]string, error)
103}
104
105func apiInfoFromStateInfo(stInfo *state.Info, stConn apiAddresser) *api.Info {
106 addrs, err := stConn.APIAddresses()
107 if err != nil {
108 validationLogger.Warningf("Unable to get the list of valid API Addresses: %v", err)
109 }
110 return &api.Info{
111 Addrs: addrs,
112 CACert: stInfo.CACert,
113 Tag: stInfo.Tag,
114 Password: stInfo.Password,
115 }
116}
117
118// EnsureAPIInfo makes sure we can connect as an agent to the API server 1.10
119// did not set an API password for machine agents, 1.11 sets it the same as the
120// mongo password. 1.10 also does not set any API Info at all for Unit agents.
121// conf is the agent.conf for this machine/unit agent. agentConn is the direct
122// connection to the State database
123func EnsureAPIInfo(conf *agent.Conf, stConn *state.State, agentConn AgentState) error {
124 // In 1.10 Machine Agents will have an API Info section, but will not
125 // have properly configured the Password field, so when upgrading to
126 // 1.11 we must set that field
127 // In 1.10 Unit Agents will not have an API Info section at all, so we
128 // must set everything from the StateInfo (correcting the Addrs for API
129 // port vs Mongo port)
130 // A new 1.11 Unit Agent will have an OldPassword but no Password set
131 // (in StateInfo or APIInfo). Because we only change the password on
132 // API connect, not on DB connect. We don't want to make extra
133 // SetPassword calls when we already have a valid password set
134 if conf.APIInfo != nil && (conf.APIInfo.Password != "" || conf.StateInfo.Password == "") {
135 // We must have set it earlier
136 return nil
137 }
138 setter, ok := agentConn.(passwordSetter)
139 if !ok {
140 panic("AgentState is missing a SetPassword method?")
141 }
142 if conf.APIInfo == nil {
143 // Unit agents didn't get any APIInfo in 1.10
144 conf.APIInfo = apiInfoFromStateInfo(conf.StateInfo, stConn)
145 validationLogger.Infof(
146 "agent.conf APIInfo is not set. Setting to {Addrs: %s, Tag: %s}",
147 conf.APIInfo.Addrs,
148 conf.APIInfo.Tag,
149 )
150 } else {
151 validationLogger.Infof("agent.conf APIInfo password is not set. Setting to state password")
152 conf.APIInfo.Password = conf.StateInfo.Password
153 }
154 password := conf.StateInfo.Password
155 if password == "" {
156 // In 1.11 we don't set a new password on connect (because it is
157 // done in the API code, and we don't have any API workers).
158 // We want to make sure the API user has the correct password
159 // (OldPassword). We *don't* want to set APIInfo.Password
160 // because then when we do connect via the API we wouldn't
161 // change the password. So this is only used for SetPassword
162 validationLogger.Infof(
163 "agent.conf StateInfo password is \"\". Setting Agent password for %s to OldPassword",
164 conf.APIInfo.Tag)
165 password = conf.OldPassword
166 }
167 // We set the actual password before writing it to disk, because
168 // otherwise we would not set it correctly in the future
169 if err := setter.SetPassword(password); err != nil {
170 return err
171 }
172 if err := conf.Write(); err != nil {
173 return err
174 }
175 return nil
176}
92177
=== added file 'cmd/jujud/upgradevalidation_test.go'
--- cmd/jujud/upgradevalidation_test.go 1970-01-01 00:00:00 +0000
+++ cmd/jujud/upgradevalidation_test.go 2013-07-17 07:05:30 +0000
@@ -0,0 +1,335 @@
1// Copyright 2012, 2013 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package main
5
6import (
7 "fmt"
8 "time"
9
10 gc "launchpad.net/gocheck"
11
12 "launchpad.net/juju-core/agent"
13 "launchpad.net/juju-core/constraints"
14 "launchpad.net/juju-core/container/lxc"
15 "launchpad.net/juju-core/state"
16 "launchpad.net/juju-core/state/api"
17 "launchpad.net/juju-core/testing"
18)
19
20var _ = gc.Suite(&UpgradeValidationMachineSuite{})
21
22type UpgradeValidationMachineSuite struct {
23 agentSuite
24 lxc.TestSuite
25}
26
27func (s *UpgradeValidationMachineSuite) SetUpSuite(c *gc.C) {
28 s.agentSuite.SetUpSuite(c)
29 s.TestSuite.SetUpSuite(c)
30}
31
32func (s *UpgradeValidationMachineSuite) TearDownSuite(c *gc.C) {
33 s.TestSuite.TearDownSuite(c)
34 s.agentSuite.TearDownSuite(c)
35}
36
37func (s *UpgradeValidationMachineSuite) SetUpTest(c *gc.C) {
38 s.agentSuite.SetUpTest(c)
39 s.TestSuite.SetUpTest(c)
40}
41
42func (s *UpgradeValidationMachineSuite) TearDownTest(c *gc.C) {
43 s.TestSuite.TearDownTest(c)
44 s.agentSuite.TearDownTest(c)
45}
46
47func (s *UpgradeValidationMachineSuite) Create1_10Machine(c *gc.C) (*state.Machine, *agent.Conf) {
48 // Given the current connection to state, create a new machine, and 'reset'
49 // the configuration so that it looks like how juju 1.10 would have
50 // configured it
51 m, err := s.State.InjectMachine("series", constraints.Value{}, "ardbeg-0", state.JobHostUnits)
52 c.Assert(err, gc.IsNil)
53 err = m.SetMongoPassword("machine-password")
54 c.Assert(err, gc.IsNil)
55 // We intentionally do *not* call m.SetPassword here, as it was not
56 // done in 1.10, we also intentionally set the APIInfo.Password back to
57 // the empty string.
58 conf, _ := s.agentSuite.primeAgent(c, m.Tag(), "machine-password")
59 conf.MachineNonce = state.BootstrapNonce
60 conf.APIInfo.Password = ""
61 conf.Write()
62 c.Assert(conf.StateInfo.Tag, gc.Equals, m.Tag())
63 c.Assert(conf.StateInfo.Password, gc.Equals, "machine-password")
64 c.Assert(err, gc.IsNil)
65 return m, conf
66}
67
68func (s *UpgradeValidationMachineSuite) TestEnsureAPIInfo(c *gc.C) {
69 m, conf := s.Create1_10Machine(c)
70 // Opening the API should fail as is
71 apiState, newPassword, err := conf.OpenAPI(api.DialOpts{})
72 c.Assert(apiState, gc.IsNil)
73 c.Assert(newPassword, gc.Equals, "")
74 c.Assert(err, gc.NotNil)
75 c.Assert(err, gc.ErrorMatches, "invalid entity name or password")
76
77 err = EnsureAPIInfo(conf, s.State, m)
78 c.Assert(err, gc.IsNil)
79 // After EnsureAPIInfo we should be able to connect
80 apiState, newPassword, err = conf.OpenAPI(api.DialOpts{})
81 c.Assert(err, gc.IsNil)
82 c.Assert(apiState, gc.NotNil)
83 // We shouldn't need to set a new password
84 c.Assert(newPassword, gc.Equals, "")
85}
86
87func (s *UpgradeValidationMachineSuite) TestEnsureAPIInfoNoOp(c *gc.C) {
88 m, conf := s.Create1_10Machine(c)
89 // Set the API password to something, and record it, ensure that
90 // EnsureAPIInfo doesn't change it on us
91 m.SetPassword("frobnizzle")
92 conf.APIInfo.Password = "frobnizzle"
93 // We matched them, so we should be able to open the API
94 apiState, newPassword, err := conf.OpenAPI(api.DialOpts{})
95 c.Assert(apiState, gc.NotNil)
96 c.Assert(newPassword, gc.Equals, "")
97 c.Assert(err, gc.IsNil)
98 apiState.Close()
99
100 err = EnsureAPIInfo(conf, s.State, m)
101 c.Assert(err, gc.IsNil)
102 // After EnsureAPIInfo we should still be able to connect
103 apiState, newPassword, err = conf.OpenAPI(api.DialOpts{})
104 c.Assert(err, gc.IsNil)
105 c.Assert(apiState, gc.NotNil)
106 // We shouldn't need to set a new password
107 c.Assert(newPassword, gc.Equals, "")
108 // The password hasn't been changed
109 c.Assert(conf.APIInfo.Password, gc.Equals, "frobnizzle")
110}
111
112// Test that MachineAgent enforces the API password on startup
113func (s *UpgradeValidationMachineSuite) TestAgentEnsuresAPIInfo(c *gc.C) {
114 m, _ := s.Create1_10Machine(c)
115 // This is similar to assertJobWithState, however we need to control
116 // how the machine is initialized, so it looks like a 1.10 upgrade
117 a := &MachineAgent{}
118 s.initAgent(c, a, "--machine-id", m.Id())
119
120 agentStates := make(chan *state.State, 1000)
121 undo := sendOpenedStates(agentStates)
122 defer undo()
123
124 done := make(chan error)
125 go func() {
126 done <- a.Run(nil)
127 }()
128
129 select {
130 case agentState := <-agentStates:
131 c.Assert(agentState, gc.NotNil)
132 c.Assert(a.Conf.Conf.APIInfo.Password, gc.Equals, "machine-password")
133 case <-time.After(testing.LongWait):
134 c.Fatalf("state not opened")
135 }
136 err := a.Stop()
137 c.Assert(err, gc.IsNil)
138 c.Assert(<-done, gc.IsNil)
139}
140
141// Test that MachineAgent enforces the API password on startup even for machine>0
142func (s *UpgradeValidationMachineSuite) TestAgentEnsuresAPIInfoOnWorkers(c *gc.C) {
143 // create a machine-0, then create a new machine-1
144 _, _ = s.Create1_10Machine(c)
145 m1, _ := s.Create1_10Machine(c)
146
147 a := &MachineAgent{}
148 s.initAgent(c, a, "--machine-id", m1.Id())
149
150 agentStates := make(chan *state.State, 1000)
151 undo := sendOpenedStates(agentStates)
152 defer undo()
153
154 done := make(chan error)
155 go func() {
156 done <- a.Run(nil)
157 }()
158
159 select {
160 case agentState := <-agentStates:
161 c.Assert(agentState, gc.NotNil)
162 c.Assert(a.Conf.Conf.APIInfo.Password, gc.Equals, "machine-password")
163 case <-time.After(testing.LongWait):
164 c.Fatalf("state not opened")
165 }
166 err := a.Stop()
167 c.Assert(err, gc.IsNil)
168 c.Assert(<-done, gc.IsNil)
169}
170
171var _ = gc.Suite(&UpgradeValidationUnitSuite{})
172
173type UpgradeValidationUnitSuite struct {
174 agentSuite
175 testing.GitSuite
176}
177
178func (s *UpgradeValidationUnitSuite) SetUpTest(c *gc.C) {
179 s.agentSuite.SetUpTest(c)
180 s.GitSuite.SetUpTest(c)
181}
182
183func (s *UpgradeValidationUnitSuite) TearDownTest(c *gc.C) {
184 s.GitSuite.SetUpTest(c)
185 s.agentSuite.TearDownTest(c)
186}
187
188func (s *UpgradeValidationUnitSuite) Create1_10Unit(c *gc.C) (*state.Unit, *agent.Conf) {
189 svc, err := s.State.AddService("wordpress", s.AddTestingCharm(c, "wordpress"))
190 c.Assert(err, gc.IsNil)
191 unit, err := svc.AddUnit()
192 c.Assert(err, gc.IsNil)
193 err = unit.SetMongoPassword("unit-password")
194 c.Assert(err, gc.IsNil)
195 // We do not call SetPassword for the unit agent, and we force the
196 // APIInfo to be empty
197 conf, _ := s.agentSuite.primeAgent(c, unit.Tag(), "unit-password")
198 conf.APIInfo = nil
199 c.Assert(conf.Write(), gc.IsNil)
200 return unit, conf
201}
202
203func (s *UpgradeValidationUnitSuite) TestEnsureAPIInfo(c *gc.C) {
204 u, conf := s.Create1_10Unit(c)
205 // Opening the API should fail as is
206 c.Assert(func() { conf.OpenAPI(api.DialOpts{}) }, gc.PanicMatches, ".*nil pointer dereference")
207
208 err := EnsureAPIInfo(conf, s.State, u)
209 c.Assert(err, gc.IsNil)
210 // The test suite runs the API on non-standard ports. Fix it
211 apiAddresses, err := s.State.APIAddresses()
212 c.Assert(err, gc.IsNil)
213 c.Assert(conf.APIInfo.Addrs, gc.DeepEquals, apiAddresses)
214 conf.APIInfo.Addrs = s.APIInfo(c).Addrs
215 apiState, newPassword, err := conf.OpenAPI(api.DialOpts{})
216 c.Assert(err, gc.IsNil)
217 c.Assert(apiState, gc.NotNil)
218 // We shouldn't need to set a new password
219 c.Assert(newPassword, gc.Equals, "")
220}
221
222func (s *UpgradeValidationUnitSuite) TestEnsureAPIInfo1_11(c *gc.C) {
223 // In 1.11 State.Password is actually "", and the valid password is
224 // OldPassword. This is because in 1.11 we only change the password in
225 // OpenAPI which we don't call until we actually have agent workers
226 // But we don't want to set the actual entity password to the empty string
227 u, conf := s.Create1_10Unit(c)
228 conf.OldPassword = conf.StateInfo.Password
229 conf.StateInfo.Password = ""
230
231 err := EnsureAPIInfo(conf, s.State, u)
232 c.Assert(err, gc.IsNil)
233 // The test suite runs the API on non-standard ports. Fix it
234 apiAddresses, err := s.State.APIAddresses()
235 c.Assert(err, gc.IsNil)
236 c.Assert(conf.APIInfo.Addrs, gc.DeepEquals, apiAddresses)
237 conf.APIInfo.Addrs = s.APIInfo(c).Addrs
238 apiState, newPassword, err := conf.OpenAPI(api.DialOpts{})
239 c.Assert(err, gc.IsNil)
240 c.Assert(apiState, gc.NotNil)
241 // It should want to set a new Password
242 c.Assert(newPassword, gc.Not(gc.Equals), "")
243}
244
245func (s *UpgradeValidationUnitSuite) TestEnsureAPIInfo1_11Noop(c *gc.C) {
246 // We should notice if APIInfo is 'valid' in that it matches StateInfo
247 // even though in 1.1 both Password fields are empty.
248 u, conf := s.Create1_10Unit(c)
249 conf.OldPassword = conf.StateInfo.Password
250 conf.StateInfo.Password = ""
251 u.SetPassword(conf.OldPassword)
252 testAPIInfo := s.APIInfo(c)
253 conf.APIInfo = &api.Info{
254 Addrs: testAPIInfo.Addrs,
255 Tag: u.Tag(),
256 Password: "",
257 CACert: testAPIInfo.CACert,
258 }
259
260 err := EnsureAPIInfo(conf, s.State, u)
261 c.Assert(err, gc.IsNil)
262 // We should not have changed the API Addrs or Password
263 c.Assert(conf.APIInfo.Password, gc.Equals, "")
264 c.Assert(conf.APIInfo.Addrs, gc.DeepEquals, testAPIInfo.Addrs)
265 apiState, newPassword, err := conf.OpenAPI(api.DialOpts{})
266 c.Assert(err, gc.IsNil)
267 c.Assert(apiState, gc.NotNil)
268 // It should want to set a new Password
269 c.Assert(newPassword, gc.Not(gc.Equals), "")
270}
271
272// Test that UnitAgent enforces the API password on startup
273func (s *UpgradeValidationUnitSuite) TestAgentEnsuresAPIInfo(c *gc.C) {
274 unit, _ := s.Create1_10Unit(c)
275 a := &UnitAgent{}
276 s.initAgent(c, a, "--unit-name", unit.Name())
277 go func() { c.Check(a.Run(nil), gc.IsNil) }()
278 waitForUnitStarted(s.State, unit, c)
279 c.Check(a.Stop(), gc.IsNil)
280 c.Check(a.Conf.APIInfo.Password, gc.Equals, "unit-password")
281}
282
283var _ = gc.Suite(&UpgradeValidationSuite{})
284
285type UpgradeValidationSuite struct {
286 testing.LoggingSuite
287}
288
289type mockAddresser struct {
290 Addrs []string
291 Err error
292}
293
294func (m *mockAddresser) APIAddresses() ([]string, error) {
295 return m.Addrs, m.Err
296}
297
298func (s *UpgradeValidationSuite) TestapiInfoFromStateInfo(c *gc.C) {
299 cert := []byte("stuff")
300 stInfo := &state.Info{
301 Addrs: []string{"example.invalid:37070"},
302 CACert: cert,
303 Tag: "machine-0",
304 Password: "abcdefh",
305 }
306 apiAddresses := []string{"example.invalid:17070", "another.invalid:1234"}
307 apiInfo := apiInfoFromStateInfo(stInfo, &mockAddresser{Addrs: apiAddresses})
308 c.Assert(*apiInfo, gc.DeepEquals, api.Info{
309 Addrs: apiAddresses,
310 CACert: cert,
311 Tag: "machine-0",
312 Password: "abcdefh",
313 })
314
315}
316
317func (s *UpgradeValidationSuite) TestapiInfoFromStateInfoSwallowsError(c *gc.C) {
318 // No reason for it to die just because of this
319 cert := []byte("stuff")
320 stInfo := &state.Info{
321 Addrs: []string{"example.invalid:37070"},
322 CACert: cert,
323 Tag: "machine-0",
324 Password: "abcdefh",
325 }
326 apiAddresses := []string{}
327 apiInfo := apiInfoFromStateInfo(stInfo, &mockAddresser{Addrs: apiAddresses, Err: fmt.Errorf("bad")})
328 c.Assert(*apiInfo, gc.DeepEquals, api.Info{
329 Addrs: []string{},
330 CACert: cert,
331 Tag: "machine-0",
332 Password: "abcdefh",
333 })
334
335}

Subscribers

People subscribed via source and target branches

to status/vote changes: