github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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/names" 10 "github.com/juju/testing" 11 jc "github.com/juju/testing/checkers" 12 gc "gopkg.in/check.v1" 13 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/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.NewServiceTag("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) TestModelTagCannotChangeConfig(c *gc.C) { 74 stub := checkModelTagUpdate(c, errors.New("oh noes")) 75 stub.CheckCallNames(c, 76 "ChangeConfig", 77 "Life", "SetPassword", 78 ) 79 } 80 81 func (*ScaryConnectSuite) TestModelTagCannotGetTag(c *gc.C) { 82 stub := checkModelTagUpdate(c, nil, errors.New("oh noes")) 83 stub.CheckCallNames(c, 84 "ChangeConfig", "ModelTag", 85 "Life", "SetPassword", 86 ) 87 } 88 89 func (*ScaryConnectSuite) TestModelTagCannotMigrate(c *gc.C) { 90 stub := checkModelTagUpdate(c, nil, nil, errors.New("oh noes")) 91 stub.CheckCallNames(c, 92 "ChangeConfig", "ModelTag", "Migrate", 93 "Life", "SetPassword", 94 ) 95 c.Check(stub.Calls()[2].Args, jc.DeepEquals, []interface{}{ 96 agent.MigrateParams{Model: coretesting.ModelTag}, 97 }) 98 } 99 100 func (*ScaryConnectSuite) TestModelTagSuccess(c *gc.C) { 101 stub := checkModelTagUpdate(c) 102 stub.CheckCallNames(c, 103 "ChangeConfig", "ModelTag", "Migrate", 104 "Life", "SetPassword", 105 ) 106 c.Check(stub.Calls()[2].Args, jc.DeepEquals, []interface{}{ 107 agent.MigrateParams{Model: coretesting.ModelTag}, 108 }) 109 } 110 111 func checkModelTagUpdate(c *gc.C, errs ...error) *testing.Stub { 112 // success case; just a little failure we don't mind, otherwise 113 // equivalent to testEntityFine. 114 stub := &testing.Stub{} 115 stub.SetErrors(errs...) // from ChangeConfig 116 expectConn := &mockConn{stub: stub} 117 apiOpen := func(info *api.Info, opts api.DialOpts) (api.Connection, error) { 118 return expectConn, nil 119 } 120 121 entity := names.NewServiceTag("omg") 122 connect := func() (api.Connection, error) { 123 return apicaller.ScaryConnect(&mockAgent{ 124 stub: stub, 125 // no model set; triggers ChangeConfig 126 entity: entity, 127 }, apiOpen) 128 } 129 conn, err := lifeTest(c, stub, apiagent.Alive, connect) 130 c.Check(conn, gc.Equals, expectConn) 131 c.Check(err, jc.ErrorIsNil) 132 return stub 133 } 134 135 func (*ScaryConnectSuite) TestEntityDead(c *gc.C) { 136 // permanent failure case 137 stub := &testing.Stub{} 138 expectConn := &mockConn{stub: stub} 139 apiOpen := func(info *api.Info, opts api.DialOpts) (api.Connection, error) { 140 return expectConn, nil 141 } 142 143 entity := names.NewServiceTag("omg") 144 connect := func() (api.Connection, error) { 145 return apicaller.ScaryConnect(&mockAgent{ 146 stub: stub, 147 model: coretesting.ModelTag, 148 entity: entity, 149 }, apiOpen) 150 } 151 152 conn, err := lifeTest(c, stub, apiagent.Dead, connect) 153 c.Check(conn, gc.IsNil) 154 c.Check(err, gc.Equals, apicaller.ErrConnectImpossible) 155 stub.CheckCalls(c, []testing.StubCall{{ 156 FuncName: "Life", 157 Args: []interface{}{entity}, 158 }, { 159 FuncName: "Close", 160 }}) 161 } 162 163 func (*ScaryConnectSuite) TestEntityDenied(c *gc.C) { 164 // permanent failure case 165 stub := &testing.Stub{} 166 stub.SetErrors(apiagent.ErrDenied) 167 expectConn := &mockConn{stub: stub} 168 apiOpen := func(info *api.Info, opts api.DialOpts) (api.Connection, error) { 169 return expectConn, nil 170 } 171 172 entity := names.NewServiceTag("omg") 173 connect := func() (api.Connection, error) { 174 return apicaller.ScaryConnect(&mockAgent{ 175 stub: stub, 176 model: coretesting.ModelTag, 177 entity: entity, 178 }, apiOpen) 179 } 180 181 conn, err := lifeTest(c, stub, apiagent.Dead, connect) 182 c.Check(conn, gc.IsNil) 183 c.Check(err, gc.Equals, apicaller.ErrConnectImpossible) 184 stub.CheckCalls(c, []testing.StubCall{{ 185 FuncName: "Life", 186 Args: []interface{}{entity}, 187 }, { 188 FuncName: "Close", 189 }}) 190 } 191 192 func (*ScaryConnectSuite) TestEntityUnknownLife(c *gc.C) { 193 // "random" failure case 194 stub := &testing.Stub{} 195 expectConn := &mockConn{stub: stub} 196 apiOpen := func(info *api.Info, opts api.DialOpts) (api.Connection, error) { 197 return expectConn, nil 198 } 199 200 entity := names.NewServiceTag("omg") 201 connect := func() (api.Connection, error) { 202 return apicaller.ScaryConnect(&mockAgent{ 203 stub: stub, 204 model: coretesting.ModelTag, 205 entity: entity, 206 }, apiOpen) 207 } 208 209 conn, err := lifeTest(c, stub, apiagent.Life("zombie"), connect) 210 c.Check(conn, gc.IsNil) 211 c.Check(err, gc.ErrorMatches, `unknown life value "zombie"`) 212 stub.CheckCalls(c, []testing.StubCall{{ 213 FuncName: "Life", 214 Args: []interface{}{entity}, 215 }, { 216 FuncName: "Close", 217 }}) 218 } 219 220 func (*ScaryConnectSuite) TestChangePasswordConfigError(c *gc.C) { 221 // "random" failure case 222 stub, err := checkChangePassword(c, nil, errors.New("zap")) 223 c.Check(err, gc.ErrorMatches, "zap") 224 stub.CheckCallNames(c, 225 "Life", "ChangeConfig", 226 "Close", 227 ) 228 } 229 230 func (*ScaryConnectSuite) TestChangePasswordRemoteError(c *gc.C) { 231 // "random" failure case 232 stub, err := checkChangePassword(c, 233 nil, nil, nil, nil, errors.New("pow"), 234 ) 235 c.Check(err, gc.ErrorMatches, "pow") 236 stub.CheckCallNames(c, 237 "Life", "ChangeConfig", 238 // Be careful, these are two different SetPassword receivers. 239 "SetPassword", "SetOldPassword", "SetPassword", 240 "Close", 241 ) 242 checkSaneChange(c, stub.Calls()[2:5]) 243 } 244 245 func (*ScaryConnectSuite) TestChangePasswordRemoteDenied(c *gc.C) { 246 // permanent failure case 247 stub, err := checkChangePassword(c, 248 nil, nil, nil, nil, apiagent.ErrDenied, 249 ) 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", 255 "Close", 256 ) 257 checkSaneChange(c, stub.Calls()[2:5]) 258 } 259 260 func (*ScaryConnectSuite) TestChangePasswordSuccess(c *gc.C) { 261 // retry-please failure case 262 stub, err := checkChangePassword(c) 263 c.Check(err, gc.Equals, apicaller.ErrChangedPassword) 264 stub.CheckCallNames(c, 265 "Life", "ChangeConfig", 266 // Be careful, these are two different SetPassword receivers. 267 "SetPassword", "SetOldPassword", "SetPassword", 268 "Close", 269 ) 270 checkSaneChange(c, stub.Calls()[2:5]) 271 } 272 273 func checkChangePassword(c *gc.C, errs ...error) (*testing.Stub, error) { 274 // We prepend the unauth/success pair that triggers password 275 // change, and consume them in apiOpen below... 276 errUnauth := ¶ms.Error{Code: params.CodeUnauthorized} 277 allErrs := append([]error{errUnauth, nil}, errs...) 278 279 stub := &testing.Stub{} 280 stub.SetErrors(allErrs...) 281 expectConn := &mockConn{stub: stub} 282 apiOpen := func(info *api.Info, opts api.DialOpts) (api.Connection, error) { 283 // ...but we *don't* record the calls themselves; they 284 // are tested plenty elsewhere, and hiding them makes 285 // client code simpler. 286 if err := stub.NextErr(); err != nil { 287 return nil, err 288 } 289 return expectConn, nil 290 } 291 292 entity := names.NewServiceTag("omg") 293 connect := func() (api.Connection, error) { 294 return apicaller.ScaryConnect(&mockAgent{ 295 stub: stub, 296 model: coretesting.ModelTag, 297 entity: entity, 298 }, apiOpen) 299 } 300 301 conn, err := lifeTest(c, stub, apiagent.Alive, connect) 302 c.Check(conn, gc.IsNil) 303 return stub, err 304 } 305 306 func checkSaneChange(c *gc.C, calls []testing.StubCall) { 307 c.Assert(calls, gc.HasLen, 3) 308 localSet := calls[0] 309 localSetOld := calls[1] 310 remoteSet := calls[2] 311 chosePassword := localSet.Args[0].(string) 312 switch chosePassword { 313 case "", "new", "old": 314 c.Fatalf("very bad new password: %q", chosePassword) 315 } 316 317 c.Check(localSet, jc.DeepEquals, testing.StubCall{ 318 FuncName: "SetPassword", 319 Args: []interface{}{chosePassword}, 320 }) 321 c.Check(localSetOld, jc.DeepEquals, testing.StubCall{ 322 FuncName: "SetOldPassword", 323 Args: []interface{}{"old"}, 324 }) 325 c.Check(remoteSet, jc.DeepEquals, testing.StubCall{ 326 FuncName: "SetPassword", 327 Args: []interface{}{names.NewServiceTag("omg"), chosePassword}, 328 }) 329 }