github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/worker/uniter/runner/context/context_test.go (about) 1 // Copyright 2012-2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package context_test 5 6 import ( 7 "errors" 8 "time" 9 10 "github.com/juju/testing" 11 jc "github.com/juju/testing/checkers" 12 gc "gopkg.in/check.v1" 13 "gopkg.in/juju/charm.v6-unstable" 14 15 "github.com/juju/juju/apiserver/params" 16 "github.com/juju/juju/network" 17 "github.com/juju/juju/status" 18 "github.com/juju/juju/worker/uniter/runner" 19 "github.com/juju/juju/worker/uniter/runner/context" 20 "github.com/juju/juju/worker/uniter/runner/jujuc" 21 ) 22 23 type InterfaceSuite struct { 24 HookContextSuite 25 stub testing.Stub 26 } 27 28 var _ = gc.Suite(&InterfaceSuite{}) 29 30 func (s *InterfaceSuite) TestUnitName(c *gc.C) { 31 ctx := s.GetContext(c, -1, "") 32 c.Assert(ctx.UnitName(), gc.Equals, "u/0") 33 } 34 35 func (s *InterfaceSuite) TestHookRelation(c *gc.C) { 36 ctx := s.GetContext(c, -1, "") 37 r, err := ctx.HookRelation() 38 c.Assert(err, gc.ErrorMatches, ".*") 39 c.Assert(r, gc.IsNil) 40 } 41 42 func (s *InterfaceSuite) TestRemoteUnitName(c *gc.C) { 43 ctx := s.GetContext(c, -1, "") 44 name, err := ctx.RemoteUnitName() 45 c.Assert(err, gc.ErrorMatches, ".*") 46 c.Assert(name, gc.Equals, "") 47 } 48 49 func (s *InterfaceSuite) TestRelationIds(c *gc.C) { 50 ctx := s.GetContext(c, -1, "") 51 relIds, err := ctx.RelationIds() 52 c.Assert(err, jc.ErrorIsNil) 53 c.Assert(relIds, gc.HasLen, 2) 54 r, err := ctx.Relation(0) 55 c.Assert(err, jc.ErrorIsNil) 56 c.Assert(r.Name(), gc.Equals, "db") 57 c.Assert(r.FakeId(), gc.Equals, "db:0") 58 r, err = ctx.Relation(123) 59 c.Assert(err, gc.ErrorMatches, ".*") 60 c.Assert(r, gc.IsNil) 61 } 62 63 func (s *InterfaceSuite) TestRelationContext(c *gc.C) { 64 ctx := s.GetContext(c, 1, "") 65 r, err := ctx.HookRelation() 66 c.Assert(err, jc.ErrorIsNil) 67 c.Assert(r.Name(), gc.Equals, "db") 68 c.Assert(r.FakeId(), gc.Equals, "db:1") 69 } 70 71 func (s *InterfaceSuite) TestRelationContextWithRemoteUnitName(c *gc.C) { 72 ctx := s.GetContext(c, 1, "u/123") 73 name, err := ctx.RemoteUnitName() 74 c.Assert(err, jc.ErrorIsNil) 75 c.Assert(name, gc.Equals, "u/123") 76 } 77 78 func (s *InterfaceSuite) TestAddingMetricsInWrongContext(c *gc.C) { 79 ctx := s.GetContext(c, 1, "u/123") 80 err := ctx.AddMetric("key", "123", time.Now()) 81 c.Assert(err, gc.ErrorMatches, "metrics not allowed in this context") 82 } 83 84 func (s *InterfaceSuite) TestAvailabilityZone(c *gc.C) { 85 ctx := s.GetContext(c, -1, "") 86 zone, err := ctx.AvailabilityZone() 87 c.Check(err, jc.ErrorIsNil) 88 c.Check(zone, gc.Equals, "a-zone") 89 } 90 91 func (s *InterfaceSuite) TestUnitNetworkConfig(c *gc.C) { 92 // Only the error case is tested to ensure end-to-end integration, the rest 93 // of the cases are tested separately for network-get, api/uniter, and 94 // apiserver/uniter, respectively. 95 ctx := s.GetContext(c, -1, "") 96 netConfig, err := ctx.NetworkConfig("unknown") 97 c.Check(err, gc.ErrorMatches, `binding name "unknown" not defined by the unit's charm`) 98 c.Check(netConfig, gc.IsNil) 99 } 100 101 func (s *InterfaceSuite) TestUnitStatus(c *gc.C) { 102 ctx := s.GetContext(c, -1, "") 103 defer context.PatchCachedStatus(ctx.(runner.Context), "maintenance", "working", map[string]interface{}{"hello": "world"})() 104 status, err := ctx.UnitStatus() 105 c.Check(err, jc.ErrorIsNil) 106 c.Check(status.Status, gc.Equals, "maintenance") 107 c.Check(status.Info, gc.Equals, "working") 108 c.Check(status.Data, gc.DeepEquals, map[string]interface{}{"hello": "world"}) 109 } 110 111 func (s *InterfaceSuite) TestSetUnitStatus(c *gc.C) { 112 ctx := s.GetContext(c, -1, "") 113 status := jujuc.StatusInfo{ 114 Status: "maintenance", 115 Info: "doing work", 116 } 117 err := ctx.SetUnitStatus(status) 118 c.Check(err, jc.ErrorIsNil) 119 unitStatus, err := ctx.UnitStatus() 120 c.Check(err, jc.ErrorIsNil) 121 c.Check(unitStatus.Status, gc.Equals, "maintenance") 122 c.Check(unitStatus.Info, gc.Equals, "doing work") 123 c.Check(unitStatus.Data, gc.DeepEquals, map[string]interface{}{}) 124 } 125 126 func (s *InterfaceSuite) TestSetUnitStatusUpdatesFlag(c *gc.C) { 127 ctx := s.GetContext(c, -1, "") 128 c.Assert(ctx.(runner.Context).HasExecutionSetUnitStatus(), jc.IsFalse) 129 status := jujuc.StatusInfo{ 130 Status: "maintenance", 131 Info: "doing work", 132 } 133 err := ctx.SetUnitStatus(status) 134 c.Check(err, jc.ErrorIsNil) 135 c.Assert(ctx.(runner.Context).HasExecutionSetUnitStatus(), jc.IsTrue) 136 } 137 138 func (s *InterfaceSuite) TestGetSetWorkloadVersion(c *gc.C) { 139 ctx := s.GetContext(c, -1, "") 140 // No workload version set yet. 141 result, err := ctx.UnitWorkloadVersion() 142 c.Assert(result, gc.Equals, "") 143 c.Assert(err, jc.ErrorIsNil) 144 145 err = ctx.SetUnitWorkloadVersion("Pipey") 146 c.Assert(err, jc.ErrorIsNil) 147 148 result, err = ctx.UnitWorkloadVersion() 149 c.Assert(err, jc.ErrorIsNil) 150 c.Assert(result, gc.Equals, "Pipey") 151 } 152 153 func (s *InterfaceSuite) TestUnitStatusCaching(c *gc.C) { 154 ctx := s.GetContext(c, -1, "") 155 unitStatus, err := ctx.UnitStatus() 156 c.Check(err, jc.ErrorIsNil) 157 c.Check(unitStatus.Status, gc.Equals, "waiting") 158 c.Check(unitStatus.Info, gc.Equals, "waiting for machine") 159 c.Check(unitStatus.Data, gc.DeepEquals, map[string]interface{}{}) 160 161 // Change remote state. 162 now := time.Now() 163 sInfo := status.StatusInfo{ 164 Status: status.Active, 165 Message: "it works", 166 Since: &now, 167 } 168 err = s.unit.SetStatus(sInfo) 169 c.Assert(err, jc.ErrorIsNil) 170 171 // Local view is unchanged. 172 unitStatus, err = ctx.UnitStatus() 173 c.Check(err, jc.ErrorIsNil) 174 c.Check(unitStatus.Status, gc.Equals, "waiting") 175 c.Check(unitStatus.Info, gc.Equals, "waiting for machine") 176 c.Check(unitStatus.Data, gc.DeepEquals, map[string]interface{}{}) 177 } 178 179 func (s *InterfaceSuite) TestUnitCaching(c *gc.C) { 180 ctx := s.GetContext(c, -1, "") 181 pr, err := ctx.PrivateAddress() 182 c.Assert(err, jc.ErrorIsNil) 183 c.Assert(pr, gc.Equals, "u-0.testing.invalid") 184 pa, err := ctx.PublicAddress() 185 c.Assert(err, jc.ErrorIsNil) 186 // Initially the public address is the same as the private address since 187 // the "most public" address is chosen. 188 c.Assert(pr, gc.Equals, pa) 189 190 // Change remote state. 191 err = s.machine.SetProviderAddresses( 192 network.NewScopedAddress("blah.testing.invalid", network.ScopePublic), 193 ) 194 c.Assert(err, jc.ErrorIsNil) 195 196 // Local view is unchanged. 197 pr, err = ctx.PrivateAddress() 198 c.Assert(err, jc.ErrorIsNil) 199 c.Assert(pr, gc.Equals, "u-0.testing.invalid") 200 pa, err = ctx.PublicAddress() 201 c.Assert(err, jc.ErrorIsNil) 202 c.Assert(pr, gc.Equals, pa) 203 } 204 205 func (s *InterfaceSuite) TestConfigCaching(c *gc.C) { 206 ctx := s.GetContext(c, -1, "") 207 settings, err := ctx.ConfigSettings() 208 c.Assert(err, jc.ErrorIsNil) 209 c.Assert(settings, gc.DeepEquals, charm.Settings{"blog-title": "My Title"}) 210 211 // Change remote config. 212 err = s.service.UpdateConfigSettings(charm.Settings{ 213 "blog-title": "Something Else", 214 }) 215 c.Assert(err, jc.ErrorIsNil) 216 217 // Local view is not changed. 218 settings, err = ctx.ConfigSettings() 219 c.Assert(err, jc.ErrorIsNil) 220 c.Assert(settings, gc.DeepEquals, charm.Settings{"blog-title": "My Title"}) 221 } 222 223 // TestNonActionCallsToActionMethodsFail does exactly what its name says: 224 // it simply makes sure that Action-related calls to HookContexts with a nil 225 // actionData member error out correctly. 226 func (s *InterfaceSuite) TestNonActionCallsToActionMethodsFail(c *gc.C) { 227 ctx := context.HookContext{} 228 _, err := ctx.ActionParams() 229 c.Check(err, gc.ErrorMatches, "not running an action") 230 err = ctx.SetActionFailed() 231 c.Check(err, gc.ErrorMatches, "not running an action") 232 err = ctx.SetActionMessage("foo") 233 c.Check(err, gc.ErrorMatches, "not running an action") 234 err = ctx.UpdateActionResults([]string{"1", "2", "3"}, "value") 235 c.Check(err, gc.ErrorMatches, "not running an action") 236 } 237 238 // TestUpdateActionResults demonstrates that UpdateActionResults functions 239 // as expected. 240 func (s *InterfaceSuite) TestUpdateActionResults(c *gc.C) { 241 tests := []struct { 242 initial map[string]interface{} 243 keys []string 244 value string 245 expected map[string]interface{} 246 }{{ 247 initial: map[string]interface{}{}, 248 keys: []string{"foo"}, 249 value: "bar", 250 expected: map[string]interface{}{ 251 "foo": "bar", 252 }, 253 }, { 254 initial: map[string]interface{}{ 255 "foo": "bar", 256 }, 257 keys: []string{"foo", "bar"}, 258 value: "baz", 259 expected: map[string]interface{}{ 260 "foo": map[string]interface{}{ 261 "bar": "baz", 262 }, 263 }, 264 }, { 265 initial: map[string]interface{}{ 266 "foo": map[string]interface{}{ 267 "bar": "baz", 268 }, 269 }, 270 keys: []string{"foo"}, 271 value: "bar", 272 expected: map[string]interface{}{ 273 "foo": "bar", 274 }, 275 }} 276 277 for i, t := range tests { 278 c.Logf("UpdateActionResults test %d: %#v: %#v", i, t.keys, t.value) 279 hctx := context.GetStubActionContext(t.initial) 280 err := hctx.UpdateActionResults(t.keys, t.value) 281 c.Assert(err, jc.ErrorIsNil) 282 actionData, err := hctx.ActionData() 283 c.Assert(err, jc.ErrorIsNil) 284 c.Assert(actionData.ResultsMap, jc.DeepEquals, t.expected) 285 } 286 } 287 288 // TestSetActionFailed ensures SetActionFailed works properly. 289 func (s *InterfaceSuite) TestSetActionFailed(c *gc.C) { 290 hctx := context.GetStubActionContext(nil) 291 err := hctx.SetActionFailed() 292 c.Assert(err, jc.ErrorIsNil) 293 actionData, err := hctx.ActionData() 294 c.Assert(err, jc.ErrorIsNil) 295 c.Check(actionData.Failed, jc.IsTrue) 296 } 297 298 // TestSetActionMessage ensures SetActionMessage works properly. 299 func (s *InterfaceSuite) TestSetActionMessage(c *gc.C) { 300 hctx := context.GetStubActionContext(nil) 301 err := hctx.SetActionMessage("because reasons") 302 c.Assert(err, jc.ErrorIsNil) 303 actionData, err := hctx.ActionData() 304 c.Check(err, jc.ErrorIsNil) 305 c.Check(actionData.ResultsMessage, gc.Equals, "because reasons") 306 } 307 308 func (s *InterfaceSuite) TestRequestRebootAfterHook(c *gc.C) { 309 var killed bool 310 p := &mockProcess{func() error { 311 killed = true 312 return nil 313 }} 314 ctx := s.GetContext(c, -1, "").(*context.HookContext) 315 ctx.SetProcess(p) 316 err := ctx.RequestReboot(jujuc.RebootAfterHook) 317 c.Assert(err, jc.ErrorIsNil) 318 c.Assert(killed, jc.IsFalse) 319 priority := ctx.GetRebootPriority() 320 c.Assert(priority, gc.Equals, jujuc.RebootAfterHook) 321 } 322 323 func (s *InterfaceSuite) TestRequestRebootNow(c *gc.C) { 324 ctx := s.GetContext(c, -1, "").(*context.HookContext) 325 326 var stub testing.Stub 327 var p *mockProcess 328 p = &mockProcess{func() error { 329 // Reboot priority should be set before the process 330 // is killed, or else the client waiting for the 331 // process to exit will race with the setting of 332 // the priority. 333 priority := ctx.GetRebootPriority() 334 c.Assert(priority, gc.Equals, jujuc.RebootNow) 335 return stub.NextErr() 336 }} 337 stub.SetErrors(errors.New("process is already dead")) 338 ctx.SetProcess(p) 339 340 err := ctx.RequestReboot(jujuc.RebootNow) 341 c.Assert(err, jc.ErrorIsNil) 342 343 // Everything went well, so priority should still be RebootNow. 344 priority := ctx.GetRebootPriority() 345 c.Assert(priority, gc.Equals, jujuc.RebootNow) 346 } 347 348 func (s *InterfaceSuite) TestRequestRebootNowTimeout(c *gc.C) { 349 ctx := s.GetContext(c, -1, "").(*context.HookContext) 350 351 var advanced bool 352 var p *mockProcess 353 p = &mockProcess{func() error { 354 // Reboot priority should be set before the process 355 // is killed, or else the client waiting for the 356 // process to exit will race with the setting of 357 // the priority. 358 priority := ctx.GetRebootPriority() 359 c.Assert(priority, gc.Equals, jujuc.RebootNow) 360 if !advanced { 361 advanced = true 362 s.clock.Advance(time.Hour) // force timeout 363 } 364 return nil 365 }} 366 ctx.SetProcess(p) 367 368 err := ctx.RequestReboot(jujuc.RebootNow) 369 c.Assert(err, gc.ErrorMatches, "failed to kill context process 123") 370 371 // RequestReboot failed, so priority should revert to RebootSkip. 372 priority := ctx.GetRebootPriority() 373 c.Assert(priority, gc.Equals, jujuc.RebootSkip) 374 } 375 376 func (s *InterfaceSuite) TestRequestRebootNowNoProcess(c *gc.C) { 377 // A normal hook run or a juju-run command will record the *os.Process 378 // object of the running command, in HookContext. When requesting a 379 // reboot with the --now flag, the process is killed and only 380 // then will we set the reboot priority. This test basically simulates 381 // the case when the process calling juju-reboot is not recorded. 382 ctx := context.HookContext{} 383 err := ctx.RequestReboot(jujuc.RebootNow) 384 c.Assert(err, gc.ErrorMatches, "no process to kill") 385 priority := ctx.GetRebootPriority() 386 c.Assert(priority, gc.Equals, jujuc.RebootNow) 387 } 388 389 func (s *InterfaceSuite) TestStorageAddConstraints(c *gc.C) { 390 expected := map[string][]params.StorageConstraints{ 391 "data": []params.StorageConstraints{ 392 params.StorageConstraints{}, 393 }, 394 } 395 396 ctx := context.HookContext{} 397 addStorageToContext(&ctx, "data", params.StorageConstraints{}) 398 assertStorageAddInContext(c, ctx, expected) 399 } 400 401 var two = uint64(2) 402 403 func (s *InterfaceSuite) TestStorageAddConstraintsSameStorage(c *gc.C) { 404 expected := map[string][]params.StorageConstraints{ 405 "data": []params.StorageConstraints{ 406 params.StorageConstraints{}, 407 params.StorageConstraints{Count: &two}, 408 }, 409 } 410 411 ctx := context.HookContext{} 412 addStorageToContext(&ctx, "data", params.StorageConstraints{}) 413 addStorageToContext(&ctx, "data", params.StorageConstraints{Count: &two}) 414 assertStorageAddInContext(c, ctx, expected) 415 } 416 417 func (s *InterfaceSuite) TestStorageAddConstraintsDifferentStorage(c *gc.C) { 418 expected := map[string][]params.StorageConstraints{ 419 "data": []params.StorageConstraints{params.StorageConstraints{}}, 420 "diff": []params.StorageConstraints{ 421 params.StorageConstraints{Count: &two}}, 422 } 423 424 ctx := context.HookContext{} 425 addStorageToContext(&ctx, "data", params.StorageConstraints{}) 426 addStorageToContext(&ctx, "diff", params.StorageConstraints{Count: &two}) 427 assertStorageAddInContext(c, ctx, expected) 428 } 429 430 func addStorageToContext(ctx *context.HookContext, 431 name string, 432 cons params.StorageConstraints, 433 ) { 434 addOne := map[string]params.StorageConstraints{name: cons} 435 ctx.AddUnitStorage(addOne) 436 } 437 438 func assertStorageAddInContext(c *gc.C, 439 ctx context.HookContext, expected map[string][]params.StorageConstraints, 440 ) { 441 obtained := context.StorageAddConstraints(&ctx) 442 c.Assert(len(obtained), gc.Equals, len(expected)) 443 for k, v := range obtained { 444 c.Assert(v, jc.SameContents, expected[k]) 445 } 446 } 447 448 type mockProcess struct { 449 kill func() error 450 } 451 452 func (p *mockProcess) Kill() error { 453 return p.kill() 454 } 455 456 func (p *mockProcess) Pid() int { 457 return 123 458 }