github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/worker/uniter/runner/util_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 "fmt" 8 "os" 9 "path/filepath" 10 "runtime" 11 "strings" 12 "time" 13 14 "github.com/juju/names" 15 jujutesting "github.com/juju/testing" 16 jc "github.com/juju/testing/checkers" 17 "github.com/juju/utils" 18 "github.com/juju/utils/proxy" 19 "github.com/juju/utils/set" 20 gc "gopkg.in/check.v1" 21 "gopkg.in/juju/charm.v5" 22 23 "github.com/juju/juju/api" 24 "github.com/juju/juju/api/block" 25 "github.com/juju/juju/api/uniter" 26 "github.com/juju/juju/instance" 27 "github.com/juju/juju/juju/testing" 28 "github.com/juju/juju/network" 29 "github.com/juju/juju/state" 30 "github.com/juju/juju/state/multiwatcher" 31 "github.com/juju/juju/storage" 32 "github.com/juju/juju/worker/uniter/runner" 33 "github.com/juju/juju/worker/uniter/runner/jujuc" 34 ) 35 36 var noProxies = proxy.Settings{} 37 var apiAddrs = []string{"a1:123", "a2:123"} 38 var expectedApiAddrs = strings.Join(apiAddrs, " ") 39 40 // MockEnvPaths implements Paths for tests that don't need to actually touch 41 // the filesystem. 42 type MockEnvPaths struct{} 43 44 func (MockEnvPaths) GetToolsDir() string { 45 return "path-to-tools" 46 } 47 48 func (MockEnvPaths) GetCharmDir() string { 49 return "path-to-charm" 50 } 51 52 func (MockEnvPaths) GetJujucSocket() string { 53 return "path-to-jujuc.socket" 54 } 55 56 func (MockEnvPaths) GetMetricsSpoolDir() string { 57 return "path-to-metrics-spool-dir" 58 } 59 60 // RealPaths implements Paths for tests that do touch the filesystem. 61 type RealPaths struct { 62 tools string 63 charm string 64 socket string 65 metricsspool string 66 } 67 68 func osDependentSockPath(c *gc.C) string { 69 sockPath := filepath.Join(c.MkDir(), "test.sock") 70 if runtime.GOOS == "windows" { 71 return `\\.\pipe` + sockPath[2:] 72 } 73 return sockPath 74 } 75 76 func NewRealPaths(c *gc.C) RealPaths { 77 return RealPaths{ 78 tools: c.MkDir(), 79 charm: c.MkDir(), 80 socket: osDependentSockPath(c), 81 metricsspool: c.MkDir(), 82 } 83 } 84 85 func (p RealPaths) GetMetricsSpoolDir() string { 86 return p.metricsspool 87 } 88 89 func (p RealPaths) GetToolsDir() string { 90 return p.tools 91 } 92 93 func (p RealPaths) GetCharmDir() string { 94 return p.charm 95 } 96 97 func (p RealPaths) GetJujucSocket() string { 98 return p.socket 99 } 100 101 // HookContextSuite contains shared setup for various other test suites. Test 102 // methods should not be added to this type, because they'll get run repeatedly. 103 type HookContextSuite struct { 104 testing.JujuConnSuite 105 service *state.Service 106 unit *state.Unit 107 machine *state.Machine 108 relch *state.Charm 109 relunits map[int]*state.RelationUnit 110 storage *storageContextAccessor 111 112 st api.Connection 113 uniter *uniter.State 114 apiUnit *uniter.Unit 115 meteredApiUnit *uniter.Unit 116 meteredCharm *state.Charm 117 apiRelunits map[int]*uniter.RelationUnit 118 BlockHelper 119 } 120 121 func (s *HookContextSuite) SetUpTest(c *gc.C) { 122 var err error 123 s.JujuConnSuite.SetUpTest(c) 124 s.BlockHelper = NewBlockHelper(s.APIState) 125 c.Assert(s.BlockHelper, gc.NotNil) 126 s.AddCleanup(func(*gc.C) { s.BlockHelper.Close() }) 127 128 // reset 129 s.machine = nil 130 131 sch := s.AddTestingCharm(c, "wordpress") 132 s.service = s.AddTestingService(c, "u", sch) 133 s.unit = s.AddUnit(c, s.service) 134 135 s.meteredCharm = s.AddTestingCharm(c, "metered") 136 meteredService := s.AddTestingService(c, "m", s.meteredCharm) 137 meteredUnit := s.addUnit(c, meteredService) 138 err = meteredUnit.SetCharmURL(s.meteredCharm.URL()) 139 c.Assert(err, jc.ErrorIsNil) 140 141 password, err := utils.RandomPassword() 142 err = s.unit.SetPassword(password) 143 c.Assert(err, jc.ErrorIsNil) 144 s.st = s.OpenAPIAs(c, s.unit.Tag(), password) 145 s.uniter, err = s.st.Uniter() 146 c.Assert(err, jc.ErrorIsNil) 147 c.Assert(s.uniter, gc.NotNil) 148 s.apiUnit, err = s.uniter.Unit(s.unit.Tag().(names.UnitTag)) 149 c.Assert(err, jc.ErrorIsNil) 150 151 err = meteredUnit.SetPassword(password) 152 c.Assert(err, jc.ErrorIsNil) 153 meteredState := s.OpenAPIAs(c, meteredUnit.Tag(), password) 154 meteredUniter, err := meteredState.Uniter() 155 s.meteredApiUnit, err = meteredUniter.Unit(meteredUnit.Tag().(names.UnitTag)) 156 c.Assert(err, jc.ErrorIsNil) 157 158 // Note: The unit must always have a charm URL set, because this 159 // happens as part of the installation process (that happens 160 // before the initial install hook). 161 err = s.unit.SetCharmURL(sch.URL()) 162 c.Assert(err, jc.ErrorIsNil) 163 s.relch = s.AddTestingCharm(c, "mysql") 164 s.relunits = map[int]*state.RelationUnit{} 165 s.apiRelunits = map[int]*uniter.RelationUnit{} 166 s.AddContextRelation(c, "db0") 167 s.AddContextRelation(c, "db1") 168 169 storageData0 := names.NewStorageTag("data/0") 170 s.storage = &storageContextAccessor{ 171 map[names.StorageTag]*contextStorage{ 172 storageData0: &contextStorage{ 173 storageData0, 174 storage.StorageKindBlock, 175 "/dev/sdb", 176 }, 177 }, 178 } 179 } 180 181 func (s *HookContextSuite) GetContext( 182 c *gc.C, relId int, remoteName string, 183 ) jujuc.Context { 184 uuid, err := utils.NewUUID() 185 c.Assert(err, jc.ErrorIsNil) 186 return s.getHookContext( 187 c, uuid.String(), relId, remoteName, noProxies, 188 ) 189 } 190 191 func (s *HookContextSuite) addUnit(c *gc.C, svc *state.Service) *state.Unit { 192 unit, err := svc.AddUnit() 193 c.Assert(err, jc.ErrorIsNil) 194 if s.machine != nil { 195 err = unit.AssignToMachine(s.machine) 196 c.Assert(err, jc.ErrorIsNil) 197 return unit 198 } 199 200 err = s.State.AssignUnit(unit, state.AssignCleanEmpty) 201 c.Assert(err, jc.ErrorIsNil) 202 machineId, err := unit.AssignedMachineId() 203 c.Assert(err, jc.ErrorIsNil) 204 s.machine, err = s.State.Machine(machineId) 205 c.Assert(err, jc.ErrorIsNil) 206 zone := "a-zone" 207 hwc := instance.HardwareCharacteristics{ 208 AvailabilityZone: &zone, 209 } 210 err = s.machine.SetProvisioned("i-exist", "fake_nonce", &hwc) 211 c.Assert(err, jc.ErrorIsNil) 212 return unit 213 } 214 215 func (s *HookContextSuite) AddUnit(c *gc.C, svc *state.Service) *state.Unit { 216 unit := s.addUnit(c, svc) 217 name := strings.Replace(unit.Name(), "/", "-", 1) 218 privateAddr := network.NewScopedAddress(name+".testing.invalid", network.ScopeCloudLocal) 219 err := s.machine.SetProviderAddresses(privateAddr) 220 c.Assert(err, jc.ErrorIsNil) 221 return unit 222 } 223 224 func (s *HookContextSuite) AddContextRelation(c *gc.C, name string) { 225 s.AddTestingService(c, name, s.relch) 226 eps, err := s.State.InferEndpoints("u", name) 227 c.Assert(err, jc.ErrorIsNil) 228 rel, err := s.State.AddRelation(eps...) 229 c.Assert(err, jc.ErrorIsNil) 230 ru, err := rel.Unit(s.unit) 231 c.Assert(err, jc.ErrorIsNil) 232 err = ru.EnterScope(map[string]interface{}{"relation-name": name}) 233 c.Assert(err, jc.ErrorIsNil) 234 s.relunits[rel.Id()] = ru 235 apiRel, err := s.uniter.Relation(rel.Tag().(names.RelationTag)) 236 c.Assert(err, jc.ErrorIsNil) 237 apiRelUnit, err := apiRel.Unit(s.apiUnit) 238 c.Assert(err, jc.ErrorIsNil) 239 s.apiRelunits[rel.Id()] = apiRelUnit 240 } 241 242 func (s *HookContextSuite) getHookContext(c *gc.C, uuid string, relid int, 243 remote string, proxies proxy.Settings) *runner.HookContext { 244 if relid != -1 { 245 _, found := s.apiRelunits[relid] 246 c.Assert(found, jc.IsTrue) 247 } 248 facade, err := s.st.Uniter() 249 c.Assert(err, jc.ErrorIsNil) 250 251 relctxs := map[int]*runner.ContextRelation{} 252 for relId, relUnit := range s.apiRelunits { 253 cache := runner.NewRelationCache(relUnit.ReadSettings, nil) 254 relctxs[relId] = runner.NewContextRelation(relUnit, cache) 255 } 256 257 env, err := s.State.Environment() 258 c.Assert(err, jc.ErrorIsNil) 259 260 context, err := runner.NewHookContext(s.apiUnit, facade, "TestCtx", uuid, 261 env.Name(), relid, remote, relctxs, apiAddrs, 262 proxies, false, nil, nil, s.machine.Tag().(names.MachineTag), 263 NewRealPaths(c)) 264 c.Assert(err, jc.ErrorIsNil) 265 return context 266 } 267 268 func (s *HookContextSuite) getMeteredHookContext(c *gc.C, uuid string, relid int, 269 remote string, proxies proxy.Settings, canAddMetrics bool, metrics *charm.Metrics, paths RealPaths) *runner.HookContext { 270 if relid != -1 { 271 _, found := s.apiRelunits[relid] 272 c.Assert(found, jc.IsTrue) 273 } 274 facade, err := s.st.Uniter() 275 c.Assert(err, jc.ErrorIsNil) 276 277 relctxs := map[int]*runner.ContextRelation{} 278 for relId, relUnit := range s.apiRelunits { 279 cache := runner.NewRelationCache(relUnit.ReadSettings, nil) 280 relctxs[relId] = runner.NewContextRelation(relUnit, cache) 281 } 282 283 context, err := runner.NewHookContext(s.meteredApiUnit, facade, "TestCtx", uuid, 284 "test-env-name", relid, remote, relctxs, apiAddrs, 285 proxies, canAddMetrics, metrics, nil, s.machine.Tag().(names.MachineTag), 286 paths) 287 c.Assert(err, jc.ErrorIsNil) 288 return context 289 } 290 291 func (s *HookContextSuite) metricsDefinition(name string) *charm.Metrics { 292 return &charm.Metrics{Metrics: map[string]charm.Metric{name: {Type: charm.MetricTypeGauge, Description: "generated metric"}}} 293 } 294 295 func (s *HookContextSuite) AssertCoreContext(c *gc.C, ctx runner.Context) { 296 c.Assert(ctx.UnitName(), gc.Equals, "u/0") 297 c.Assert(runner.ContextMachineTag(ctx), jc.DeepEquals, names.NewMachineTag("0")) 298 299 expect, expectOK := s.unit.PrivateAddress() 300 actual, actualOK := ctx.PrivateAddress() 301 c.Assert(actual, gc.Equals, expect) 302 c.Assert(actualOK, gc.Equals, expectOK) 303 304 expect, expectOK = s.unit.PublicAddress() 305 actual, actualOK = ctx.PublicAddress() 306 c.Assert(actual, gc.Equals, expect) 307 c.Assert(actualOK, gc.Equals, expectOK) 308 309 env, err := s.State.Environment() 310 c.Assert(err, jc.ErrorIsNil) 311 name, uuid := runner.ContextEnvInfo(ctx) 312 c.Assert(name, gc.Equals, env.Name()) 313 c.Assert(uuid, gc.Equals, env.UUID()) 314 315 c.Assert(ctx.RelationIds(), gc.HasLen, 2) 316 317 r, found := ctx.Relation(0) 318 c.Assert(found, jc.IsTrue) 319 c.Assert(r.Name(), gc.Equals, "db") 320 c.Assert(r.FakeId(), gc.Equals, "db:0") 321 322 r, found = ctx.Relation(1) 323 c.Assert(found, jc.IsTrue) 324 c.Assert(r.Name(), gc.Equals, "db") 325 c.Assert(r.FakeId(), gc.Equals, "db:1") 326 } 327 328 func (s *HookContextSuite) AssertNotActionContext(c *gc.C, ctx runner.Context) { 329 actionData, err := ctx.ActionData() 330 c.Assert(actionData, gc.IsNil) 331 c.Assert(err, gc.ErrorMatches, "not running an action") 332 } 333 334 func (s *HookContextSuite) AssertActionContext(c *gc.C, ctx runner.Context) { 335 actionData, err := ctx.ActionData() 336 c.Assert(actionData, gc.NotNil) 337 c.Assert(err, jc.ErrorIsNil) 338 } 339 340 func (s *HookContextSuite) AssertNotStorageContext(c *gc.C, ctx runner.Context) { 341 storageAttachment, ok := ctx.HookStorage() 342 c.Assert(storageAttachment, gc.IsNil) 343 c.Assert(ok, jc.IsFalse) 344 } 345 346 func (s *HookContextSuite) AssertStorageContext(c *gc.C, ctx runner.Context, id string, attachment storage.StorageAttachmentInfo) { 347 fromCache, ok := ctx.HookStorage() 348 c.Assert(ok, jc.IsTrue) 349 c.Assert(fromCache, gc.NotNil) 350 c.Assert(fromCache.Tag().Id(), gc.Equals, id) 351 c.Assert(fromCache.Kind(), gc.Equals, attachment.Kind) 352 c.Assert(fromCache.Location(), gc.Equals, attachment.Location) 353 } 354 355 func (s *HookContextSuite) AssertRelationContext(c *gc.C, ctx runner.Context, relId int, remoteUnit string) *runner.ContextRelation { 356 actualRemoteUnit, _ := ctx.RemoteUnitName() 357 c.Assert(actualRemoteUnit, gc.Equals, remoteUnit) 358 rel, found := ctx.HookRelation() 359 c.Assert(found, jc.IsTrue) 360 c.Assert(rel.Id(), gc.Equals, relId) 361 return rel.(*runner.ContextRelation) 362 } 363 364 func (s *HookContextSuite) AssertNotRelationContext(c *gc.C, ctx runner.Context) { 365 rel, found := ctx.HookRelation() 366 c.Assert(rel, gc.IsNil) 367 c.Assert(found, jc.IsFalse) 368 } 369 370 // hookSpec supports makeCharm. 371 type hookSpec struct { 372 // dir is the directory to create the hook in. 373 dir string 374 // name is the name of the hook. 375 name string 376 // perm is the file permissions of the hook. 377 perm os.FileMode 378 // code is the exit status of the hook. 379 code int 380 // stdout holds a string to print to stdout 381 stdout string 382 // stderr holds a string to print to stderr 383 stderr string 384 // background holds a string to print in the background after 0.2s. 385 background string 386 } 387 388 // makeCharm constructs a fake charm dir containing a single named hook 389 // with permissions perm and exit code code. If output is non-empty, 390 // the charm will write it to stdout and stderr, with each one prefixed 391 // by name of the stream. 392 func makeCharm(c *gc.C, spec hookSpec, charmDir string) { 393 dir := charmDir 394 if spec.dir != "" { 395 dir = filepath.Join(dir, spec.dir) 396 err := os.Mkdir(dir, 0755) 397 c.Assert(err, jc.ErrorIsNil) 398 } 399 c.Logf("openfile perm %v", spec.perm) 400 hook, err := os.OpenFile( 401 filepath.Join(dir, spec.name), os.O_CREATE|os.O_WRONLY, spec.perm, 402 ) 403 c.Assert(err, jc.ErrorIsNil) 404 defer func() { 405 c.Assert(hook.Close(), gc.IsNil) 406 }() 407 408 printf := func(f string, a ...interface{}) { 409 _, err := fmt.Fprintf(hook, f+"\n", a...) 410 c.Assert(err, jc.ErrorIsNil) 411 } 412 if runtime.GOOS != "windows" { 413 printf("#!/bin/bash") 414 } 415 printf(echoPidScript) 416 if spec.stdout != "" { 417 printf("echo %s", spec.stdout) 418 } 419 if spec.stderr != "" { 420 printf("echo %s >&2", spec.stderr) 421 } 422 if spec.background != "" { 423 // Print something fairly quickly, then sleep for 424 // quite a long time - if the hook execution is 425 // blocking because of the background process, 426 // the hook execution will take much longer than 427 // expected. 428 printf("(sleep 0.2; echo %s; sleep 10) &", spec.background) 429 } 430 printf("exit %d", spec.code) 431 } 432 433 type storageContextAccessor struct { 434 storage map[names.StorageTag]*contextStorage 435 } 436 437 func (s *storageContextAccessor) StorageTags() []names.StorageTag { 438 tags := set.NewTags() 439 for tag := range s.storage { 440 tags.Add(tag) 441 } 442 storageTags := make([]names.StorageTag, len(tags)) 443 for i, tag := range tags.SortedValues() { 444 storageTags[i] = tag.(names.StorageTag) 445 } 446 return storageTags 447 } 448 449 func (s *storageContextAccessor) Storage(tag names.StorageTag) (jujuc.ContextStorageAttachment, bool) { 450 storage, ok := s.storage[tag] 451 return storage, ok 452 } 453 454 type contextStorage struct { 455 tag names.StorageTag 456 kind storage.StorageKind 457 location string 458 } 459 460 func (c *contextStorage) Tag() names.StorageTag { 461 return c.tag 462 } 463 464 func (c *contextStorage) Kind() storage.StorageKind { 465 return c.kind 466 } 467 468 func (c *contextStorage) Location() string { 469 return c.location 470 } 471 472 type BlockHelper struct { 473 blockClient *block.Client 474 } 475 476 // NewBlockHelper creates a block switch used in testing 477 // to manage desired juju blocks. 478 func NewBlockHelper(st api.Connection) BlockHelper { 479 return BlockHelper{ 480 blockClient: block.NewClient(st), 481 } 482 } 483 484 // on switches on desired block and 485 // asserts that no errors were encountered. 486 func (s *BlockHelper) on(c *gc.C, blockType multiwatcher.BlockType, msg string) { 487 c.Assert(s.blockClient.SwitchBlockOn(string(blockType), msg), gc.IsNil) 488 } 489 490 // BlockAllChanges switches changes block on. 491 // This prevents all changes to juju environment. 492 func (s *BlockHelper) BlockAllChanges(c *gc.C, msg string) { 493 s.on(c, multiwatcher.BlockChange, msg) 494 } 495 496 // BlockRemoveObject switches remove block on. 497 // This prevents any object/entity removal on juju environment 498 func (s *BlockHelper) BlockRemoveObject(c *gc.C, msg string) { 499 s.on(c, multiwatcher.BlockRemove, msg) 500 } 501 502 // BlockDestroyEnvironment switches destroy block on. 503 // This prevents juju environment destruction. 504 func (s *BlockHelper) BlockDestroyEnvironment(c *gc.C, msg string) { 505 s.on(c, multiwatcher.BlockDestroy, msg) 506 } 507 508 func (s *BlockHelper) Close() { 509 s.blockClient.Close() 510 } 511 512 // StubMetricsRecorder implements the MetricsRecorder interface. 513 type StubMetricsRecorder struct { 514 *jujutesting.Stub 515 } 516 517 // AddMetric implements the MetricsRecorder interface. 518 func (s StubMetricsRecorder) AddMetric(key, value string, created time.Time) error { 519 s.AddCall("AddMetric", key, value, created) 520 return nil 521 } 522 523 func (mr *StubMetricsRecorder) IsDeclaredMetric(key string) bool { 524 mr.MethodCall(mr, "IsDeclaredMetric", key) 525 return true 526 } 527 528 // Close implements the MetricsRecorder interface. 529 func (s StubMetricsRecorder) Close() error { 530 s.AddCall("Close") 531 return nil 532 } 533 534 var _ runner.MetricsRecorder = (*StubMetricsRecorder)(nil)