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