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