github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/worker/uniter/context_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package uniter_test 5 6 import ( 7 "fmt" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 "strings" 12 "time" 13 14 gc "launchpad.net/gocheck" 15 16 "launchpad.net/juju-core/charm" 17 "launchpad.net/juju-core/juju/osenv" 18 "launchpad.net/juju-core/juju/testing" 19 "launchpad.net/juju-core/state" 20 "launchpad.net/juju-core/state/api" 21 "launchpad.net/juju-core/state/api/params" 22 apiuniter "launchpad.net/juju-core/state/api/uniter" 23 jc "launchpad.net/juju-core/testing/checkers" 24 "launchpad.net/juju-core/utils" 25 "launchpad.net/juju-core/worker/uniter" 26 "launchpad.net/juju-core/worker/uniter/jujuc" 27 ) 28 29 var noProxies = osenv.ProxySettings{} 30 31 type RunHookSuite struct { 32 HookContextSuite 33 } 34 35 var _ = gc.Suite(&RunHookSuite{}) 36 37 type hookSpec struct { 38 // name is the name of the hook. 39 name string 40 // perm is the file permissions of the hook. 41 perm os.FileMode 42 // code is the exit status of the hook. 43 code int 44 // stdout holds a string to print to stdout 45 stdout string 46 // stderr holds a string to print to stderr 47 stderr string 48 // background holds a string to print in the background after 0.2s. 49 background string 50 } 51 52 // makeCharm constructs a fake charm dir containing a single named hook 53 // with permissions perm and exit code code. If output is non-empty, 54 // the charm will write it to stdout and stderr, with each one prefixed 55 // by name of the stream. It returns the charm directory and the path 56 // to which the hook script will write environment variables. 57 func makeCharm(c *gc.C, spec hookSpec) (charmDir, outPath string) { 58 charmDir = c.MkDir() 59 hooksDir := filepath.Join(charmDir, "hooks") 60 err := os.Mkdir(hooksDir, 0755) 61 c.Assert(err, gc.IsNil) 62 c.Logf("openfile perm %v", spec.perm) 63 hook, err := os.OpenFile(filepath.Join(hooksDir, spec.name), os.O_CREATE|os.O_WRONLY, spec.perm) 64 c.Assert(err, gc.IsNil) 65 defer hook.Close() 66 printf := func(f string, a ...interface{}) { 67 if _, err := fmt.Fprintf(hook, f+"\n", a...); err != nil { 68 panic(err) 69 } 70 } 71 outPath = filepath.Join(c.MkDir(), "hook.out") 72 printf("#!/bin/bash") 73 printf("env > " + outPath) 74 if spec.stdout != "" { 75 printf("echo %s", spec.stdout) 76 } 77 if spec.stderr != "" { 78 printf("echo %s >&2", spec.stderr) 79 } 80 if spec.background != "" { 81 // Print something fairly quickly, then sleep for 82 // quite a long time - if the hook execution is 83 // blocking because of the background process, 84 // the hook execution will take much longer than 85 // expected. 86 printf("(sleep 0.2; echo %s; sleep 10) &", spec.background) 87 } 88 printf("exit %d", spec.code) 89 return charmDir, outPath 90 } 91 92 func AssertEnvContains(c *gc.C, lines []string, env map[string]string) { 93 for k, v := range env { 94 sought := k + "=" + v 95 found := false 96 for _, line := range lines { 97 if line == sought { 98 found = true 99 continue 100 } 101 } 102 comment := gc.Commentf("expected to find %v among %v", sought, lines) 103 c.Assert(found, gc.Equals, true, comment) 104 } 105 } 106 107 func AssertEnv(c *gc.C, outPath string, charmDir string, env map[string]string, uuid string) { 108 out, err := ioutil.ReadFile(outPath) 109 c.Assert(err, gc.IsNil) 110 lines := strings.Split(string(out), "\n") 111 AssertEnvContains(c, lines, env) 112 AssertEnvContains(c, lines, map[string]string{ 113 "DEBIAN_FRONTEND": "noninteractive", 114 "APT_LISTCHANGES_FRONTEND": "none", 115 "CHARM_DIR": charmDir, 116 "JUJU_AGENT_SOCKET": "/path/to/socket", 117 "JUJU_ENV_UUID": uuid, 118 }) 119 } 120 121 // LineBufferSize matches the constant used when creating 122 // the bufio line reader. 123 const lineBufferSize = 4096 124 125 var apiAddrs = []string{"a1:123", "a2:123"} 126 var expectedApiAddrs = strings.Join(apiAddrs, " ") 127 128 var runHookTests = []struct { 129 summary string 130 relid int 131 remote string 132 spec hookSpec 133 err string 134 env map[string]string 135 proxySettings osenv.ProxySettings 136 }{ 137 { 138 summary: "missing hook is not an error", 139 relid: -1, 140 }, { 141 summary: "report failure to execute hook", 142 relid: -1, 143 spec: hookSpec{perm: 0600}, 144 err: `exec: .*something-happened": permission denied`, 145 }, { 146 summary: "report error indicated by hook's exit status", 147 relid: -1, 148 spec: hookSpec{ 149 perm: 0700, 150 code: 99, 151 }, 152 err: "exit status 99", 153 }, { 154 summary: "output logging", 155 relid: -1, 156 spec: hookSpec{ 157 perm: 0700, 158 stdout: "stdout", 159 stderr: "stderr", 160 }, 161 }, { 162 summary: "output logging with background process", 163 relid: -1, 164 spec: hookSpec{ 165 perm: 0700, 166 stdout: "stdout", 167 background: "not printed", 168 }, 169 }, { 170 summary: "long line split", 171 relid: -1, 172 spec: hookSpec{ 173 perm: 0700, 174 stdout: strings.Repeat("a", lineBufferSize+10), 175 }, 176 }, { 177 summary: "check shell environment for non-relation hook context", 178 relid: -1, 179 spec: hookSpec{perm: 0700}, 180 proxySettings: osenv.ProxySettings{ 181 Http: "http", Https: "https", Ftp: "ftp", NoProxy: "no proxy"}, 182 env: map[string]string{ 183 "JUJU_UNIT_NAME": "u/0", 184 "JUJU_API_ADDRESSES": expectedApiAddrs, 185 "JUJU_ENV_NAME": "test-env-name", 186 "http_proxy": "http", 187 "HTTP_PROXY": "http", 188 "https_proxy": "https", 189 "HTTPS_PROXY": "https", 190 "ftp_proxy": "ftp", 191 "FTP_PROXY": "ftp", 192 "no_proxy": "no proxy", 193 "NO_PROXY": "no proxy", 194 }, 195 }, { 196 summary: "check shell environment for relation-broken hook context", 197 relid: 1, 198 spec: hookSpec{perm: 0700}, 199 env: map[string]string{ 200 "JUJU_UNIT_NAME": "u/0", 201 "JUJU_API_ADDRESSES": expectedApiAddrs, 202 "JUJU_ENV_NAME": "test-env-name", 203 "JUJU_RELATION": "db", 204 "JUJU_RELATION_ID": "db:1", 205 "JUJU_REMOTE_UNIT": "", 206 }, 207 }, { 208 summary: "check shell environment for relation hook context", 209 relid: 1, 210 remote: "r/1", 211 spec: hookSpec{perm: 0700}, 212 env: map[string]string{ 213 "JUJU_UNIT_NAME": "u/0", 214 "JUJU_API_ADDRESSES": expectedApiAddrs, 215 "JUJU_ENV_NAME": "test-env-name", 216 "JUJU_RELATION": "db", 217 "JUJU_RELATION_ID": "db:1", 218 "JUJU_REMOTE_UNIT": "r/1", 219 }, 220 }, 221 } 222 223 func (s *RunHookSuite) TestRunHook(c *gc.C) { 224 uuid, err := utils.NewUUID() 225 c.Assert(err, gc.IsNil) 226 for i, t := range runHookTests { 227 c.Logf("\ntest %d: %s; perm %v", i, t.summary, t.spec.perm) 228 ctx := s.getHookContext(c, uuid.String(), t.relid, t.remote, t.proxySettings) 229 var charmDir, outPath string 230 var hookExists bool 231 if t.spec.perm == 0 { 232 charmDir = c.MkDir() 233 } else { 234 spec := t.spec 235 spec.name = "something-happened" 236 c.Logf("makeCharm %#v", spec) 237 charmDir, outPath = makeCharm(c, spec) 238 hookExists = true 239 } 240 toolsDir := c.MkDir() 241 t0 := time.Now() 242 err := ctx.RunHook("something-happened", charmDir, toolsDir, "/path/to/socket") 243 if t.err == "" && hookExists { 244 c.Assert(err, gc.IsNil) 245 } else if !hookExists { 246 c.Assert(uniter.IsMissingHookError(err), jc.IsTrue) 247 } else { 248 c.Assert(err, gc.ErrorMatches, t.err) 249 } 250 if t.env != nil { 251 env := map[string]string{"PATH": toolsDir + ":" + os.Getenv("PATH")} 252 for k, v := range t.env { 253 env[k] = v 254 } 255 AssertEnv(c, outPath, charmDir, env, uuid.String()) 256 } 257 if t.spec.background != "" && time.Now().Sub(t0) > 5*time.Second { 258 c.Errorf("background process holding up hook execution") 259 } 260 } 261 } 262 263 // split the line into buffer-sized lengths. 264 func splitLine(s string) []string { 265 var ss []string 266 for len(s) > lineBufferSize { 267 ss = append(ss, s[0:lineBufferSize]) 268 s = s[lineBufferSize:] 269 } 270 if len(s) > 0 { 271 ss = append(ss, s) 272 } 273 return ss 274 } 275 276 func (s *RunHookSuite) TestRunHookRelationFlushing(c *gc.C) { 277 // Create a charm with a breaking hook. 278 uuid, err := utils.NewUUID() 279 c.Assert(err, gc.IsNil) 280 ctx := s.getHookContext(c, uuid.String(), -1, "", noProxies) 281 charmDir, _ := makeCharm(c, hookSpec{ 282 name: "something-happened", 283 perm: 0700, 284 code: 123, 285 }) 286 287 // Mess with multiple relation settings. 288 node0, err := s.relctxs[0].Settings() 289 node0.Set("foo", "1") 290 node1, err := s.relctxs[1].Settings() 291 node1.Set("bar", "2") 292 293 // Run the failing hook. 294 err = ctx.RunHook("something-happened", charmDir, c.MkDir(), "/path/to/socket") 295 c.Assert(err, gc.ErrorMatches, "exit status 123") 296 297 // Check that the changes to the local settings nodes have been discarded. 298 node0, err = s.relctxs[0].Settings() 299 c.Assert(err, gc.IsNil) 300 c.Assert(node0.Map(), gc.DeepEquals, params.RelationSettings{"relation-name": "db0"}) 301 node1, err = s.relctxs[1].Settings() 302 c.Assert(err, gc.IsNil) 303 c.Assert(node1.Map(), gc.DeepEquals, params.RelationSettings{"relation-name": "db1"}) 304 305 // Check that the changes have been written to state. 306 settings0, err := s.relunits[0].ReadSettings("u/0") 307 c.Assert(err, gc.IsNil) 308 c.Assert(settings0, gc.DeepEquals, map[string]interface{}{"relation-name": "db0"}) 309 settings1, err := s.relunits[1].ReadSettings("u/0") 310 c.Assert(err, gc.IsNil) 311 c.Assert(settings1, gc.DeepEquals, map[string]interface{}{"relation-name": "db1"}) 312 313 // Create a charm with a working hook, and mess with settings again. 314 charmDir, _ = makeCharm(c, hookSpec{ 315 name: "something-happened", 316 perm: 0700, 317 }) 318 node0.Set("baz", "3") 319 node1.Set("qux", "4") 320 321 // Run the hook. 322 err = ctx.RunHook("something-happened", charmDir, c.MkDir(), "/path/to/socket") 323 c.Assert(err, gc.IsNil) 324 325 // Check that the changes to the local settings nodes are still there. 326 node0, err = s.relctxs[0].Settings() 327 c.Assert(err, gc.IsNil) 328 c.Assert(node0.Map(), gc.DeepEquals, params.RelationSettings{ 329 "relation-name": "db0", 330 "baz": "3", 331 }) 332 node1, err = s.relctxs[1].Settings() 333 c.Assert(err, gc.IsNil) 334 c.Assert(node1.Map(), gc.DeepEquals, params.RelationSettings{ 335 "relation-name": "db1", 336 "qux": "4", 337 }) 338 339 // Check that the changes have been written to state. 340 settings0, err = s.relunits[0].ReadSettings("u/0") 341 c.Assert(err, gc.IsNil) 342 c.Assert(settings0, gc.DeepEquals, map[string]interface{}{ 343 "relation-name": "db0", 344 "baz": "3", 345 }) 346 settings1, err = s.relunits[1].ReadSettings("u/0") 347 c.Assert(err, gc.IsNil) 348 c.Assert(settings1, gc.DeepEquals, map[string]interface{}{ 349 "relation-name": "db1", 350 "qux": "4", 351 }) 352 } 353 354 type ContextRelationSuite struct { 355 testing.JujuConnSuite 356 svc *state.Service 357 rel *state.Relation 358 ru *state.RelationUnit 359 360 st *api.State 361 uniter *apiuniter.State 362 apiRelUnit *apiuniter.RelationUnit 363 } 364 365 var _ = gc.Suite(&ContextRelationSuite{}) 366 367 func (s *ContextRelationSuite) SetUpTest(c *gc.C) { 368 s.JujuConnSuite.SetUpTest(c) 369 ch := s.AddTestingCharm(c, "riak") 370 var err error 371 s.svc = s.AddTestingService(c, "u", ch) 372 rels, err := s.svc.Relations() 373 c.Assert(err, gc.IsNil) 374 c.Assert(rels, gc.HasLen, 1) 375 s.rel = rels[0] 376 unit, err := s.svc.AddUnit() 377 c.Assert(err, gc.IsNil) 378 s.ru, err = s.rel.Unit(unit) 379 c.Assert(err, gc.IsNil) 380 err = s.ru.EnterScope(nil) 381 c.Assert(err, gc.IsNil) 382 383 password, err := utils.RandomPassword() 384 c.Assert(err, gc.IsNil) 385 err = unit.SetPassword(password) 386 c.Assert(err, gc.IsNil) 387 s.st = s.OpenAPIAs(c, unit.Tag(), password) 388 s.uniter = s.st.Uniter() 389 c.Assert(s.uniter, gc.NotNil) 390 391 apiRel, err := s.uniter.Relation(s.rel.Tag()) 392 c.Assert(err, gc.IsNil) 393 apiUnit, err := s.uniter.Unit(unit.Tag()) 394 c.Assert(err, gc.IsNil) 395 s.apiRelUnit, err = apiRel.Unit(apiUnit) 396 c.Assert(err, gc.IsNil) 397 } 398 399 func (s *ContextRelationSuite) TestChangeMembers(c *gc.C) { 400 ctx := uniter.NewContextRelation(s.apiRelUnit, nil) 401 c.Assert(ctx.UnitNames(), gc.HasLen, 0) 402 403 // Check the units and settings after a simple update. 404 ctx.UpdateMembers(uniter.SettingsMap{ 405 "u/2": {"baz": "2"}, 406 "u/4": {"qux": "4"}, 407 }) 408 c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/2", "u/4"}) 409 assertSettings := func(unit string, expect params.RelationSettings) { 410 actual, err := ctx.ReadSettings(unit) 411 c.Assert(err, gc.IsNil) 412 c.Assert(actual, gc.DeepEquals, expect) 413 } 414 assertSettings("u/2", params.RelationSettings{"baz": "2"}) 415 assertSettings("u/4", params.RelationSettings{"qux": "4"}) 416 417 // Send a second update; check that members are only added, not removed. 418 ctx.UpdateMembers(uniter.SettingsMap{ 419 "u/1": {"foo": "1"}, 420 "u/2": {"abc": "2"}, 421 "u/3": {"bar": "3"}, 422 }) 423 c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/1", "u/2", "u/3", "u/4"}) 424 425 // Check that all settings remain cached. 426 assertSettings("u/1", params.RelationSettings{"foo": "1"}) 427 assertSettings("u/2", params.RelationSettings{"abc": "2"}) 428 assertSettings("u/3", params.RelationSettings{"bar": "3"}) 429 assertSettings("u/4", params.RelationSettings{"qux": "4"}) 430 431 // Delete a member, and check that it is no longer a member... 432 ctx.DeleteMember("u/2") 433 c.Assert(ctx.UnitNames(), gc.DeepEquals, []string{"u/1", "u/3", "u/4"}) 434 435 // ...and that its settings are no longer cached. 436 _, err := ctx.ReadSettings("u/2") 437 c.Assert(err, gc.ErrorMatches, "cannot read settings for unit \"u/2\" in relation \"u:ring\": settings not found") 438 } 439 440 func (s *ContextRelationSuite) TestMemberCaching(c *gc.C) { 441 unit, err := s.svc.AddUnit() 442 c.Assert(err, gc.IsNil) 443 ru, err := s.rel.Unit(unit) 444 c.Assert(err, gc.IsNil) 445 err = ru.EnterScope(map[string]interface{}{"blib": "blob"}) 446 c.Assert(err, gc.IsNil) 447 settings, err := ru.Settings() 448 c.Assert(err, gc.IsNil) 449 settings.Set("ping", "pong") 450 _, err = settings.Write() 451 c.Assert(err, gc.IsNil) 452 ctx := uniter.NewContextRelation(s.apiRelUnit, map[string]int64{"u/1": 0}) 453 454 // Check that uncached settings are read from state. 455 m, err := ctx.ReadSettings("u/1") 456 c.Assert(err, gc.IsNil) 457 expectMap := settings.Map() 458 expectSettings := convertMap(expectMap) 459 c.Assert(m, gc.DeepEquals, expectSettings) 460 461 // Check that changes to state do not affect the cached settings. 462 settings.Set("ping", "pow") 463 _, err = settings.Write() 464 c.Assert(err, gc.IsNil) 465 m, err = ctx.ReadSettings("u/1") 466 c.Assert(err, gc.IsNil) 467 c.Assert(m, gc.DeepEquals, expectSettings) 468 469 // Check that ClearCache spares the members cache. 470 ctx.ClearCache() 471 m, err = ctx.ReadSettings("u/1") 472 c.Assert(err, gc.IsNil) 473 c.Assert(m, gc.DeepEquals, expectSettings) 474 475 // Check that updating the context overwrites the cached settings, and 476 // that the contents of state are ignored. 477 ctx.UpdateMembers(uniter.SettingsMap{"u/1": {"entirely": "different"}}) 478 m, err = ctx.ReadSettings("u/1") 479 c.Assert(err, gc.IsNil) 480 c.Assert(m, gc.DeepEquals, params.RelationSettings{"entirely": "different"}) 481 } 482 483 func (s *ContextRelationSuite) TestNonMemberCaching(c *gc.C) { 484 unit, err := s.svc.AddUnit() 485 c.Assert(err, gc.IsNil) 486 ru, err := s.rel.Unit(unit) 487 c.Assert(err, gc.IsNil) 488 err = ru.EnterScope(map[string]interface{}{"blib": "blob"}) 489 c.Assert(err, gc.IsNil) 490 settings, err := ru.Settings() 491 c.Assert(err, gc.IsNil) 492 settings.Set("ping", "pong") 493 _, err = settings.Write() 494 c.Assert(err, gc.IsNil) 495 ctx := uniter.NewContextRelation(s.apiRelUnit, nil) 496 497 // Check that settings are read from state. 498 m, err := ctx.ReadSettings("u/1") 499 c.Assert(err, gc.IsNil) 500 expectMap := settings.Map() 501 expectSettings := convertMap(expectMap) 502 c.Assert(m, gc.DeepEquals, expectSettings) 503 504 // Check that changes to state do not affect the obtained settings... 505 settings.Set("ping", "pow") 506 _, err = settings.Write() 507 c.Assert(err, gc.IsNil) 508 m, err = ctx.ReadSettings("u/1") 509 c.Assert(err, gc.IsNil) 510 c.Assert(m, gc.DeepEquals, expectSettings) 511 512 // ...until the caches are cleared. 513 ctx.ClearCache() 514 c.Assert(err, gc.IsNil) 515 m, err = ctx.ReadSettings("u/1") 516 c.Assert(err, gc.IsNil) 517 c.Assert(m["ping"], gc.Equals, "pow") 518 } 519 520 func (s *ContextRelationSuite) TestSettings(c *gc.C) { 521 ctx := uniter.NewContextRelation(s.apiRelUnit, nil) 522 523 // Change Settings, then clear cache without writing. 524 node, err := ctx.Settings() 525 c.Assert(err, gc.IsNil) 526 expectSettings := node.Map() 527 expectMap := convertSettings(expectSettings) 528 node.Set("change", "exciting") 529 ctx.ClearCache() 530 531 // Check that the change is not cached... 532 node, err = ctx.Settings() 533 c.Assert(err, gc.IsNil) 534 c.Assert(node.Map(), gc.DeepEquals, expectSettings) 535 536 // ...and not written to state. 537 settings, err := s.ru.ReadSettings("u/0") 538 c.Assert(err, gc.IsNil) 539 c.Assert(settings, gc.DeepEquals, expectMap) 540 541 // Change again, write settings, and clear caches. 542 node.Set("change", "exciting") 543 err = ctx.WriteSettings() 544 c.Assert(err, gc.IsNil) 545 ctx.ClearCache() 546 547 // Check that the change is reflected in Settings... 548 expectSettings["change"] = "exciting" 549 expectMap["change"] = expectSettings["change"] 550 node, err = ctx.Settings() 551 c.Assert(err, gc.IsNil) 552 c.Assert(node.Map(), gc.DeepEquals, expectSettings) 553 554 // ...and was written to state. 555 settings, err = s.ru.ReadSettings("u/0") 556 c.Assert(err, gc.IsNil) 557 c.Assert(settings, gc.DeepEquals, expectMap) 558 } 559 560 type InterfaceSuite struct { 561 HookContextSuite 562 } 563 564 var _ = gc.Suite(&InterfaceSuite{}) 565 566 func (s *InterfaceSuite) GetContext(c *gc.C, relId int, 567 remoteName string) jujuc.Context { 568 uuid, err := utils.NewUUID() 569 c.Assert(err, gc.IsNil) 570 return s.HookContextSuite.getHookContext(c, uuid.String(), relId, remoteName, noProxies) 571 } 572 573 func (s *InterfaceSuite) TestUtils(c *gc.C) { 574 ctx := s.GetContext(c, -1, "") 575 c.Assert(ctx.UnitName(), gc.Equals, "u/0") 576 r, found := ctx.HookRelation() 577 c.Assert(found, gc.Equals, false) 578 c.Assert(r, gc.IsNil) 579 name, found := ctx.RemoteUnitName() 580 c.Assert(found, gc.Equals, false) 581 c.Assert(name, gc.Equals, "") 582 c.Assert(ctx.RelationIds(), gc.HasLen, 2) 583 r, found = ctx.Relation(0) 584 c.Assert(found, gc.Equals, true) 585 c.Assert(r.Name(), gc.Equals, "db") 586 c.Assert(r.FakeId(), gc.Equals, "db:0") 587 r, found = ctx.Relation(123) 588 c.Assert(found, gc.Equals, false) 589 c.Assert(r, gc.IsNil) 590 591 ctx = s.GetContext(c, 1, "") 592 r, found = ctx.HookRelation() 593 c.Assert(found, gc.Equals, true) 594 c.Assert(r.Name(), gc.Equals, "db") 595 c.Assert(r.FakeId(), gc.Equals, "db:1") 596 597 ctx = s.GetContext(c, 1, "u/123") 598 name, found = ctx.RemoteUnitName() 599 c.Assert(found, gc.Equals, true) 600 c.Assert(name, gc.Equals, "u/123") 601 } 602 603 func (s *InterfaceSuite) TestUnitCaching(c *gc.C) { 604 ctx := s.GetContext(c, -1, "") 605 pr, ok := ctx.PrivateAddress() 606 c.Assert(ok, gc.Equals, true) 607 c.Assert(pr, gc.Equals, "u-0.testing.invalid") 608 _, ok = ctx.PublicAddress() 609 c.Assert(ok, gc.Equals, false) 610 611 // Change remote state. 612 u, err := s.State.Unit("u/0") 613 c.Assert(err, gc.IsNil) 614 err = u.SetPrivateAddress("") 615 c.Assert(err, gc.IsNil) 616 err = u.SetPublicAddress("blah.testing.invalid") 617 c.Assert(err, gc.IsNil) 618 619 // Local view is unchanged. 620 pr, ok = ctx.PrivateAddress() 621 c.Assert(ok, gc.Equals, true) 622 c.Assert(pr, gc.Equals, "u-0.testing.invalid") 623 _, ok = ctx.PublicAddress() 624 c.Assert(ok, gc.Equals, false) 625 } 626 627 func (s *InterfaceSuite) TestConfigCaching(c *gc.C) { 628 ctx := s.GetContext(c, -1, "") 629 settings, err := ctx.ConfigSettings() 630 c.Assert(err, gc.IsNil) 631 c.Assert(settings, gc.DeepEquals, charm.Settings{"blog-title": "My Title"}) 632 633 // Change remote config. 634 err = s.service.UpdateConfigSettings(charm.Settings{ 635 "blog-title": "Something Else", 636 }) 637 c.Assert(err, gc.IsNil) 638 639 // Local view is not changed. 640 settings, err = ctx.ConfigSettings() 641 c.Assert(err, gc.IsNil) 642 c.Assert(settings, gc.DeepEquals, charm.Settings{"blog-title": "My Title"}) 643 } 644 645 type HookContextSuite struct { 646 testing.JujuConnSuite 647 service *state.Service 648 unit *state.Unit 649 relch *state.Charm 650 relunits map[int]*state.RelationUnit 651 relctxs map[int]*uniter.ContextRelation 652 653 st *api.State 654 uniter *apiuniter.State 655 apiUnit *apiuniter.Unit 656 } 657 658 func (s *HookContextSuite) SetUpTest(c *gc.C) { 659 s.JujuConnSuite.SetUpTest(c) 660 var err error 661 sch := s.AddTestingCharm(c, "wordpress") 662 s.service = s.AddTestingService(c, "u", sch) 663 s.unit = s.AddUnit(c, s.service) 664 665 password, err := utils.RandomPassword() 666 err = s.unit.SetPassword(password) 667 c.Assert(err, gc.IsNil) 668 s.st = s.OpenAPIAs(c, s.unit.Tag(), password) 669 s.uniter = s.st.Uniter() 670 c.Assert(s.uniter, gc.NotNil) 671 672 // Note: The unit must always have a charm URL set, because this 673 // happens as part of the installation process (that happens 674 // before the initial install hook). 675 err = s.unit.SetCharmURL(sch.URL()) 676 c.Assert(err, gc.IsNil) 677 s.relch = s.AddTestingCharm(c, "mysql") 678 s.relunits = map[int]*state.RelationUnit{} 679 s.relctxs = map[int]*uniter.ContextRelation{} 680 s.AddContextRelation(c, "db0") 681 s.AddContextRelation(c, "db1") 682 } 683 684 func (s *HookContextSuite) AddUnit(c *gc.C, svc *state.Service) *state.Unit { 685 unit, err := svc.AddUnit() 686 c.Assert(err, gc.IsNil) 687 name := strings.Replace(unit.Name(), "/", "-", 1) 688 err = unit.SetPrivateAddress(name + ".testing.invalid") 689 c.Assert(err, gc.IsNil) 690 return unit 691 } 692 693 func (s *HookContextSuite) AddContextRelation(c *gc.C, name string) { 694 s.AddTestingService(c, name, s.relch) 695 eps, err := s.State.InferEndpoints([]string{"u", name}) 696 c.Assert(err, gc.IsNil) 697 rel, err := s.State.AddRelation(eps...) 698 c.Assert(err, gc.IsNil) 699 ru, err := rel.Unit(s.unit) 700 c.Assert(err, gc.IsNil) 701 s.relunits[rel.Id()] = ru 702 err = ru.EnterScope(map[string]interface{}{"relation-name": name}) 703 c.Assert(err, gc.IsNil) 704 s.apiUnit, err = s.uniter.Unit(s.unit.Tag()) 705 c.Assert(err, gc.IsNil) 706 apiRel, err := s.uniter.Relation(rel.Tag()) 707 c.Assert(err, gc.IsNil) 708 apiRelUnit, err := apiRel.Unit(s.apiUnit) 709 c.Assert(err, gc.IsNil) 710 s.relctxs[rel.Id()] = uniter.NewContextRelation(apiRelUnit, nil) 711 } 712 713 func (s *HookContextSuite) getHookContext(c *gc.C, uuid string, relid int, 714 remote string, proxies osenv.ProxySettings) *uniter.HookContext { 715 if relid != -1 { 716 _, found := s.relctxs[relid] 717 c.Assert(found, gc.Equals, true) 718 } 719 context, err := uniter.NewHookContext(s.apiUnit, "TestCtx", uuid, 720 "test-env-name", relid, remote, s.relctxs, apiAddrs, "test-owner", 721 proxies) 722 c.Assert(err, gc.IsNil) 723 return context 724 } 725 726 func convertSettings(settings params.RelationSettings) map[string]interface{} { 727 result := make(map[string]interface{}) 728 for k, v := range settings { 729 result[k] = v 730 } 731 return result 732 } 733 734 func convertMap(settingsMap map[string]interface{}) params.RelationSettings { 735 result := make(params.RelationSettings) 736 for k, v := range settingsMap { 737 result[k] = v.(string) 738 } 739 return result 740 } 741 742 type RunCommandSuite struct { 743 HookContextSuite 744 } 745 746 var _ = gc.Suite(&RunCommandSuite{}) 747 748 func (s *RunCommandSuite) getHookContext(c *gc.C) *uniter.HookContext { 749 uuid, err := utils.NewUUID() 750 c.Assert(err, gc.IsNil) 751 return s.HookContextSuite.getHookContext(c, uuid.String(), -1, "", noProxies) 752 } 753 754 func (s *RunCommandSuite) TestRunCommandsHasEnvironSet(c *gc.C) { 755 context := s.getHookContext(c) 756 charmDir := c.MkDir() 757 result, err := context.RunCommands("env | sort", charmDir, "/path/to/tools", "/path/to/socket") 758 c.Assert(err, gc.IsNil) 759 760 executionEnvironment := map[string]string{} 761 for _, value := range strings.Split(string(result.Stdout), "\n") { 762 bits := strings.SplitN(value, "=", 2) 763 if len(bits) == 2 { 764 executionEnvironment[bits[0]] = bits[1] 765 } 766 } 767 expected := map[string]string{ 768 "APT_LISTCHANGES_FRONTEND": "none", 769 "DEBIAN_FRONTEND": "noninteractive", 770 "CHARM_DIR": charmDir, 771 "JUJU_CONTEXT_ID": "TestCtx", 772 "JUJU_AGENT_SOCKET": "/path/to/socket", 773 "JUJU_UNIT_NAME": "u/0", 774 "JUJU_ENV_NAME": "test-env-name", 775 } 776 for key, value := range expected { 777 c.Check(executionEnvironment[key], gc.Equals, value) 778 } 779 } 780 781 func (s *RunCommandSuite) TestRunCommandsStdOutAndErrAndRC(c *gc.C) { 782 context := s.getHookContext(c) 783 charmDir := c.MkDir() 784 commands := ` 785 echo this is standard out 786 echo this is standard err >&2 787 exit 42 788 ` 789 result, err := context.RunCommands(commands, charmDir, "/path/to/tools", "/path/to/socket") 790 c.Assert(err, gc.IsNil) 791 792 c.Assert(result.Code, gc.Equals, 42) 793 c.Assert(string(result.Stdout), gc.Equals, "this is standard out\n") 794 c.Assert(string(result.Stderr), gc.Equals, "this is standard err\n") 795 }