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