github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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 "strings" 11 12 "github.com/juju/names" 13 jc "github.com/juju/testing/checkers" 14 "github.com/juju/utils" 15 "github.com/juju/utils/proxy" 16 gc "gopkg.in/check.v1" 17 "gopkg.in/juju/charm.v4" 18 19 "github.com/juju/juju/api" 20 "github.com/juju/juju/api/uniter" 21 "github.com/juju/juju/instance" 22 "github.com/juju/juju/juju/testing" 23 "github.com/juju/juju/network" 24 "github.com/juju/juju/state" 25 "github.com/juju/juju/worker/uniter/runner" 26 ) 27 28 var noProxies = proxy.Settings{} 29 var apiAddrs = []string{"a1:123", "a2:123"} 30 var expectedApiAddrs = strings.Join(apiAddrs, " ") 31 32 // MockEnvPaths implements Paths for tests that don't need to actually touch 33 // the filesystem. 34 type MockEnvPaths struct{} 35 36 func (MockEnvPaths) GetToolsDir() string { 37 return "path-to-tools" 38 } 39 40 func (MockEnvPaths) GetCharmDir() string { 41 return "path-to-charm" 42 } 43 44 func (MockEnvPaths) GetJujucSocket() string { 45 return "path-to-jujuc.socket" 46 } 47 48 // RealPaths implements Paths for tests that do touch the filesystem. 49 type RealPaths struct { 50 tools string 51 charm string 52 socket string 53 } 54 55 func NewRealPaths(c *gc.C) RealPaths { 56 return RealPaths{ 57 tools: c.MkDir(), 58 charm: c.MkDir(), 59 socket: filepath.Join(c.MkDir(), "jujuc.socket"), 60 } 61 } 62 63 func (p RealPaths) GetToolsDir() string { 64 return p.tools 65 } 66 67 func (p RealPaths) GetCharmDir() string { 68 return p.charm 69 } 70 71 func (p RealPaths) GetJujucSocket() string { 72 return p.socket 73 } 74 75 // HookContextSuite contains shared setup for various other test suites. Test 76 // methods should not be added to this type, because they'll get run repeatedly. 77 type HookContextSuite struct { 78 testing.JujuConnSuite 79 service *state.Service 80 unit *state.Unit 81 machine *state.Machine 82 relch *state.Charm 83 relunits map[int]*state.RelationUnit 84 85 st *api.State 86 uniter *uniter.State 87 apiUnit *uniter.Unit 88 meteredApiUnit *uniter.Unit 89 apiRelunits map[int]*uniter.RelationUnit 90 } 91 92 func (s *HookContextSuite) SetUpTest(c *gc.C) { 93 var err error 94 s.JujuConnSuite.SetUpTest(c) 95 96 // reset 97 s.machine = nil 98 99 sch := s.AddTestingCharm(c, "wordpress") 100 s.service = s.AddTestingService(c, "u", sch) 101 s.unit = s.AddUnit(c, s.service) 102 103 meteredCharm := s.AddTestingCharm(c, "metered") 104 meteredService := s.AddTestingService(c, "m", meteredCharm) 105 meteredUnit := s.addUnit(c, meteredService) 106 err = meteredUnit.SetCharmURL(meteredCharm.URL()) 107 c.Assert(err, jc.ErrorIsNil) 108 109 password, err := utils.RandomPassword() 110 err = s.unit.SetPassword(password) 111 c.Assert(err, jc.ErrorIsNil) 112 s.st = s.OpenAPIAs(c, s.unit.Tag(), password) 113 s.uniter, err = s.st.Uniter() 114 c.Assert(err, jc.ErrorIsNil) 115 c.Assert(s.uniter, gc.NotNil) 116 s.apiUnit, err = s.uniter.Unit(s.unit.Tag().(names.UnitTag)) 117 c.Assert(err, jc.ErrorIsNil) 118 119 err = meteredUnit.SetPassword(password) 120 c.Assert(err, jc.ErrorIsNil) 121 meteredState := s.OpenAPIAs(c, meteredUnit.Tag(), password) 122 meteredUniter, err := meteredState.Uniter() 123 s.meteredApiUnit, err = meteredUniter.Unit(meteredUnit.Tag().(names.UnitTag)) 124 c.Assert(err, jc.ErrorIsNil) 125 126 // Note: The unit must always have a charm URL set, because this 127 // happens as part of the installation process (that happens 128 // before the initial install hook). 129 err = s.unit.SetCharmURL(sch.URL()) 130 c.Assert(err, jc.ErrorIsNil) 131 s.relch = s.AddTestingCharm(c, "mysql") 132 s.relunits = map[int]*state.RelationUnit{} 133 s.apiRelunits = map[int]*uniter.RelationUnit{} 134 s.AddContextRelation(c, "db0") 135 s.AddContextRelation(c, "db1") 136 } 137 138 func (s *HookContextSuite) addUnit(c *gc.C, svc *state.Service) *state.Unit { 139 unit, err := svc.AddUnit() 140 c.Assert(err, jc.ErrorIsNil) 141 if s.machine == nil { 142 s.machine, err = s.State.AddMachine("quantal", state.JobHostUnits) 143 c.Assert(err, jc.ErrorIsNil) 144 zone := "a-zone" 145 hwc := instance.HardwareCharacteristics{ 146 AvailabilityZone: &zone, 147 } 148 err = s.machine.SetProvisioned("i-exist", "fake_nonce", &hwc) 149 c.Assert(err, jc.ErrorIsNil) 150 } 151 err = unit.AssignToMachine(s.machine) 152 c.Assert(err, jc.ErrorIsNil) 153 return unit 154 } 155 156 func (s *HookContextSuite) AddUnit(c *gc.C, svc *state.Service) *state.Unit { 157 unit := s.addUnit(c, svc) 158 name := strings.Replace(unit.Name(), "/", "-", 1) 159 privateAddr := network.NewAddress(name+".testing.invalid", network.ScopeCloudLocal) 160 err := s.machine.SetAddresses(privateAddr) 161 c.Assert(err, jc.ErrorIsNil) 162 return unit 163 } 164 165 func (s *HookContextSuite) AddContextRelation(c *gc.C, name string) { 166 s.AddTestingService(c, name, s.relch) 167 eps, err := s.State.InferEndpoints("u", name) 168 c.Assert(err, jc.ErrorIsNil) 169 rel, err := s.State.AddRelation(eps...) 170 c.Assert(err, jc.ErrorIsNil) 171 ru, err := rel.Unit(s.unit) 172 c.Assert(err, jc.ErrorIsNil) 173 err = ru.EnterScope(map[string]interface{}{"relation-name": name}) 174 c.Assert(err, jc.ErrorIsNil) 175 s.relunits[rel.Id()] = ru 176 apiRel, err := s.uniter.Relation(rel.Tag().(names.RelationTag)) 177 c.Assert(err, jc.ErrorIsNil) 178 apiRelUnit, err := apiRel.Unit(s.apiUnit) 179 c.Assert(err, jc.ErrorIsNil) 180 s.apiRelunits[rel.Id()] = apiRelUnit 181 } 182 183 func (s *HookContextSuite) getHookContext(c *gc.C, uuid string, relid int, 184 remote string, proxies proxy.Settings) *runner.HookContext { 185 if relid != -1 { 186 _, found := s.apiRelunits[relid] 187 c.Assert(found, jc.IsTrue) 188 } 189 facade, err := s.st.Uniter() 190 c.Assert(err, jc.ErrorIsNil) 191 192 relctxs := map[int]*runner.ContextRelation{} 193 for relId, relUnit := range s.apiRelunits { 194 cache := runner.NewRelationCache(relUnit.ReadSettings, nil) 195 relctxs[relId] = runner.NewContextRelation(relUnit, cache) 196 } 197 198 context, err := runner.NewHookContext(s.apiUnit, facade, "TestCtx", uuid, 199 "test-env-name", relid, remote, relctxs, apiAddrs, names.NewUserTag("owner"), 200 proxies, false, nil, nil, s.machine.Tag().(names.MachineTag)) 201 c.Assert(err, jc.ErrorIsNil) 202 return context 203 } 204 205 func (s *HookContextSuite) getMeteredHookContext(c *gc.C, uuid string, relid int, 206 remote string, proxies proxy.Settings, canAddMetrics bool, metrics *charm.Metrics) *runner.HookContext { 207 if relid != -1 { 208 _, found := s.apiRelunits[relid] 209 c.Assert(found, jc.IsTrue) 210 } 211 facade, err := s.st.Uniter() 212 c.Assert(err, jc.ErrorIsNil) 213 214 relctxs := map[int]*runner.ContextRelation{} 215 for relId, relUnit := range s.apiRelunits { 216 cache := runner.NewRelationCache(relUnit.ReadSettings, nil) 217 relctxs[relId] = runner.NewContextRelation(relUnit, cache) 218 } 219 220 context, err := runner.NewHookContext(s.meteredApiUnit, facade, "TestCtx", uuid, 221 "test-env-name", relid, remote, relctxs, apiAddrs, names.NewUserTag("owner"), 222 proxies, canAddMetrics, metrics, nil, s.machine.Tag().(names.MachineTag)) 223 c.Assert(err, jc.ErrorIsNil) 224 return context 225 } 226 227 func (s *HookContextSuite) metricsDefinition(name string) *charm.Metrics { 228 return &charm.Metrics{Metrics: map[string]charm.Metric{name: {Type: charm.MetricTypeGauge, Description: "generated metric"}}} 229 } 230 231 // hookSpec supports makeCharm. 232 type hookSpec struct { 233 // dir is the directory to create the hook in. 234 dir string 235 // name is the name of the hook. 236 name string 237 // perm is the file permissions of the hook. 238 perm os.FileMode 239 // code is the exit status of the hook. 240 code int 241 // stdout holds a string to print to stdout 242 stdout string 243 // stderr holds a string to print to stderr 244 stderr string 245 // background holds a string to print in the background after 0.2s. 246 background string 247 } 248 249 // makeCharm constructs a fake charm dir containing a single named hook 250 // with permissions perm and exit code code. If output is non-empty, 251 // the charm will write it to stdout and stderr, with each one prefixed 252 // by name of the stream. 253 func makeCharm(c *gc.C, spec hookSpec, charmDir string) { 254 dir := charmDir 255 if spec.dir != "" { 256 dir = filepath.Join(dir, spec.dir) 257 err := os.Mkdir(dir, 0755) 258 c.Assert(err, jc.ErrorIsNil) 259 } 260 c.Logf("openfile perm %v", spec.perm) 261 hook, err := os.OpenFile( 262 filepath.Join(dir, spec.name), os.O_CREATE|os.O_WRONLY, spec.perm, 263 ) 264 c.Assert(err, jc.ErrorIsNil) 265 defer func() { 266 c.Assert(hook.Close(), gc.IsNil) 267 }() 268 269 printf := func(f string, a ...interface{}) { 270 _, err := fmt.Fprintf(hook, f+"\n", a...) 271 c.Assert(err, jc.ErrorIsNil) 272 } 273 printf("#!/bin/bash") 274 printf("echo $$ > pid") 275 if spec.stdout != "" { 276 printf("echo %s", spec.stdout) 277 } 278 if spec.stderr != "" { 279 printf("echo %s >&2", spec.stderr) 280 } 281 if spec.background != "" { 282 // Print something fairly quickly, then sleep for 283 // quite a long time - if the hook execution is 284 // blocking because of the background process, 285 // the hook execution will take much longer than 286 // expected. 287 printf("(sleep 0.2; echo %s; sleep 10) &", spec.background) 288 } 289 printf("exit %d", spec.code) 290 }