1
// Copyright 2012-2015 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
9
"github.com/juju/testing"
10
jc "github.com/juju/testing/checkers"
11
gc "gopkg.in/check.v1"
12
"gopkg.in/juju/names.v2"
14
"github.com/juju/juju/agent"
15
"github.com/juju/juju/api"
16
apiagent "github.com/juju/juju/api/agent"
17
"github.com/juju/juju/apiserver/common"
18
"github.com/juju/juju/apiserver/params"
19
coretesting "github.com/juju/juju/testing"
20
"github.com/juju/juju/worker/apicaller"
23
// ScaryConnectSuite should cover all the *lines* where we get a connection
24
// without triggering the checkProvisionedStrategy ugliness. It tests the
25
// various conditions in isolation; it's possible that some real scenarios
26
// may trigger more than one of these, but it's impractical to test *every*
28
type ScaryConnectSuite struct {
29
testing.IsolationSuite
32
var _ = gc.Suite(&ScaryConnectSuite{})
34
func (*ScaryConnectSuite) TestEntityAlive(c *gc.C) {
35
testEntityFine(c, apiagent.Alive)
38
func (*ScaryConnectSuite) TestEntityDying(c *gc.C) {
39
testEntityFine(c, apiagent.Dying)
42
func testEntityFine(c *gc.C, life apiagent.Life) {
43
stub := &testing.Stub{}
44
expectConn := &mockConn{stub: stub}
45
apiOpen := func(info *api.Info, opts api.DialOpts) (api.Connection, error) {
46
// no apiOpen stub calls necessary in this suite; covered
47
// by RetrySuite, just an extra complication here.
48
return expectConn, nil
51
// to make the point that this code should be entity-agnostic,
52
// use an entity that doesn't correspond to an agent at all.
53
entity := names.NewApplicationTag("omg")
54
connect := func() (api.Connection, error) {
55
return apicaller.ScaryConnect(&mockAgent{
57
model: coretesting.ModelTag,
62
conn, err := lifeTest(c, stub, apiagent.Alive, connect)
63
c.Check(conn, gc.Equals, expectConn)
64
c.Check(err, jc.ErrorIsNil)
65
stub.CheckCalls(c, []testing.StubCall{{
67
Args: []interface{}{entity},
69
FuncName: "SetPassword",
70
Args: []interface{}{entity, "new"},
74
func (*ScaryConnectSuite) TestModelTagCannotChangeConfig(c *gc.C) {
75
stub := checkModelTagUpdate(c, errors.New("oh noes"))
76
stub.CheckCallNames(c,
78
"Life", "SetPassword",
82
func (*ScaryConnectSuite) TestModelTagCannotGetTag(c *gc.C) {
83
stub := checkModelTagUpdate(c, nil, errors.New("oh noes"))
84
stub.CheckCallNames(c,
85
"ChangeConfig", "ModelTag",
86
"Life", "SetPassword",
90
func (*ScaryConnectSuite) TestModelTagCannotMigrate(c *gc.C) {
91
stub := checkModelTagUpdate(c, nil, nil, errors.New("oh noes"))
92
stub.CheckCallNames(c,
93
"ChangeConfig", "ModelTag", "Migrate",
94
"Life", "SetPassword",
96
c.Check(stub.Calls()[2].Args, jc.DeepEquals, []interface{}{
97
agent.MigrateParams{Model: coretesting.ModelTag},
101
func (*ScaryConnectSuite) TestModelTagSuccess(c *gc.C) {
102
stub := checkModelTagUpdate(c)
103
stub.CheckCallNames(c,
104
"ChangeConfig", "ModelTag", "Migrate",
105
"Life", "SetPassword",
107
c.Check(stub.Calls()[2].Args, jc.DeepEquals, []interface{}{
108
agent.MigrateParams{Model: coretesting.ModelTag},
112
func checkModelTagUpdate(c *gc.C, errs ...error) *testing.Stub {
113
// success case; just a little failure we don't mind, otherwise
114
// equivalent to testEntityFine.
115
stub := &testing.Stub{}
116
stub.SetErrors(errs...) // from ChangeConfig
117
expectConn := &mockConn{stub: stub}
118
apiOpen := func(info *api.Info, opts api.DialOpts) (api.Connection, error) {
119
return expectConn, nil
122
entity := names.NewApplicationTag("omg")
123
connect := func() (api.Connection, error) {
124
return apicaller.ScaryConnect(&mockAgent{
126
// no model set; triggers ChangeConfig
130
conn, err := lifeTest(c, stub, apiagent.Alive, connect)
131
c.Check(conn, gc.Equals, expectConn)
132
c.Check(err, jc.ErrorIsNil)
136
func (*ScaryConnectSuite) TestEntityDead(c *gc.C) {
137
// permanent failure case
138
stub := &testing.Stub{}
139
expectConn := &mockConn{stub: stub}
140
apiOpen := func(info *api.Info, opts api.DialOpts) (api.Connection, error) {
141
return expectConn, nil
144
entity := names.NewApplicationTag("omg")
145
connect := func() (api.Connection, error) {
146
return apicaller.ScaryConnect(&mockAgent{
148
model: coretesting.ModelTag,
153
conn, err := lifeTest(c, stub, apiagent.Dead, connect)
154
c.Check(conn, gc.IsNil)
155
c.Check(err, gc.Equals, apicaller.ErrConnectImpossible)
156
stub.CheckCalls(c, []testing.StubCall{{
158
Args: []interface{}{entity},
164
func (*ScaryConnectSuite) TestEntityDenied(c *gc.C) {
165
// permanent failure case
166
stub := &testing.Stub{}
167
stub.SetErrors(apiagent.ErrDenied)
168
expectConn := &mockConn{stub: stub}
169
apiOpen := func(info *api.Info, opts api.DialOpts) (api.Connection, error) {
170
return expectConn, nil
173
entity := names.NewApplicationTag("omg")
174
connect := func() (api.Connection, error) {
175
return apicaller.ScaryConnect(&mockAgent{
177
model: coretesting.ModelTag,
182
conn, err := lifeTest(c, stub, apiagent.Dead, connect)
183
c.Check(conn, gc.IsNil)
184
c.Check(err, gc.Equals, apicaller.ErrConnectImpossible)
185
stub.CheckCalls(c, []testing.StubCall{{
187
Args: []interface{}{entity},
193
func (*ScaryConnectSuite) TestEntityUnknownLife(c *gc.C) {
194
// "random" failure case
195
stub := &testing.Stub{}
196
expectConn := &mockConn{stub: stub}
197
apiOpen := func(info *api.Info, opts api.DialOpts) (api.Connection, error) {
198
return expectConn, nil
201
entity := names.NewApplicationTag("omg")
202
connect := func() (api.Connection, error) {
203
return apicaller.ScaryConnect(&mockAgent{
205
model: coretesting.ModelTag,
210
conn, err := lifeTest(c, stub, apiagent.Life("zombie"), connect)
211
c.Check(conn, gc.IsNil)
212
c.Check(err, gc.ErrorMatches, `unknown life value "zombie"`)
213
stub.CheckCalls(c, []testing.StubCall{{
215
Args: []interface{}{entity},
221
func (*ScaryConnectSuite) TestChangePasswordConfigError(c *gc.C) {
222
// "random" failure case
223
stub := createUnauthorisedStub(nil, errors.New("zap"))
224
err := checkChangePassword(c, stub)
225
c.Check(err, gc.ErrorMatches, "zap")
226
stub.CheckCallNames(c,
227
"Life", "ChangeConfig",
232
func (*ScaryConnectSuite) TestChangePasswordRemoteError(c *gc.C) {
233
// "random" failure case
234
stub := createUnauthorisedStub(nil, nil, nil, nil, errors.New("pow"))
235
err := checkChangePassword(c, stub)
236
c.Check(err, gc.ErrorMatches, "pow")
237
stub.CheckCallNames(c,
238
"Life", "ChangeConfig",
239
// Be careful, these are two different SetPassword receivers.
240
"SetPassword", "SetOldPassword", "SetPassword",
243
checkSaneChange(c, stub.Calls()[2:5])
246
func (*ScaryConnectSuite) TestChangePasswordRemoteDenied(c *gc.C) {
247
// permanent failure case
248
stub := createUnauthorisedStub(nil, nil, nil, nil, apiagent.ErrDenied)
249
err := checkChangePassword(c, stub)
250
c.Check(err, gc.Equals, apicaller.ErrConnectImpossible)
251
stub.CheckCallNames(c,
252
"Life", "ChangeConfig",
253
// Be careful, these are two different SetPassword receivers.
254
"SetPassword", "SetOldPassword", "SetPassword",
257
checkSaneChange(c, stub.Calls()[2:5])
260
func (s *ScaryConnectSuite) TestChangePasswordSuccessAfterUnauthorisedError(c *gc.C) {
261
// This will try to login with old password if current one fails.
262
stub := createUnauthorisedStub()
263
s.assertChangePasswordSuccess(c, stub)
266
func (s *ScaryConnectSuite) TestChangePasswordSuccessAfterBadCurrentPasswordError(c *gc.C) {
267
// This will try to login with old password if current one fails.
268
stub := createPasswordCheckStub(common.ErrBadCreds)
269
s.assertChangePasswordSuccess(c, stub)
272
func (*ScaryConnectSuite) assertChangePasswordSuccess(c *gc.C, stub *testing.Stub) {
273
err := checkChangePassword(c, stub)
274
c.Check(err, gc.Equals, apicaller.ErrChangedPassword)
275
stub.CheckCallNames(c,
276
"Life", "ChangeConfig",
277
// Be careful, these are two different SetPassword receivers.
278
"SetPassword", "SetOldPassword", "SetPassword",
281
checkSaneChange(c, stub.Calls()[2:5])
284
func createUnauthorisedStub(errs ...error) *testing.Stub {
285
return createPasswordCheckStub(¶ms.Error{Code: params.CodeUnauthorized}, errs...)
288
func createPasswordCheckStub(currentPwdLoginErr error, errs ...error) *testing.Stub {
289
allErrs := append([]error{currentPwdLoginErr, nil}, errs...)
291
stub := &testing.Stub{}
292
stub.SetErrors(allErrs...)
296
func checkChangePassword(c *gc.C, stub *testing.Stub) error {
297
// We prepend the unauth/success pair that triggers password
298
// change, and consume them in apiOpen below...
299
//errUnauth := ¶ms.Error{Code: params.CodeUnauthorized}
300
//allErrs := append([]error{errUnauth, nil}, errs...)
302
//stub := &testing.Stub{}
303
//stub.SetErrors(allErrs...)
304
expectConn := &mockConn{stub: stub}
305
apiOpen := func(info *api.Info, opts api.DialOpts) (api.Connection, error) {
306
// ...but we *don't* record the calls themselves; they
307
// are tested plenty elsewhere, and hiding them makes
308
// client code simpler.
309
if err := stub.NextErr(); err != nil {
312
return expectConn, nil
315
entity := names.NewApplicationTag("omg")
316
connect := func() (api.Connection, error) {
317
return apicaller.ScaryConnect(&mockAgent{
319
model: coretesting.ModelTag,
324
conn, err := lifeTest(c, stub, apiagent.Alive, connect)
325
c.Check(conn, gc.IsNil)
329
func checkSaneChange(c *gc.C, calls []testing.StubCall) {
330
c.Assert(calls, gc.HasLen, 3)
332
localSetOld := calls[1]
333
remoteSet := calls[2]
334
chosePassword := localSet.Args[0].(string)
335
switch chosePassword {
336
case "", "new", "old":
337
c.Fatalf("very bad new password: %q", chosePassword)
340
c.Check(localSet, jc.DeepEquals, testing.StubCall{
341
FuncName: "SetPassword",
342
Args: []interface{}{chosePassword},
344
c.Check(localSetOld, jc.DeepEquals, testing.StubCall{
345
FuncName: "SetOldPassword",
346
Args: []interface{}{"old"},
348
c.Check(remoteSet, jc.DeepEquals, testing.StubCall{
349
FuncName: "SetPassword",
350
Args: []interface{}{names.NewApplicationTag("omg"), chosePassword},