github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/worker/uniter/runner/context_test.go (about) 1 // Copyright 2012-2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package runner_test 5 6 import ( 7 "os" 8 "runtime" 9 "syscall" 10 "time" 11 12 "github.com/juju/testing" 13 jc "github.com/juju/testing/checkers" 14 "github.com/juju/utils" 15 "github.com/juju/utils/exec" 16 gc "gopkg.in/check.v1" 17 "gopkg.in/juju/charm.v5" 18 19 "github.com/juju/juju/apiserver/params" 20 "github.com/juju/juju/network" 21 "github.com/juju/juju/state" 22 "github.com/juju/juju/worker/uniter/runner" 23 "github.com/juju/juju/worker/uniter/runner/jujuc" 24 ) 25 26 type InterfaceSuite struct { 27 HookContextSuite 28 stub testing.Stub 29 } 30 31 var _ = gc.Suite(&InterfaceSuite{}) 32 33 func (s *InterfaceSuite) TestUnitName(c *gc.C) { 34 ctx := s.GetContext(c, -1, "") 35 c.Assert(ctx.UnitName(), gc.Equals, "u/0") 36 } 37 38 func (s *InterfaceSuite) TestHookRelation(c *gc.C) { 39 ctx := s.GetContext(c, -1, "") 40 r, ok := ctx.HookRelation() 41 c.Assert(ok, jc.IsFalse) 42 c.Assert(r, gc.IsNil) 43 } 44 45 func (s *InterfaceSuite) TestRemoteUnitName(c *gc.C) { 46 ctx := s.GetContext(c, -1, "") 47 name, found := ctx.RemoteUnitName() 48 c.Assert(found, jc.IsFalse) 49 c.Assert(name, gc.Equals, "") 50 } 51 52 func (s *InterfaceSuite) TestRelationIds(c *gc.C) { 53 ctx := s.GetContext(c, -1, "") 54 relIds := ctx.RelationIds() 55 c.Assert(relIds, gc.HasLen, 2) 56 r, found := ctx.Relation(0) 57 c.Assert(found, jc.IsTrue) 58 c.Assert(r.Name(), gc.Equals, "db") 59 c.Assert(r.FakeId(), gc.Equals, "db:0") 60 r, found = ctx.Relation(123) 61 c.Assert(found, jc.IsFalse) 62 c.Assert(r, gc.IsNil) 63 } 64 65 func (s *InterfaceSuite) TestRelationContext(c *gc.C) { 66 ctx := s.GetContext(c, 1, "") 67 r, ok := ctx.HookRelation() 68 c.Assert(ok, jc.IsTrue) 69 c.Assert(r.Name(), gc.Equals, "db") 70 c.Assert(r.FakeId(), gc.Equals, "db:1") 71 } 72 73 func (s *InterfaceSuite) TestRelationContextWithRemoteUnitName(c *gc.C) { 74 ctx := s.GetContext(c, 1, "u/123") 75 name, found := ctx.RemoteUnitName() 76 c.Assert(found, jc.IsTrue) 77 c.Assert(name, gc.Equals, "u/123") 78 } 79 80 func (s *InterfaceSuite) TestAddingMetricsWhenNotEnabledFails(c *gc.C) { 81 ctx := s.GetContext(c, 1, "u/123") 82 err := ctx.AddMetric("key", "123", time.Now()) 83 c.Assert(err, gc.ErrorMatches, "metrics disabled") 84 } 85 86 func (s *InterfaceSuite) TestAddingMetrics(c *gc.C) { 87 uuid := utils.MustNewUUID() 88 ctx := s.getMeteredHookContext(c, uuid.String(), -1, "", noProxies, true, s.metricsDefinition("key"), NewRealPaths(c)) 89 cleanup := runner.PatchMetricsRecorder(ctx, &StubMetricsRecorder{&s.stub}) 90 defer cleanup() 91 92 now := time.Now() 93 err := ctx.AddMetric("key", "123", now) 94 c.Assert(err, jc.ErrorIsNil) 95 96 s.stub.CheckCalls(c, 97 []testing.StubCall{{ 98 FuncName: "AddMetric", 99 Args: []interface{}{"key", "123", now}, 100 }}) 101 } 102 103 func (s *InterfaceSuite) TestAvailabilityZone(c *gc.C) { 104 ctx := s.GetContext(c, -1, "") 105 zone, ok := ctx.AvailabilityZone() 106 c.Check(ok, jc.IsTrue) 107 c.Check(zone, gc.Equals, "a-zone") 108 } 109 110 func (s *InterfaceSuite) TestUnitStatus(c *gc.C) { 111 ctx := s.GetContext(c, -1, "") 112 defer runner.PatchCachedStatus(ctx.(runner.Context), "maintenance", "working", map[string]interface{}{"hello": "world"})() 113 status, err := ctx.UnitStatus() 114 c.Check(err, jc.ErrorIsNil) 115 c.Check(status.Status, gc.Equals, "maintenance") 116 c.Check(status.Info, gc.Equals, "working") 117 c.Check(status.Data, gc.DeepEquals, map[string]interface{}{"hello": "world"}) 118 } 119 120 func (s *InterfaceSuite) TestSetUnitStatus(c *gc.C) { 121 ctx := s.GetContext(c, -1, "") 122 status := jujuc.StatusInfo{ 123 Status: "maintenance", 124 Info: "doing work", 125 } 126 err := ctx.SetUnitStatus(status) 127 c.Check(err, jc.ErrorIsNil) 128 unitStatus, err := ctx.UnitStatus() 129 c.Check(err, jc.ErrorIsNil) 130 c.Check(unitStatus.Status, gc.Equals, "maintenance") 131 c.Check(unitStatus.Info, gc.Equals, "doing work") 132 c.Check(unitStatus.Data, gc.DeepEquals, map[string]interface{}{}) 133 } 134 135 func (s *InterfaceSuite) TestSetUnitStatusUpdatesFlag(c *gc.C) { 136 ctx := s.GetContext(c, -1, "") 137 c.Assert(ctx.(runner.Context).HasExecutionSetUnitStatus(), jc.IsFalse) 138 status := jujuc.StatusInfo{ 139 Status: "maintenance", 140 Info: "doing work", 141 } 142 err := ctx.SetUnitStatus(status) 143 c.Check(err, jc.ErrorIsNil) 144 c.Assert(ctx.(runner.Context).HasExecutionSetUnitStatus(), jc.IsTrue) 145 } 146 147 func (s *InterfaceSuite) TestUnitStatusCaching(c *gc.C) { 148 ctx := s.GetContext(c, -1, "") 149 status, err := ctx.UnitStatus() 150 c.Check(err, jc.ErrorIsNil) 151 c.Check(status.Status, gc.Equals, "unknown") 152 c.Check(status.Data, gc.DeepEquals, map[string]interface{}{}) 153 154 // Change remote state. 155 err = s.unit.SetStatus(state.StatusActive, "it works", nil) 156 c.Assert(err, jc.ErrorIsNil) 157 158 // Local view is unchanged. 159 status, err = ctx.UnitStatus() 160 c.Check(err, jc.ErrorIsNil) 161 c.Check(status.Status, gc.Equals, "unknown") 162 c.Check(status.Data, gc.DeepEquals, map[string]interface{}{}) 163 } 164 165 func (s *InterfaceSuite) TestUnitCaching(c *gc.C) { 166 ctx := s.GetContext(c, -1, "") 167 pr, ok := ctx.PrivateAddress() 168 c.Assert(ok, jc.IsTrue) 169 c.Assert(pr, gc.Equals, "u-0.testing.invalid") 170 pa, ok := ctx.PublicAddress() 171 c.Assert(ok, jc.IsTrue) 172 // Initially the public address is the same as the private address since 173 // the "most public" address is chosen. 174 c.Assert(pr, gc.Equals, pa) 175 176 // Change remote state. 177 err := s.machine.SetProviderAddresses( 178 network.NewScopedAddress("blah.testing.invalid", network.ScopePublic), 179 ) 180 c.Assert(err, jc.ErrorIsNil) 181 182 // Local view is unchanged. 183 pr, ok = ctx.PrivateAddress() 184 c.Assert(ok, jc.IsTrue) 185 c.Assert(pr, gc.Equals, "u-0.testing.invalid") 186 pa, ok = ctx.PublicAddress() 187 c.Assert(ok, jc.IsTrue) 188 c.Assert(pr, gc.Equals, pa) 189 } 190 191 func (s *InterfaceSuite) TestConfigCaching(c *gc.C) { 192 ctx := s.GetContext(c, -1, "") 193 settings, err := ctx.ConfigSettings() 194 c.Assert(err, jc.ErrorIsNil) 195 c.Assert(settings, gc.DeepEquals, charm.Settings{"blog-title": "My Title"}) 196 197 // Change remote config. 198 err = s.service.UpdateConfigSettings(charm.Settings{ 199 "blog-title": "Something Else", 200 }) 201 c.Assert(err, jc.ErrorIsNil) 202 203 // Local view is not changed. 204 settings, err = ctx.ConfigSettings() 205 c.Assert(err, jc.ErrorIsNil) 206 c.Assert(settings, gc.DeepEquals, charm.Settings{"blog-title": "My Title"}) 207 } 208 209 // TestNonActionCallsToActionMethodsFail does exactly what its name says: 210 // it simply makes sure that Action-related calls to HookContexts with a nil 211 // actionData member error out correctly. 212 func (s *InterfaceSuite) TestNonActionCallsToActionMethodsFail(c *gc.C) { 213 ctx := runner.HookContext{} 214 _, err := ctx.ActionParams() 215 c.Check(err, gc.ErrorMatches, "not running an action") 216 err = ctx.SetActionFailed() 217 c.Check(err, gc.ErrorMatches, "not running an action") 218 err = ctx.SetActionMessage("foo") 219 c.Check(err, gc.ErrorMatches, "not running an action") 220 err = ctx.UpdateActionResults([]string{"1", "2", "3"}, "value") 221 c.Check(err, gc.ErrorMatches, "not running an action") 222 } 223 224 // TestUpdateActionResults demonstrates that UpdateActionResults functions 225 // as expected. 226 func (s *InterfaceSuite) TestUpdateActionResults(c *gc.C) { 227 tests := []struct { 228 initial map[string]interface{} 229 keys []string 230 value string 231 expected map[string]interface{} 232 }{{ 233 initial: map[string]interface{}{}, 234 keys: []string{"foo"}, 235 value: "bar", 236 expected: map[string]interface{}{ 237 "foo": "bar", 238 }, 239 }, { 240 initial: map[string]interface{}{ 241 "foo": "bar", 242 }, 243 keys: []string{"foo", "bar"}, 244 value: "baz", 245 expected: map[string]interface{}{ 246 "foo": map[string]interface{}{ 247 "bar": "baz", 248 }, 249 }, 250 }, { 251 initial: map[string]interface{}{ 252 "foo": map[string]interface{}{ 253 "bar": "baz", 254 }, 255 }, 256 keys: []string{"foo"}, 257 value: "bar", 258 expected: map[string]interface{}{ 259 "foo": "bar", 260 }, 261 }} 262 263 for i, t := range tests { 264 c.Logf("UpdateActionResults test %d: %#v: %#v", i, t.keys, t.value) 265 hctx := runner.GetStubActionContext(t.initial) 266 err := hctx.UpdateActionResults(t.keys, t.value) 267 c.Assert(err, jc.ErrorIsNil) 268 actionData, err := hctx.ActionData() 269 c.Assert(err, jc.ErrorIsNil) 270 c.Assert(actionData.ResultsMap, jc.DeepEquals, t.expected) 271 } 272 } 273 274 // TestSetActionFailed ensures SetActionFailed works properly. 275 func (s *InterfaceSuite) TestSetActionFailed(c *gc.C) { 276 hctx := runner.GetStubActionContext(nil) 277 err := hctx.SetActionFailed() 278 c.Assert(err, jc.ErrorIsNil) 279 actionData, err := hctx.ActionData() 280 c.Assert(err, jc.ErrorIsNil) 281 c.Check(actionData.Failed, jc.IsTrue) 282 } 283 284 // TestSetActionMessage ensures SetActionMessage works properly. 285 func (s *InterfaceSuite) TestSetActionMessage(c *gc.C) { 286 hctx := runner.GetStubActionContext(nil) 287 err := hctx.SetActionMessage("because reasons") 288 c.Assert(err, jc.ErrorIsNil) 289 actionData, err := hctx.ActionData() 290 c.Check(err, jc.ErrorIsNil) 291 c.Check(actionData.ResultsMessage, gc.Equals, "because reasons") 292 } 293 294 func (s *InterfaceSuite) startProcess(c *gc.C) *os.Process { 295 command := exec.RunParams{ 296 Commands: "trap 'exit 0' SIGTERM; while true;do sleep 1;done", 297 } 298 err := command.Run() 299 c.Assert(err, jc.ErrorIsNil) 300 p := command.Process() 301 s.AddCleanup(func(c *gc.C) { p.Kill() }) 302 return p 303 } 304 305 func (s *InterfaceSuite) TestRequestRebootAfterHook(c *gc.C) { 306 if runtime.GOOS == "windows" { 307 c.Skip("bug 1403084: Cannot send sigterm on windows") 308 } 309 ctx := runner.HookContext{} 310 p := s.startProcess(c) 311 ctx.SetProcess(p) 312 err := ctx.RequestReboot(jujuc.RebootAfterHook) 313 c.Assert(err, jc.ErrorIsNil) 314 err = p.Signal(syscall.SIGTERM) 315 c.Assert(err, jc.ErrorIsNil) 316 _, err = p.Wait() 317 c.Assert(err, jc.ErrorIsNil) 318 priority := ctx.GetRebootPriority() 319 c.Assert(priority, gc.Equals, jujuc.RebootAfterHook) 320 } 321 322 func (s *InterfaceSuite) TestRequestRebootNow(c *gc.C) { 323 ctx := runner.HookContext{} 324 p := s.startProcess(c) 325 ctx.SetProcess(p) 326 go func() { 327 _, err := p.Wait() 328 c.Assert(err, jc.ErrorIsNil) 329 }() 330 err := ctx.RequestReboot(jujuc.RebootNow) 331 c.Assert(err, jc.ErrorIsNil) 332 priority := ctx.GetRebootPriority() 333 c.Assert(priority, gc.Equals, jujuc.RebootNow) 334 } 335 336 func (s *InterfaceSuite) TestRequestRebootNowNoProcess(c *gc.C) { 337 // A normal hook run or a juju-run command will record the *os.Process 338 // object of the running command, in HookContext. When requesting a 339 // reboot with the --now flag, the process is killed and only 340 // then will we set the reboot priority. This test basically simulates 341 // the case when the process calling juju-reboot is not recorded. 342 ctx := runner.HookContext{} 343 err := ctx.RequestReboot(jujuc.RebootNow) 344 c.Assert(err, gc.ErrorMatches, "no process to kill") 345 priority := ctx.GetRebootPriority() 346 c.Assert(priority, gc.Equals, jujuc.RebootNow) 347 } 348 349 func (s *InterfaceSuite) TestStorageAddConstraints(c *gc.C) { 350 expected := map[string][]params.StorageConstraints{ 351 "data": []params.StorageConstraints{ 352 params.StorageConstraints{}, 353 }, 354 } 355 356 ctx := runner.HookContext{} 357 addStorageToContext(&ctx, "data", params.StorageConstraints{}) 358 assertStorageAddInContext(c, ctx, expected) 359 } 360 361 var two = uint64(2) 362 363 func (s *InterfaceSuite) TestStorageAddConstraintsSameStorage(c *gc.C) { 364 expected := map[string][]params.StorageConstraints{ 365 "data": []params.StorageConstraints{ 366 params.StorageConstraints{}, 367 params.StorageConstraints{Count: &two}, 368 }, 369 } 370 371 ctx := runner.HookContext{} 372 addStorageToContext(&ctx, "data", params.StorageConstraints{}) 373 addStorageToContext(&ctx, "data", params.StorageConstraints{Count: &two}) 374 assertStorageAddInContext(c, ctx, expected) 375 } 376 377 func (s *InterfaceSuite) TestStorageAddConstraintsDifferentStorage(c *gc.C) { 378 expected := map[string][]params.StorageConstraints{ 379 "data": []params.StorageConstraints{params.StorageConstraints{}}, 380 "diff": []params.StorageConstraints{ 381 params.StorageConstraints{Count: &two}}, 382 } 383 384 ctx := runner.HookContext{} 385 addStorageToContext(&ctx, "data", params.StorageConstraints{}) 386 addStorageToContext(&ctx, "diff", params.StorageConstraints{Count: &two}) 387 assertStorageAddInContext(c, ctx, expected) 388 } 389 390 func addStorageToContext(ctx *runner.HookContext, 391 name string, 392 cons params.StorageConstraints, 393 ) { 394 addOne := map[string]params.StorageConstraints{name: cons} 395 ctx.AddUnitStorage(addOne) 396 } 397 398 func assertStorageAddInContext(c *gc.C, 399 ctx runner.HookContext, expected map[string][]params.StorageConstraints, 400 ) { 401 obtained := ctx.StorageAddConstraints() 402 c.Assert(len(obtained), gc.Equals, len(expected)) 403 for k, v := range obtained { 404 c.Assert(v, jc.SameContents, expected[k]) 405 } 406 }