github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/worker/uniter/runner/context/contextfactory_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package context_test 5 6 import ( 7 "os" 8 "time" 9 10 "github.com/juju/errors" 11 "github.com/juju/testing" 12 jc "github.com/juju/testing/checkers" 13 "github.com/juju/utils" 14 "github.com/juju/utils/fs" 15 gc "gopkg.in/check.v1" 16 "gopkg.in/juju/charm.v6-unstable/hooks" 17 "gopkg.in/juju/names.v2" 18 19 "github.com/juju/juju/apiserver/params" 20 "github.com/juju/juju/core/leadership" 21 "github.com/juju/juju/state" 22 "github.com/juju/juju/storage" 23 "github.com/juju/juju/testcharms" 24 "github.com/juju/juju/worker/uniter/hook" 25 "github.com/juju/juju/worker/uniter/runner/context" 26 runnertesting "github.com/juju/juju/worker/uniter/runner/testing" 27 ) 28 29 type ContextFactorySuite struct { 30 HookContextSuite 31 paths runnertesting.RealPaths 32 factory context.ContextFactory 33 membership map[int][]string 34 } 35 36 var _ = gc.Suite(&ContextFactorySuite{}) 37 38 func (s *ContextFactorySuite) SetUpTest(c *gc.C) { 39 s.HookContextSuite.SetUpTest(c) 40 s.paths = runnertesting.NewRealPaths(c) 41 s.membership = map[int][]string{} 42 43 contextFactory, err := context.NewContextFactory( 44 s.uniter, 45 s.unit.Tag().(names.UnitTag), 46 runnertesting.FakeTracker{}, 47 s.getRelationInfos, 48 s.storage, 49 s.paths, 50 testing.NewClock(time.Time{}), 51 ) 52 c.Assert(err, jc.ErrorIsNil) 53 s.factory = contextFactory 54 } 55 56 func (s *ContextFactorySuite) setUpCacheMethods(c *gc.C) { 57 // The factory's caches are created lazily, so it doesn't have any at all to 58 // begin with. Creating and discarding a context lets us call updateCache 59 // without panicking. (IMO this is less invasive that making updateCache 60 // responsible for creating missing caches etc.) 61 _, err := s.factory.HookContext(hook.Info{Kind: hooks.Install}) 62 c.Assert(err, jc.ErrorIsNil) 63 } 64 65 func (s *ContextFactorySuite) updateCache(relId int, unitName string, settings params.Settings) { 66 context.UpdateCachedSettings(s.factory, relId, unitName, settings) 67 } 68 69 func (s *ContextFactorySuite) getCache(relId int, unitName string) (params.Settings, bool) { 70 return context.CachedSettings(s.factory, relId, unitName) 71 } 72 73 func (s *ContextFactorySuite) SetCharm(c *gc.C, name string) { 74 err := os.RemoveAll(s.paths.GetCharmDir()) 75 c.Assert(err, jc.ErrorIsNil) 76 err = fs.Copy(testcharms.Repo.CharmDirPath(name), s.paths.GetCharmDir()) 77 c.Assert(err, jc.ErrorIsNil) 78 } 79 80 func (s *ContextFactorySuite) getRelationInfos() map[int]*context.RelationInfo { 81 info := map[int]*context.RelationInfo{} 82 for relId, relUnit := range s.apiRelunits { 83 info[relId] = &context.RelationInfo{ 84 RelationUnit: relUnit, 85 MemberNames: s.membership[relId], 86 } 87 } 88 return info 89 } 90 91 func (s *ContextFactorySuite) testLeadershipContextWiring(c *gc.C, createContext func() *context.HookContext) { 92 var stub testing.Stub 93 stub.SetErrors(errors.New("bam")) 94 restore := context.PatchNewLeadershipContext( 95 func(accessor context.LeadershipSettingsAccessor, tracker leadership.Tracker) context.LeadershipContext { 96 stub.AddCall("NewLeadershipContext", accessor, tracker) 97 return &StubLeadershipContext{Stub: &stub} 98 }, 99 ) 100 defer restore() 101 102 ctx := createContext() 103 isLeader, err := ctx.IsLeader() 104 c.Check(err, gc.ErrorMatches, "bam") 105 c.Check(isLeader, jc.IsFalse) 106 107 stub.CheckCalls(c, []testing.StubCall{{ 108 FuncName: "NewLeadershipContext", 109 Args: []interface{}{s.uniter.LeadershipSettings, runnertesting.FakeTracker{}}, 110 }, { 111 FuncName: "IsLeader", 112 }}) 113 114 } 115 116 func (s *ContextFactorySuite) TestNewHookContextLeadershipContext(c *gc.C) { 117 s.testLeadershipContextWiring(c, func() *context.HookContext { 118 ctx, err := s.factory.HookContext(hook.Info{Kind: hooks.ConfigChanged}) 119 c.Assert(err, jc.ErrorIsNil) 120 return ctx 121 }) 122 } 123 124 func (s *ContextFactorySuite) TestNewCommandContextLeadershipContext(c *gc.C) { 125 s.testLeadershipContextWiring(c, func() *context.HookContext { 126 ctx, err := s.factory.CommandContext(context.CommandInfo{RelationId: -1}) 127 c.Assert(err, jc.ErrorIsNil) 128 return ctx 129 }) 130 } 131 132 func (s *ContextFactorySuite) TestNewActionContextLeadershipContext(c *gc.C) { 133 s.testLeadershipContextWiring(c, func() *context.HookContext { 134 s.SetCharm(c, "dummy") 135 action, err := s.State.EnqueueAction(s.unit.Tag(), "snapshot", nil) 136 c.Assert(err, jc.ErrorIsNil) 137 138 actionData := &context.ActionData{ 139 Name: action.Name(), 140 Tag: names.NewActionTag(action.Id()), 141 Params: action.Parameters(), 142 ResultsMap: map[string]interface{}{}, 143 } 144 145 ctx, err := s.factory.ActionContext(actionData) 146 c.Assert(err, jc.ErrorIsNil) 147 return ctx 148 }) 149 } 150 151 func (s *ContextFactorySuite) TestRelationHookContext(c *gc.C) { 152 hi := hook.Info{ 153 Kind: hooks.RelationBroken, 154 RelationId: 1, 155 } 156 ctx, err := s.factory.HookContext(hi) 157 c.Assert(err, jc.ErrorIsNil) 158 s.AssertCoreContext(c, ctx) 159 s.AssertNotActionContext(c, ctx) 160 s.AssertRelationContext(c, ctx, 1, "") 161 s.AssertNotStorageContext(c, ctx) 162 } 163 164 func (s *ContextFactorySuite) TestNewHookContextWithStorage(c *gc.C) { 165 // We need to set up a unit that has storage metadata defined. 166 ch := s.AddTestingCharm(c, "storage-block") 167 sCons := map[string]state.StorageConstraints{ 168 "data": {Pool: "", Size: 1024, Count: 1}, 169 } 170 service := s.AddTestingServiceWithStorage(c, "storage-block", ch, sCons) 171 s.machine = nil // allocate a new machine 172 unit := s.AddUnit(c, service) 173 174 storageAttachments, err := s.State.UnitStorageAttachments(unit.UnitTag()) 175 c.Assert(err, jc.ErrorIsNil) 176 c.Assert(storageAttachments, gc.HasLen, 1) 177 storageTag := storageAttachments[0].StorageInstance() 178 179 volume, err := s.State.StorageInstanceVolume(storageTag) 180 c.Assert(err, jc.ErrorIsNil) 181 volumeTag := volume.VolumeTag() 182 machineTag := s.machine.MachineTag() 183 184 err = s.State.SetVolumeInfo( 185 volumeTag, state.VolumeInfo{ 186 VolumeId: "vol-123", 187 Size: 456, 188 }, 189 ) 190 c.Assert(err, jc.ErrorIsNil) 191 err = s.State.SetVolumeAttachmentInfo( 192 machineTag, volumeTag, state.VolumeAttachmentInfo{ 193 DeviceName: "sdb", 194 }, 195 ) 196 c.Assert(err, jc.ErrorIsNil) 197 198 password, err := utils.RandomPassword() 199 err = unit.SetPassword(password) 200 c.Assert(err, jc.ErrorIsNil) 201 st := s.OpenAPIAs(c, unit.Tag(), password) 202 uniter, err := st.Uniter() 203 c.Assert(err, jc.ErrorIsNil) 204 205 contextFactory, err := context.NewContextFactory( 206 uniter, 207 unit.Tag().(names.UnitTag), 208 runnertesting.FakeTracker{}, 209 s.getRelationInfos, 210 s.storage, 211 s.paths, 212 testing.NewClock(time.Time{}), 213 ) 214 c.Assert(err, jc.ErrorIsNil) 215 ctx, err := contextFactory.HookContext(hook.Info{ 216 Kind: hooks.StorageAttached, 217 StorageId: "data/0", 218 }) 219 c.Assert(err, jc.ErrorIsNil) 220 c.Assert(ctx.UnitName(), gc.Equals, "storage-block/0") 221 s.AssertStorageContext(c, ctx, "data/0", storage.StorageAttachmentInfo{ 222 Kind: storage.StorageKindBlock, 223 Location: "/dev/sdb", 224 }) 225 s.AssertNotActionContext(c, ctx) 226 s.AssertNotRelationContext(c, ctx) 227 } 228 229 func (s *ContextFactorySuite) TestActionContext(c *gc.C) { 230 s.SetCharm(c, "dummy") 231 action, err := s.State.EnqueueAction(s.unit.Tag(), "snapshot", nil) 232 c.Assert(err, jc.ErrorIsNil) 233 234 actionData := &context.ActionData{ 235 Name: action.Name(), 236 Tag: names.NewActionTag(action.Id()), 237 Params: action.Parameters(), 238 ResultsMap: map[string]interface{}{}, 239 } 240 241 ctx, err := s.factory.ActionContext(actionData) 242 c.Assert(err, jc.ErrorIsNil) 243 244 s.AssertCoreContext(c, ctx) 245 s.AssertActionContext(c, ctx) 246 s.AssertNotRelationContext(c, ctx) 247 s.AssertNotStorageContext(c, ctx) 248 } 249 250 func (s *ContextFactorySuite) TestCommandContext(c *gc.C) { 251 ctx, err := s.factory.CommandContext(context.CommandInfo{RelationId: -1}) 252 c.Assert(err, jc.ErrorIsNil) 253 254 s.AssertCoreContext(c, ctx) 255 s.AssertNotActionContext(c, ctx) 256 s.AssertNotRelationContext(c, ctx) 257 s.AssertNotStorageContext(c, ctx) 258 } 259 260 func (s *ContextFactorySuite) TestCommandContextNoRelation(c *gc.C) { 261 ctx, err := s.factory.CommandContext(context.CommandInfo{RelationId: -1}) 262 c.Assert(err, jc.ErrorIsNil) 263 s.AssertCoreContext(c, ctx) 264 s.AssertNotActionContext(c, ctx) 265 s.AssertNotRelationContext(c, ctx) 266 s.AssertNotStorageContext(c, ctx) 267 } 268 269 func (s *ContextFactorySuite) TestNewCommandContextForceNoRemoteUnit(c *gc.C) { 270 ctx, err := s.factory.CommandContext(context.CommandInfo{ 271 RelationId: 0, ForceRemoteUnit: true, 272 }) 273 c.Assert(err, jc.ErrorIsNil) 274 s.AssertCoreContext(c, ctx) 275 s.AssertNotActionContext(c, ctx) 276 s.AssertRelationContext(c, ctx, 0, "") 277 s.AssertNotStorageContext(c, ctx) 278 } 279 280 func (s *ContextFactorySuite) TestNewCommandContextForceRemoteUnitMissing(c *gc.C) { 281 ctx, err := s.factory.CommandContext(context.CommandInfo{ 282 RelationId: 0, RemoteUnitName: "blah/123", ForceRemoteUnit: true, 283 }) 284 c.Assert(err, gc.IsNil) 285 s.AssertCoreContext(c, ctx) 286 s.AssertNotActionContext(c, ctx) 287 s.AssertRelationContext(c, ctx, 0, "blah/123") 288 s.AssertNotStorageContext(c, ctx) 289 } 290 291 func (s *ContextFactorySuite) TestNewCommandContextInferRemoteUnit(c *gc.C) { 292 s.membership[0] = []string{"foo/2"} 293 ctx, err := s.factory.CommandContext(context.CommandInfo{RelationId: 0}) 294 c.Assert(err, jc.ErrorIsNil) 295 s.AssertCoreContext(c, ctx) 296 s.AssertNotActionContext(c, ctx) 297 s.AssertRelationContext(c, ctx, 0, "foo/2") 298 s.AssertNotStorageContext(c, ctx) 299 } 300 301 func (s *ContextFactorySuite) TestNewHookContextPrunesNonMemberCaches(c *gc.C) { 302 303 // Write cached member settings for a member and a non-member. 304 s.setUpCacheMethods(c) 305 s.membership[0] = []string{"rel0/0"} 306 s.updateCache(0, "rel0/0", params.Settings{"keep": "me"}) 307 s.updateCache(0, "rel0/1", params.Settings{"drop": "me"}) 308 309 ctx, err := s.factory.HookContext(hook.Info{Kind: hooks.Install}) 310 c.Assert(err, jc.ErrorIsNil) 311 312 settings0, found := s.getCache(0, "rel0/0") 313 c.Assert(found, jc.IsTrue) 314 c.Assert(settings0, jc.DeepEquals, params.Settings{"keep": "me"}) 315 316 settings1, found := s.getCache(0, "rel0/1") 317 c.Assert(found, jc.IsFalse) 318 c.Assert(settings1, gc.IsNil) 319 320 // Check the caches are being used by the context relations. 321 relCtx, err := ctx.Relation(0) 322 c.Assert(err, jc.ErrorIsNil) 323 324 // Verify that the settings really were cached by trying to look them up. 325 // Nothing's really in scope, so the call would fail if they weren't. 326 settings0, err = relCtx.ReadSettings("rel0/0") 327 c.Assert(err, jc.ErrorIsNil) 328 c.Assert(settings0, jc.DeepEquals, params.Settings{"keep": "me"}) 329 330 // Verify that the non-member settings were purged by looking them up and 331 // checking for the expected error. 332 settings1, err = relCtx.ReadSettings("rel0/1") 333 c.Assert(settings1, gc.IsNil) 334 c.Assert(err, gc.ErrorMatches, "permission denied") 335 } 336 337 func (s *ContextFactorySuite) TestNewHookContextRelationJoinedUpdatesRelationContextAndCaches(c *gc.C) { 338 // Write some cached settings for r/0, so we can verify the cache gets cleared. 339 s.setUpCacheMethods(c) 340 s.membership[1] = []string{"r/0"} 341 s.updateCache(1, "r/0", params.Settings{"foo": "bar"}) 342 343 ctx, err := s.factory.HookContext(hook.Info{ 344 Kind: hooks.RelationJoined, 345 RelationId: 1, 346 RemoteUnit: "r/0", 347 }) 348 c.Assert(err, jc.ErrorIsNil) 349 s.AssertCoreContext(c, ctx) 350 s.AssertNotActionContext(c, ctx) 351 s.AssertNotStorageContext(c, ctx) 352 rel := s.AssertRelationContext(c, ctx, 1, "r/0") 353 c.Assert(rel.UnitNames(), jc.DeepEquals, []string{"r/0"}) 354 cached0, member := s.getCache(1, "r/0") 355 c.Assert(cached0, gc.IsNil) 356 c.Assert(member, jc.IsTrue) 357 } 358 359 func (s *ContextFactorySuite) TestNewHookContextRelationChangedUpdatesRelationContextAndCaches(c *gc.C) { 360 // Update member settings to have actual values, so we can check that 361 // the change for r/4 clears its cache but leaves r/0's alone. 362 s.setUpCacheMethods(c) 363 s.membership[1] = []string{"r/0", "r/4"} 364 s.updateCache(1, "r/0", params.Settings{"foo": "bar"}) 365 s.updateCache(1, "r/4", params.Settings{"baz": "qux"}) 366 367 ctx, err := s.factory.HookContext(hook.Info{ 368 Kind: hooks.RelationChanged, 369 RelationId: 1, 370 RemoteUnit: "r/4", 371 }) 372 c.Assert(err, jc.ErrorIsNil) 373 s.AssertCoreContext(c, ctx) 374 s.AssertNotActionContext(c, ctx) 375 s.AssertNotStorageContext(c, ctx) 376 rel := s.AssertRelationContext(c, ctx, 1, "r/4") 377 c.Assert(rel.UnitNames(), jc.DeepEquals, []string{"r/0", "r/4"}) 378 cached0, member := s.getCache(1, "r/0") 379 c.Assert(cached0, jc.DeepEquals, params.Settings{"foo": "bar"}) 380 c.Assert(member, jc.IsTrue) 381 cached4, member := s.getCache(1, "r/4") 382 c.Assert(cached4, gc.IsNil) 383 c.Assert(member, jc.IsTrue) 384 } 385 386 func (s *ContextFactorySuite) TestNewHookContextRelationDepartedUpdatesRelationContextAndCaches(c *gc.C) { 387 // Update member settings to have actual values, so we can check that 388 // the depart for r/0 leaves r/4's cache alone (while discarding r/0's). 389 s.setUpCacheMethods(c) 390 s.membership[1] = []string{"r/0", "r/4"} 391 s.updateCache(1, "r/0", params.Settings{"foo": "bar"}) 392 s.updateCache(1, "r/4", params.Settings{"baz": "qux"}) 393 394 ctx, err := s.factory.HookContext(hook.Info{ 395 Kind: hooks.RelationDeparted, 396 RelationId: 1, 397 RemoteUnit: "r/0", 398 }) 399 c.Assert(err, jc.ErrorIsNil) 400 s.AssertCoreContext(c, ctx) 401 s.AssertNotActionContext(c, ctx) 402 s.AssertNotStorageContext(c, ctx) 403 rel := s.AssertRelationContext(c, ctx, 1, "r/0") 404 c.Assert(rel.UnitNames(), jc.DeepEquals, []string{"r/4"}) 405 cached0, member := s.getCache(1, "r/0") 406 c.Assert(cached0, gc.IsNil) 407 c.Assert(member, jc.IsFalse) 408 cached4, member := s.getCache(1, "r/4") 409 c.Assert(cached4, jc.DeepEquals, params.Settings{"baz": "qux"}) 410 c.Assert(member, jc.IsTrue) 411 } 412 413 func (s *ContextFactorySuite) TestNewHookContextRelationBrokenRetainsCaches(c *gc.C) { 414 // Note that this is bizarre and unrealistic, because we would never usually 415 // run relation-broken on a non-empty relation. But verfying that the settings 416 // stick around allows us to verify that there's no special handling for that 417 // hook -- as there should not be, because the relation caches will be discarded 418 // for the *next* hook, which will be constructed with the current set of known 419 // relations and ignore everything else. 420 s.setUpCacheMethods(c) 421 s.membership[1] = []string{"r/0", "r/4"} 422 s.updateCache(1, "r/0", params.Settings{"foo": "bar"}) 423 s.updateCache(1, "r/4", params.Settings{"baz": "qux"}) 424 425 ctx, err := s.factory.HookContext(hook.Info{ 426 Kind: hooks.RelationBroken, 427 RelationId: 1, 428 }) 429 c.Assert(err, jc.ErrorIsNil) 430 rel := s.AssertRelationContext(c, ctx, 1, "") 431 c.Assert(rel.UnitNames(), jc.DeepEquals, []string{"r/0", "r/4"}) 432 cached0, member := s.getCache(1, "r/0") 433 c.Assert(cached0, jc.DeepEquals, params.Settings{"foo": "bar"}) 434 c.Assert(member, jc.IsTrue) 435 cached4, member := s.getCache(1, "r/4") 436 c.Assert(cached4, jc.DeepEquals, params.Settings{"baz": "qux"}) 437 c.Assert(member, jc.IsTrue) 438 } 439 440 type StubLeadershipContext struct { 441 context.LeadershipContext 442 *testing.Stub 443 } 444 445 func (stub *StubLeadershipContext) IsLeader() (bool, error) { 446 stub.MethodCall(stub, "IsLeader") 447 return false, stub.NextErr() 448 }