github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/apicaller/connect_test.go (about) 1 // Copyright 2012-2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package apicaller_test 5 6 import ( 7 "errors" 8 9 "github.com/juju/testing" 10 jc "github.com/juju/testing/checkers" 11 gc "gopkg.in/check.v1" 12 "gopkg.in/juju/names.v2" 13 14 "github.com/juju/juju/api" 15 apiagent "github.com/juju/juju/api/agent" 16 "github.com/juju/juju/apiserver/common" 17 "github.com/juju/juju/apiserver/params" 18 coretesting "github.com/juju/juju/testing" 19 "github.com/juju/juju/worker/apicaller" 20 ) 21 22 // ScaryConnectSuite should cover all the *lines* where we get a connection 23 // without triggering the checkProvisionedStrategy ugliness. It tests the 24 // various conditions in isolation; it's possible that some real scenarios 25 // may trigger more than one of these, but it's impractical to test *every* 26 // possible *path*. 27 type ScaryConnectSuite struct { 28 testing.IsolationSuite 29 } 30 31 var _ = gc.Suite(&ScaryConnectSuite{}) 32 33 func (*ScaryConnectSuite) TestEntityAlive(c *gc.C) { 34 testEntityFine(c, apiagent.Alive) 35 } 36 37 func (*ScaryConnectSuite) TestEntityDying(c *gc.C) { 38 testEntityFine(c, apiagent.Dying) 39 } 40 41 func testEntityFine(c *gc.C, life apiagent.Life) { 42 stub := &testing.Stub{} 43 expectConn := &mockConn{stub: stub} 44 apiOpen := func(info *api.Info, opts api.DialOpts) (api.Connection, error) { 45 // no apiOpen stub calls necessary in this suite; covered 46 // by RetrySuite, just an extra complication here. 47 return expectConn, nil 48 } 49 50 // to make the point that this code should be entity-agnostic, 51 // use an entity that doesn't correspond to an agent at all. 52 entity := names.NewApplicationTag("omg") 53 connect := func() (api.Connection, error) { 54 return apicaller.ScaryConnect(&mockAgent{ 55 stub: stub, 56 model: coretesting.ModelTag, 57 entity: entity, 58 }, apiOpen) 59 } 60 61 conn, err := lifeTest(c, stub, apiagent.Alive, connect) 62 c.Check(conn, gc.Equals, expectConn) 63 c.Check(err, jc.ErrorIsNil) 64 stub.CheckCalls(c, []testing.StubCall{{ 65 FuncName: "Life", 66 Args: []interface{}{entity}, 67 }, { 68 FuncName: "SetPassword", 69 Args: []interface{}{entity, "new"}, 70 }}) 71 } 72 73 func (*ScaryConnectSuite) TestEntityDead(c *gc.C) { 74 // permanent failure case 75 stub := &testing.Stub{} 76 expectConn := &mockConn{stub: stub} 77 apiOpen := func(info *api.Info, opts api.DialOpts) (api.Connection, error) { 78 return expectConn, nil 79 } 80 81 entity := names.NewApplicationTag("omg") 82 connect := func() (api.Connection, error) { 83 return apicaller.ScaryConnect(&mockAgent{ 84 stub: stub, 85 model: coretesting.ModelTag, 86 entity: entity, 87 }, apiOpen) 88 } 89 90 conn, err := lifeTest(c, stub, apiagent.Dead, connect) 91 c.Check(conn, gc.IsNil) 92 c.Check(err, gc.Equals, apicaller.ErrConnectImpossible) 93 stub.CheckCalls(c, []testing.StubCall{{ 94 FuncName: "Life", 95 Args: []interface{}{entity}, 96 }, { 97 FuncName: "Close", 98 }}) 99 } 100 101 func (*ScaryConnectSuite) TestEntityDenied(c *gc.C) { 102 // permanent failure case 103 stub := &testing.Stub{} 104 stub.SetErrors(apiagent.ErrDenied) 105 expectConn := &mockConn{stub: stub} 106 apiOpen := func(info *api.Info, opts api.DialOpts) (api.Connection, error) { 107 return expectConn, nil 108 } 109 110 entity := names.NewApplicationTag("omg") 111 connect := func() (api.Connection, error) { 112 return apicaller.ScaryConnect(&mockAgent{ 113 stub: stub, 114 model: coretesting.ModelTag, 115 entity: entity, 116 }, apiOpen) 117 } 118 119 conn, err := lifeTest(c, stub, apiagent.Dead, connect) 120 c.Check(conn, gc.IsNil) 121 c.Check(err, gc.Equals, apicaller.ErrConnectImpossible) 122 stub.CheckCalls(c, []testing.StubCall{{ 123 FuncName: "Life", 124 Args: []interface{}{entity}, 125 }, { 126 FuncName: "Close", 127 }}) 128 } 129 130 func (*ScaryConnectSuite) TestEntityUnknownLife(c *gc.C) { 131 // "random" failure case 132 stub := &testing.Stub{} 133 expectConn := &mockConn{stub: stub} 134 apiOpen := func(info *api.Info, opts api.DialOpts) (api.Connection, error) { 135 return expectConn, nil 136 } 137 138 entity := names.NewApplicationTag("omg") 139 connect := func() (api.Connection, error) { 140 return apicaller.ScaryConnect(&mockAgent{ 141 stub: stub, 142 model: coretesting.ModelTag, 143 entity: entity, 144 }, apiOpen) 145 } 146 147 conn, err := lifeTest(c, stub, apiagent.Life("zombie"), connect) 148 c.Check(conn, gc.IsNil) 149 c.Check(err, gc.ErrorMatches, `unknown life value "zombie"`) 150 stub.CheckCalls(c, []testing.StubCall{{ 151 FuncName: "Life", 152 Args: []interface{}{entity}, 153 }, { 154 FuncName: "Close", 155 }}) 156 } 157 158 func (*ScaryConnectSuite) TestChangePasswordConfigError(c *gc.C) { 159 // "random" failure case 160 stub := createUnauthorisedStub(nil, errors.New("zap")) 161 err := checkChangePassword(c, stub) 162 c.Check(err, gc.ErrorMatches, "zap") 163 stub.CheckCallNames(c, 164 "Life", "ChangeConfig", 165 "Close", 166 ) 167 } 168 169 func (*ScaryConnectSuite) TestChangePasswordRemoteError(c *gc.C) { 170 // "random" failure case 171 stub := createUnauthorisedStub(nil, nil, nil, nil, errors.New("pow")) 172 err := checkChangePassword(c, stub) 173 c.Check(err, gc.ErrorMatches, "pow") 174 stub.CheckCallNames(c, 175 "Life", "ChangeConfig", 176 // Be careful, these are two different SetPassword receivers. 177 "SetPassword", "SetOldPassword", "SetPassword", 178 "Close", 179 ) 180 checkSaneChange(c, stub.Calls()[2:5]) 181 } 182 183 func (*ScaryConnectSuite) TestChangePasswordRemoteDenied(c *gc.C) { 184 // permanent failure case 185 stub := createUnauthorisedStub(nil, nil, nil, nil, apiagent.ErrDenied) 186 err := checkChangePassword(c, stub) 187 c.Check(err, gc.Equals, apicaller.ErrConnectImpossible) 188 stub.CheckCallNames(c, 189 "Life", "ChangeConfig", 190 // Be careful, these are two different SetPassword receivers. 191 "SetPassword", "SetOldPassword", "SetPassword", 192 "Close", 193 ) 194 checkSaneChange(c, stub.Calls()[2:5]) 195 } 196 197 func (s *ScaryConnectSuite) TestChangePasswordSuccessAfterUnauthorisedError(c *gc.C) { 198 // This will try to login with old password if current one fails. 199 stub := createUnauthorisedStub() 200 s.assertChangePasswordSuccess(c, stub) 201 } 202 203 func (s *ScaryConnectSuite) TestChangePasswordSuccessAfterBadCurrentPasswordError(c *gc.C) { 204 // This will try to login with old password if current one fails. 205 stub := createPasswordCheckStub(common.ErrBadCreds) 206 s.assertChangePasswordSuccess(c, stub) 207 } 208 209 func (*ScaryConnectSuite) assertChangePasswordSuccess(c *gc.C, stub *testing.Stub) { 210 err := checkChangePassword(c, stub) 211 c.Check(err, gc.Equals, apicaller.ErrChangedPassword) 212 stub.CheckCallNames(c, 213 "Life", "ChangeConfig", 214 // Be careful, these are two different SetPassword receivers. 215 "SetPassword", "SetOldPassword", "SetPassword", 216 "Close", 217 ) 218 checkSaneChange(c, stub.Calls()[2:5]) 219 } 220 221 func createUnauthorisedStub(errs ...error) *testing.Stub { 222 return createPasswordCheckStub(¶ms.Error{Code: params.CodeUnauthorized}, errs...) 223 } 224 225 func createPasswordCheckStub(currentPwdLoginErr error, errs ...error) *testing.Stub { 226 allErrs := append([]error{currentPwdLoginErr, nil}, errs...) 227 228 stub := &testing.Stub{} 229 stub.SetErrors(allErrs...) 230 return stub 231 } 232 233 func checkChangePassword(c *gc.C, stub *testing.Stub) error { 234 // We prepend the unauth/success pair that triggers password 235 // change, and consume them in apiOpen below... 236 //errUnauth := ¶ms.Error{Code: params.CodeUnauthorized} 237 //allErrs := append([]error{errUnauth, nil}, errs...) 238 // 239 //stub := &testing.Stub{} 240 //stub.SetErrors(allErrs...) 241 expectConn := &mockConn{stub: stub} 242 apiOpen := func(info *api.Info, opts api.DialOpts) (api.Connection, error) { 243 // ...but we *don't* record the calls themselves; they 244 // are tested plenty elsewhere, and hiding them makes 245 // client code simpler. 246 if err := stub.NextErr(); err != nil { 247 return nil, err 248 } 249 return expectConn, nil 250 } 251 252 entity := names.NewApplicationTag("omg") 253 connect := func() (api.Connection, error) { 254 return apicaller.ScaryConnect(&mockAgent{ 255 stub: stub, 256 model: coretesting.ModelTag, 257 entity: entity, 258 }, apiOpen) 259 } 260 261 conn, err := lifeTest(c, stub, apiagent.Alive, connect) 262 c.Check(conn, gc.IsNil) 263 return err 264 } 265 266 func checkSaneChange(c *gc.C, calls []testing.StubCall) { 267 c.Assert(calls, gc.HasLen, 3) 268 localSet := calls[0] 269 localSetOld := calls[1] 270 remoteSet := calls[2] 271 chosePassword := localSet.Args[0].(string) 272 switch chosePassword { 273 case "", "new", "old": 274 c.Fatalf("very bad new password: %q", chosePassword) 275 } 276 277 c.Check(localSet, jc.DeepEquals, testing.StubCall{ 278 FuncName: "SetPassword", 279 Args: []interface{}{chosePassword}, 280 }) 281 c.Check(localSetOld, jc.DeepEquals, testing.StubCall{ 282 FuncName: "SetOldPassword", 283 Args: []interface{}{"old"}, 284 }) 285 c.Check(remoteSet, jc.DeepEquals, testing.StubCall{ 286 FuncName: "SetPassword", 287 Args: []interface{}{names.NewApplicationTag("omg"), chosePassword}, 288 }) 289 }