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