github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/worker/uniter/util_test.go (about) 1 // Copyright 2012-2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package uniter_test 5 6 import ( 7 "bytes" 8 "fmt" 9 "io/ioutil" 10 "net/rpc" 11 "os" 12 "os/exec" 13 "path/filepath" 14 "reflect" 15 "strconv" 16 "strings" 17 "sync" 18 "time" 19 20 "github.com/juju/errors" 21 "github.com/juju/names" 22 gt "github.com/juju/testing" 23 jc "github.com/juju/testing/checkers" 24 ft "github.com/juju/testing/filetesting" 25 "github.com/juju/utils" 26 utilexec "github.com/juju/utils/exec" 27 "github.com/juju/utils/fslock" 28 "github.com/juju/utils/proxy" 29 gc "gopkg.in/check.v1" 30 corecharm "gopkg.in/juju/charm.v4" 31 goyaml "gopkg.in/yaml.v1" 32 33 apiuniter "github.com/juju/juju/api/uniter" 34 "github.com/juju/juju/apiserver/params" 35 "github.com/juju/juju/juju/testing" 36 "github.com/juju/juju/network" 37 "github.com/juju/juju/state" 38 "github.com/juju/juju/state/storage" 39 "github.com/juju/juju/testcharms" 40 coretesting "github.com/juju/juju/testing" 41 "github.com/juju/juju/worker" 42 "github.com/juju/juju/worker/uniter" 43 "github.com/juju/juju/worker/uniter/charm" 44 ) 45 46 // worstCase is used for timeouts when timing out 47 // will fail the test. Raising this value should 48 // not affect the overall running time of the tests 49 // unless they fail. 50 const worstCase = coretesting.LongWait 51 52 // Assign the unit to a provisioned machine with dummy addresses set. 53 func assertAssignUnit(c *gc.C, st *state.State, u *state.Unit) { 54 err := u.AssignToNewMachine() 55 c.Assert(err, jc.ErrorIsNil) 56 mid, err := u.AssignedMachineId() 57 c.Assert(err, jc.ErrorIsNil) 58 machine, err := st.Machine(mid) 59 c.Assert(err, jc.ErrorIsNil) 60 err = machine.SetProvisioned("i-exist", "fake_nonce", nil) 61 c.Assert(err, jc.ErrorIsNil) 62 err = machine.SetAddresses(network.Address{ 63 Type: network.IPv4Address, 64 Scope: network.ScopeCloudLocal, 65 Value: "private.address.example.com", 66 }, network.Address{ 67 Type: network.IPv4Address, 68 Scope: network.ScopePublic, 69 Value: "public.address.example.com", 70 }) 71 c.Assert(err, jc.ErrorIsNil) 72 } 73 74 type context struct { 75 uuid string 76 path string 77 dataDir string 78 s *UniterSuite 79 st *state.State 80 api *apiuniter.State 81 charms map[string][]byte 82 hooks []string 83 sch *state.Charm 84 svc *state.Service 85 unit *state.Unit 86 uniter *uniter.Uniter 87 relatedSvc *state.Service 88 relation *state.Relation 89 relationUnits map[string]*state.RelationUnit 90 subordinate *state.Unit 91 ticker *uniter.ManualTicker 92 93 mu sync.Mutex 94 hooksCompleted []string 95 } 96 97 var _ uniter.UniterExecutionObserver = (*context)(nil) 98 99 func (ctx *context) HookCompleted(hookName string) { 100 ctx.mu.Lock() 101 ctx.hooksCompleted = append(ctx.hooksCompleted, hookName) 102 ctx.mu.Unlock() 103 } 104 105 func (ctx *context) HookFailed(hookName string) { 106 ctx.mu.Lock() 107 ctx.hooksCompleted = append(ctx.hooksCompleted, "fail-"+hookName) 108 ctx.mu.Unlock() 109 } 110 111 func (ctx *context) run(c *gc.C, steps []stepper) { 112 defer func() { 113 if ctx.uniter != nil { 114 err := ctx.uniter.Stop() 115 c.Assert(err, jc.ErrorIsNil) 116 } 117 }() 118 for i, s := range steps { 119 c.Logf("step %d:\n", i) 120 step(c, ctx, s) 121 } 122 } 123 124 func (ctx *context) apiLogin(c *gc.C) { 125 password, err := utils.RandomPassword() 126 c.Assert(err, jc.ErrorIsNil) 127 err = ctx.unit.SetPassword(password) 128 c.Assert(err, jc.ErrorIsNil) 129 st := ctx.s.OpenAPIAs(c, ctx.unit.Tag(), password) 130 c.Assert(st, gc.NotNil) 131 c.Logf("API: login as %q successful", ctx.unit.Tag()) 132 ctx.api, err = st.Uniter() 133 c.Assert(err, jc.ErrorIsNil) 134 c.Assert(ctx.api, gc.NotNil) 135 } 136 137 var goodHook = ` 138 #!/bin/bash --norc 139 juju-log $JUJU_ENV_UUID %s $JUJU_REMOTE_UNIT 140 `[1:] 141 142 var badHook = ` 143 #!/bin/bash --norc 144 juju-log $JUJU_ENV_UUID fail-%s $JUJU_REMOTE_UNIT 145 exit 1 146 `[1:] 147 148 var rebootHook = ` 149 #!/bin/bash --norc 150 juju-reboot 151 `[1:] 152 153 var badRebootHook = ` 154 #!/bin/bash --norc 155 juju-reboot 156 exit 1 157 `[1:] 158 159 var rebootNowHook = ` 160 #!/bin/bash --norc 161 162 if [ -f "i_have_risen" ] 163 then 164 exit 0 165 fi 166 touch i_have_risen 167 juju-reboot --now 168 `[1:] 169 170 func (ctx *context) writeExplicitHook(c *gc.C, path string, contents string) { 171 err := ioutil.WriteFile(path, []byte(contents), 0755) 172 c.Assert(err, jc.ErrorIsNil) 173 } 174 175 func (ctx *context) writeHook(c *gc.C, path string, good bool) { 176 hook := badHook 177 if good { 178 hook = goodHook 179 } 180 content := fmt.Sprintf(hook, filepath.Base(path)) 181 ctx.writeExplicitHook(c, path, content) 182 } 183 184 func (ctx *context) writeActions(c *gc.C, path string, names []string) { 185 for _, name := range names { 186 ctx.writeAction(c, path, name) 187 } 188 } 189 190 func (ctx *context) writeMetricsYaml(c *gc.C, path string) { 191 metricsYamlPath := filepath.Join(path, "metrics.yaml") 192 var metricsYamlFull []byte = []byte(` 193 metrics: 194 pings: 195 type: gauge 196 description: sample metric 197 `) 198 err := ioutil.WriteFile(metricsYamlPath, []byte(metricsYamlFull), 0755) 199 c.Assert(err, jc.ErrorIsNil) 200 } 201 202 func (ctx *context) writeAction(c *gc.C, path, name string) { 203 var actions = map[string]string{ 204 "action-log": ` 205 #!/bin/bash --norc 206 juju-log $JUJU_ENV_UUID action-log 207 `[1:], 208 "snapshot": ` 209 #!/bin/bash --norc 210 action-set outfile.name="snapshot-01.tar" outfile.size="10.3GB" 211 action-set outfile.size.magnitude="10.3" outfile.size.units="GB" 212 action-set completion.status="yes" completion.time="5m" 213 action-set completion="yes" 214 `[1:], 215 "action-log-fail": ` 216 #!/bin/bash --norc 217 action-fail "I'm afraid I can't let you do that, Dave." 218 action-set foo="still works" 219 `[1:], 220 "action-log-fail-error": ` 221 #!/bin/bash --norc 222 action-fail too many arguments 223 action-set foo="still works" 224 action-fail "A real message" 225 `[1:], 226 "action-reboot": ` 227 #!/bin/bash --norc 228 juju-reboot || action-set reboot-delayed="good" 229 juju-reboot --now || action-set reboot-now="good" 230 `[1:], 231 } 232 233 actionPath := filepath.Join(path, "actions", name) 234 action := actions[name] 235 err := ioutil.WriteFile(actionPath, []byte(action), 0755) 236 c.Assert(err, jc.ErrorIsNil) 237 } 238 239 func (ctx *context) writeActionsYaml(c *gc.C, path string, names ...string) { 240 var actionsYaml = map[string]string{ 241 "base": "", 242 "snapshot": ` 243 snapshot: 244 description: Take a snapshot of the database. 245 params: 246 outfile: 247 description: "The file to write out to." 248 type: string 249 required: ["outfile"] 250 `[1:], 251 "action-log": ` 252 action-log: 253 `[1:], 254 "action-log-fail": ` 255 action-log-fail: 256 `[1:], 257 "action-log-fail-error": ` 258 action-log-fail-error: 259 `[1:], 260 "action-reboot": ` 261 action-reboot: 262 `[1:], 263 } 264 actionsYamlPath := filepath.Join(path, "actions.yaml") 265 var actionsYamlFull string 266 // Build an appropriate actions.yaml 267 if names[0] != "base" { 268 names = append([]string{"base"}, names...) 269 } 270 for _, name := range names { 271 actionsYamlFull = strings.Join( 272 []string{actionsYamlFull, actionsYaml[name]}, "\n") 273 } 274 err := ioutil.WriteFile(actionsYamlPath, []byte(actionsYamlFull), 0755) 275 c.Assert(err, jc.ErrorIsNil) 276 } 277 278 func (ctx *context) matchHooks(c *gc.C) (match bool, overshoot bool) { 279 ctx.mu.Lock() 280 defer ctx.mu.Unlock() 281 c.Logf("ctx.hooksCompleted: %#v", ctx.hooksCompleted) 282 if len(ctx.hooksCompleted) < len(ctx.hooks) { 283 return false, false 284 } 285 for i, e := range ctx.hooks { 286 if ctx.hooksCompleted[i] != e { 287 return false, false 288 } 289 } 290 return true, len(ctx.hooksCompleted) > len(ctx.hooks) 291 } 292 293 type uniterTest struct { 294 summary string 295 steps []stepper 296 } 297 298 func ut(summary string, steps ...stepper) uniterTest { 299 return uniterTest{summary, steps} 300 } 301 302 type stepper interface { 303 step(c *gc.C, ctx *context) 304 } 305 306 func step(c *gc.C, ctx *context, s stepper) { 307 c.Logf("%#v", s) 308 s.step(c, ctx) 309 } 310 311 type ensureStateWorker struct{} 312 313 func (s ensureStateWorker) step(c *gc.C, ctx *context) { 314 addresses, err := ctx.st.Addresses() 315 if err != nil || len(addresses) == 0 { 316 addStateServerMachine(c, ctx.st) 317 } 318 addresses, err = ctx.st.APIAddressesFromMachines() 319 c.Assert(err, jc.ErrorIsNil) 320 c.Assert(addresses, gc.HasLen, 1) 321 } 322 323 func addStateServerMachine(c *gc.C, st *state.State) { 324 // The AddStateServerMachine call will update the API host ports 325 // to made-up addresses. We need valid addresses so that the uniter 326 // can download charms from the API server. 327 apiHostPorts, err := st.APIHostPorts() 328 c.Assert(err, gc.IsNil) 329 testing.AddStateServerMachine(c, st) 330 err = st.SetAPIHostPorts(apiHostPorts) 331 c.Assert(err, gc.IsNil) 332 } 333 334 type createCharm struct { 335 revision int 336 badHooks []string 337 customize func(*gc.C, *context, string) 338 } 339 340 var charmHooks = []string{ 341 "install", "start", "config-changed", "upgrade-charm", "stop", 342 "db-relation-joined", "db-relation-changed", "db-relation-departed", 343 "db-relation-broken", "meter-status-changed", "collect-metrics", 344 } 345 346 func (s createCharm) step(c *gc.C, ctx *context) { 347 base := testcharms.Repo.ClonedDirPath(c.MkDir(), "wordpress") 348 for _, name := range charmHooks { 349 path := filepath.Join(base, "hooks", name) 350 good := true 351 for _, bad := range s.badHooks { 352 if name == bad { 353 good = false 354 } 355 } 356 ctx.writeHook(c, path, good) 357 } 358 if s.customize != nil { 359 s.customize(c, ctx, base) 360 } 361 dir, err := corecharm.ReadCharmDir(base) 362 c.Assert(err, jc.ErrorIsNil) 363 err = dir.SetDiskRevision(s.revision) 364 c.Assert(err, jc.ErrorIsNil) 365 step(c, ctx, addCharm{dir, curl(s.revision)}) 366 } 367 368 type addCharm struct { 369 dir *corecharm.CharmDir 370 curl *corecharm.URL 371 } 372 373 func (s addCharm) step(c *gc.C, ctx *context) { 374 var buf bytes.Buffer 375 err := s.dir.ArchiveTo(&buf) 376 c.Assert(err, jc.ErrorIsNil) 377 body := buf.Bytes() 378 hash, _, err := utils.ReadSHA256(&buf) 379 c.Assert(err, jc.ErrorIsNil) 380 381 storagePath := fmt.Sprintf("/charms/%s/%d", s.dir.Meta().Name, s.dir.Revision()) 382 ctx.charms[storagePath] = body 383 ctx.sch, err = ctx.st.AddCharm(s.dir, s.curl, storagePath, hash) 384 c.Assert(err, jc.ErrorIsNil) 385 } 386 387 type serveCharm struct{} 388 389 func (s serveCharm) step(c *gc.C, ctx *context) { 390 storage := storage.NewStorage(ctx.st.EnvironUUID(), ctx.st.MongoSession()) 391 for storagePath, data := range ctx.charms { 392 err := storage.Put(storagePath, bytes.NewReader(data), int64(len(data))) 393 c.Assert(err, jc.ErrorIsNil) 394 delete(ctx.charms, storagePath) 395 } 396 } 397 398 type createServiceAndUnit struct { 399 serviceName string 400 } 401 402 func (csau createServiceAndUnit) step(c *gc.C, ctx *context) { 403 if csau.serviceName == "" { 404 csau.serviceName = "u" 405 } 406 sch, err := ctx.st.Charm(curl(0)) 407 c.Assert(err, jc.ErrorIsNil) 408 svc := ctx.s.AddTestingService(c, csau.serviceName, sch) 409 unit, err := svc.AddUnit() 410 c.Assert(err, jc.ErrorIsNil) 411 412 // Assign the unit to a provisioned machine to match expected state. 413 assertAssignUnit(c, ctx.st, unit) 414 ctx.svc = svc 415 ctx.unit = unit 416 417 ctx.apiLogin(c) 418 } 419 420 type createUniter struct{} 421 422 func (createUniter) step(c *gc.C, ctx *context) { 423 step(c, ctx, ensureStateWorker{}) 424 step(c, ctx, createServiceAndUnit{}) 425 step(c, ctx, startUniter{}) 426 step(c, ctx, waitAddresses{}) 427 } 428 429 type waitAddresses struct{} 430 431 func (waitAddresses) step(c *gc.C, ctx *context) { 432 timeout := time.After(worstCase) 433 for { 434 select { 435 case <-timeout: 436 c.Fatalf("timed out waiting for unit addresses") 437 case <-time.After(coretesting.ShortWait): 438 err := ctx.unit.Refresh() 439 if err != nil { 440 c.Fatalf("unit refresh failed: %v", err) 441 } 442 // GZ 2013-07-10: Hardcoded values from dummy environ 443 // special cased here, questionable. 444 private, _ := ctx.unit.PrivateAddress() 445 if private != "private.address.example.com" { 446 continue 447 } 448 public, _ := ctx.unit.PublicAddress() 449 if public != "public.address.example.com" { 450 continue 451 } 452 return 453 } 454 } 455 } 456 457 type startUniter struct { 458 unitTag string 459 } 460 461 func (s startUniter) step(c *gc.C, ctx *context) { 462 if s.unitTag == "" { 463 s.unitTag = "unit-u-0" 464 } 465 if ctx.uniter != nil { 466 panic("don't start two uniters!") 467 } 468 if ctx.api == nil { 469 panic("API connection not established") 470 } 471 tag, err := names.ParseUnitTag(s.unitTag) 472 if err != nil { 473 panic(err.Error()) 474 } 475 locksDir := filepath.Join(ctx.dataDir, "locks") 476 lock, err := fslock.NewLock(locksDir, "uniter-hook-execution") 477 c.Assert(err, jc.ErrorIsNil) 478 ctx.uniter = uniter.NewUniter(ctx.api, tag, ctx.dataDir, lock) 479 uniter.SetUniterObserver(ctx.uniter, ctx) 480 } 481 482 type waitUniterDead struct { 483 err string 484 } 485 486 func (s waitUniterDead) step(c *gc.C, ctx *context) { 487 if s.err != "" { 488 err := s.waitDead(c, ctx) 489 c.Assert(err, gc.ErrorMatches, s.err) 490 return 491 } 492 // In the default case, we're waiting for worker.ErrTerminateAgent, but 493 // the path to that error can be tricky. If the unit becomes Dead at an 494 // inconvenient time, unrelated calls can fail -- as they should -- but 495 // not be detected as worker.ErrTerminateAgent. In this case, we restart 496 // the uniter and check that it fails as expected when starting up; this 497 // mimics the behaviour of the unit agent and verifies that the UA will, 498 // eventually, see the correct error and respond appropriately. 499 err := s.waitDead(c, ctx) 500 if err != worker.ErrTerminateAgent { 501 step(c, ctx, startUniter{}) 502 err = s.waitDead(c, ctx) 503 } 504 c.Assert(err, gc.Equals, worker.ErrTerminateAgent) 505 err = ctx.unit.Refresh() 506 c.Assert(err, jc.ErrorIsNil) 507 c.Assert(ctx.unit.Life(), gc.Equals, state.Dead) 508 } 509 510 func (s waitUniterDead) waitDead(c *gc.C, ctx *context) error { 511 u := ctx.uniter 512 ctx.uniter = nil 513 timeout := time.After(worstCase) 514 for { 515 // The repeated StartSync is to ensure timely completion of this method 516 // in the case(s) where a state change causes a uniter action which 517 // causes a state change which causes a uniter action, in which case we 518 // need more than one sync. At the moment there's only one situation 519 // that causes this -- setting the unit's service to Dying -- but it's 520 // not an intrinsically insane pattern of action (and helps to simplify 521 // the filter code) so this test seems like a small price to pay. 522 ctx.s.BackingState.StartSync() 523 select { 524 case <-u.Dead(): 525 return u.Wait() 526 case <-time.After(coretesting.ShortWait): 527 continue 528 case <-timeout: 529 c.Fatalf("uniter still alive") 530 } 531 } 532 } 533 534 type stopUniter struct { 535 err string 536 } 537 538 func (s stopUniter) step(c *gc.C, ctx *context) { 539 u := ctx.uniter 540 ctx.uniter = nil 541 err := u.Stop() 542 if s.err == "" { 543 c.Assert(err, jc.ErrorIsNil) 544 } else { 545 c.Assert(err, gc.ErrorMatches, s.err) 546 } 547 } 548 549 type verifyWaiting struct{} 550 551 func (s verifyWaiting) step(c *gc.C, ctx *context) { 552 step(c, ctx, stopUniter{}) 553 step(c, ctx, startUniter{}) 554 step(c, ctx, waitHooks{}) 555 } 556 557 type verifyRunning struct{} 558 559 func (s verifyRunning) step(c *gc.C, ctx *context) { 560 step(c, ctx, stopUniter{}) 561 step(c, ctx, startUniter{}) 562 step(c, ctx, waitHooks{"config-changed"}) 563 } 564 565 type startupErrorWithCustomCharm struct { 566 badHook string 567 customize func(*gc.C, *context, string) 568 } 569 570 func (s startupErrorWithCustomCharm) step(c *gc.C, ctx *context) { 571 step(c, ctx, createCharm{ 572 badHooks: []string{s.badHook}, 573 customize: s.customize, 574 }) 575 step(c, ctx, serveCharm{}) 576 step(c, ctx, createUniter{}) 577 step(c, ctx, waitUnit{ 578 status: params.StatusError, 579 info: fmt.Sprintf(`hook failed: %q`, s.badHook), 580 }) 581 for _, hook := range []string{"install", "config-changed", "start"} { 582 if hook == s.badHook { 583 step(c, ctx, waitHooks{"fail-" + hook}) 584 break 585 } 586 step(c, ctx, waitHooks{hook}) 587 } 588 step(c, ctx, verifyCharm{}) 589 } 590 591 type startupError struct { 592 badHook string 593 } 594 595 func (s startupError) step(c *gc.C, ctx *context) { 596 step(c, ctx, createCharm{badHooks: []string{s.badHook}}) 597 step(c, ctx, serveCharm{}) 598 step(c, ctx, createUniter{}) 599 step(c, ctx, waitUnit{ 600 status: params.StatusError, 601 info: fmt.Sprintf(`hook failed: %q`, s.badHook), 602 }) 603 for _, hook := range []string{"install", "config-changed", "start"} { 604 if hook == s.badHook { 605 step(c, ctx, waitHooks{"fail-" + hook}) 606 break 607 } 608 step(c, ctx, waitHooks{hook}) 609 } 610 step(c, ctx, verifyCharm{}) 611 } 612 613 type quickStart struct{} 614 615 func (s quickStart) step(c *gc.C, ctx *context) { 616 step(c, ctx, createCharm{}) 617 step(c, ctx, serveCharm{}) 618 step(c, ctx, createUniter{}) 619 step(c, ctx, waitUnit{status: params.StatusActive}) 620 step(c, ctx, waitHooks{"install", "config-changed", "start"}) 621 step(c, ctx, verifyCharm{}) 622 } 623 624 type quickStartRelation struct{} 625 626 func (s quickStartRelation) step(c *gc.C, ctx *context) { 627 step(c, ctx, quickStart{}) 628 step(c, ctx, addRelation{}) 629 step(c, ctx, addRelationUnit{}) 630 step(c, ctx, waitHooks{"db-relation-joined mysql/0 db:0", "db-relation-changed mysql/0 db:0"}) 631 step(c, ctx, verifyRunning{}) 632 } 633 634 type startupRelationError struct { 635 badHook string 636 } 637 638 func (s startupRelationError) step(c *gc.C, ctx *context) { 639 step(c, ctx, createCharm{badHooks: []string{s.badHook}}) 640 step(c, ctx, serveCharm{}) 641 step(c, ctx, createUniter{}) 642 step(c, ctx, waitUnit{status: params.StatusActive}) 643 step(c, ctx, waitHooks{"install", "config-changed", "start"}) 644 step(c, ctx, verifyCharm{}) 645 step(c, ctx, addRelation{}) 646 step(c, ctx, addRelationUnit{}) 647 } 648 649 type resolveError struct { 650 resolved state.ResolvedMode 651 } 652 653 func (s resolveError) step(c *gc.C, ctx *context) { 654 err := ctx.unit.SetResolved(s.resolved) 655 c.Assert(err, jc.ErrorIsNil) 656 } 657 658 type waitUnit struct { 659 status params.Status 660 info string 661 data map[string]interface{} 662 charm int 663 resolved state.ResolvedMode 664 } 665 666 func (s waitUnit) step(c *gc.C, ctx *context) { 667 timeout := time.After(worstCase) 668 for { 669 ctx.s.BackingState.StartSync() 670 select { 671 case <-time.After(coretesting.ShortWait): 672 err := ctx.unit.Refresh() 673 if err != nil { 674 c.Fatalf("cannot refresh unit: %v", err) 675 } 676 resolved := ctx.unit.Resolved() 677 if resolved != s.resolved { 678 c.Logf("want resolved mode %q, got %q; still waiting", s.resolved, resolved) 679 continue 680 } 681 url, ok := ctx.unit.CharmURL() 682 if !ok || *url != *curl(s.charm) { 683 var got string 684 if ok { 685 got = url.String() 686 } 687 c.Logf("want unit charm %q, got %q; still waiting", curl(s.charm), got) 688 continue 689 } 690 status, info, data, err := ctx.unit.Status() 691 c.Assert(err, jc.ErrorIsNil) 692 if string(status) != string(s.status) { 693 c.Logf("want unit status %q, got %q; still waiting", s.status, status) 694 continue 695 } 696 if info != s.info { 697 c.Logf("want unit status info %q, got %q; still waiting", s.info, info) 698 continue 699 } 700 if s.data != nil { 701 if len(data) != len(s.data) { 702 c.Logf("want %d unit status data value(s), got %d; still waiting", len(s.data), len(data)) 703 continue 704 } 705 for key, value := range s.data { 706 if data[key] != value { 707 c.Logf("want unit status data value %q for key %q, got %q; still waiting", 708 value, key, data[key]) 709 continue 710 } 711 } 712 } 713 return 714 case <-timeout: 715 c.Fatalf("never reached desired status") 716 } 717 } 718 } 719 720 type waitHooks []string 721 722 func (s waitHooks) step(c *gc.C, ctx *context) { 723 if len(s) == 0 { 724 // Give unwanted hooks a moment to run... 725 ctx.s.BackingState.StartSync() 726 time.Sleep(coretesting.ShortWait) 727 } 728 ctx.hooks = append(ctx.hooks, s...) 729 c.Logf("waiting for hooks: %#v", ctx.hooks) 730 match, overshoot := ctx.matchHooks(c) 731 if overshoot && len(s) == 0 { 732 c.Fatalf("ran more hooks than expected") 733 } 734 if match { 735 return 736 } 737 timeout := time.After(worstCase) 738 for { 739 ctx.s.BackingState.StartSync() 740 select { 741 case <-time.After(coretesting.ShortWait): 742 if match, _ = ctx.matchHooks(c); match { 743 return 744 } 745 case <-timeout: 746 c.Fatalf("never got expected hooks") 747 } 748 } 749 } 750 751 type actionResult struct { 752 name string 753 results map[string]interface{} 754 status string 755 message string 756 } 757 758 type waitActionResults struct { 759 expectedResults []actionResult 760 } 761 762 func (s waitActionResults) step(c *gc.C, ctx *context) { 763 resultsWatcher := ctx.st.WatchActionResults() 764 defer func() { 765 c.Assert(resultsWatcher.Stop(), gc.IsNil) 766 }() 767 timeout := time.After(worstCase) 768 for { 769 ctx.s.BackingState.StartSync() 770 select { 771 case <-time.After(coretesting.ShortWait): 772 continue 773 case <-timeout: 774 c.Fatalf("timed out waiting for action results") 775 case changes, ok := <-resultsWatcher.Changes(): 776 c.Logf("Got changes: %#v", changes) 777 c.Assert(ok, jc.IsTrue) 778 stateActionResults, err := ctx.unit.CompletedActions() 779 c.Assert(err, jc.ErrorIsNil) 780 if len(stateActionResults) != len(s.expectedResults) { 781 continue 782 } 783 actualResults := make([]actionResult, len(stateActionResults)) 784 for i, result := range stateActionResults { 785 results, message := result.Results() 786 actualResults[i] = actionResult{ 787 name: result.Name(), 788 results: results, 789 status: string(result.Status()), 790 message: message, 791 } 792 } 793 assertActionResultsMatch(c, actualResults, s.expectedResults) 794 return 795 } 796 } 797 } 798 799 func assertActionResultsMatch(c *gc.C, actualIn []actionResult, expectIn []actionResult) { 800 matches := 0 801 desiredMatches := len(actualIn) 802 c.Assert(len(actualIn), gc.Equals, len(expectIn)) 803 findMatch: 804 for _, expectedItem := range expectIn { 805 // find expectedItem in actualIn 806 for j, actualItem := range actualIn { 807 // If we find a match, remove both items from their 808 // respective slices, increment match count, and restart. 809 if reflect.DeepEqual(actualItem, expectedItem) { 810 actualIn = append(actualIn[:j], actualIn[j+1:]...) 811 matches++ 812 continue findMatch 813 } 814 } 815 // if we finish the whole thing without finding a match, we failed. 816 c.Assert(actualIn, jc.DeepEquals, expectIn) 817 } 818 819 c.Assert(matches, gc.Equals, desiredMatches) 820 } 821 822 type verifyNoActionResults struct{} 823 824 func (s verifyNoActionResults) step(c *gc.C, ctx *context) { 825 time.Sleep(coretesting.ShortWait) 826 result, err := ctx.unit.CompletedActions() 827 c.Assert(err, jc.ErrorIsNil) 828 c.Assert(result, gc.HasLen, 0) 829 } 830 831 type fixHook struct { 832 name string 833 } 834 835 func (s fixHook) step(c *gc.C, ctx *context) { 836 path := filepath.Join(ctx.path, "charm", "hooks", s.name) 837 ctx.writeHook(c, path, true) 838 } 839 840 type changeMeterStatus struct { 841 code string 842 info string 843 } 844 845 func (s changeMeterStatus) step(c *gc.C, ctx *context) { 846 err := ctx.unit.SetMeterStatus(s.code, s.info) 847 c.Assert(err, jc.ErrorIsNil) 848 } 849 850 type metricsTick struct{} 851 852 func (s metricsTick) step(c *gc.C, ctx *context) { 853 err := ctx.ticker.Tick() 854 c.Assert(err, jc.ErrorIsNil) 855 } 856 857 type changeConfig map[string]interface{} 858 859 func (s changeConfig) step(c *gc.C, ctx *context) { 860 err := ctx.svc.UpdateConfigSettings(corecharm.Settings(s)) 861 c.Assert(err, jc.ErrorIsNil) 862 } 863 864 type addAction struct { 865 name string 866 params map[string]interface{} 867 } 868 869 func (s addAction) step(c *gc.C, ctx *context) { 870 _, err := ctx.st.EnqueueAction(ctx.unit.Tag(), s.name, s.params) 871 // _, err := ctx.unit.AddAction(s.name, s.params) 872 c.Assert(err, jc.ErrorIsNil) 873 } 874 875 type upgradeCharm struct { 876 revision int 877 forced bool 878 } 879 880 func (s upgradeCharm) step(c *gc.C, ctx *context) { 881 curl := curl(s.revision) 882 sch, err := ctx.st.Charm(curl) 883 c.Assert(err, jc.ErrorIsNil) 884 err = ctx.svc.SetCharm(sch, s.forced) 885 c.Assert(err, jc.ErrorIsNil) 886 serveCharm{}.step(c, ctx) 887 } 888 889 type verifyCharm struct { 890 revision int 891 attemptedRevision int 892 checkFiles ft.Entries 893 } 894 895 func (s verifyCharm) step(c *gc.C, ctx *context) { 896 s.checkFiles.Check(c, filepath.Join(ctx.path, "charm")) 897 path := filepath.Join(ctx.path, "charm", "revision") 898 content, err := ioutil.ReadFile(path) 899 c.Assert(err, jc.ErrorIsNil) 900 c.Assert(string(content), gc.Equals, strconv.Itoa(s.revision)) 901 checkRevision := s.revision 902 if s.attemptedRevision > checkRevision { 903 checkRevision = s.attemptedRevision 904 } 905 err = ctx.unit.Refresh() 906 c.Assert(err, jc.ErrorIsNil) 907 url, ok := ctx.unit.CharmURL() 908 c.Assert(ok, jc.IsTrue) 909 c.Assert(url, gc.DeepEquals, curl(checkRevision)) 910 } 911 912 type startUpgradeError struct{} 913 914 func (s startUpgradeError) step(c *gc.C, ctx *context) { 915 steps := []stepper{ 916 createCharm{ 917 customize: func(c *gc.C, ctx *context, path string) { 918 appendHook(c, path, "start", "chmod 555 $CHARM_DIR") 919 }, 920 }, 921 serveCharm{}, 922 createUniter{}, 923 waitUnit{ 924 status: params.StatusActive, 925 }, 926 waitHooks{"install", "config-changed", "start"}, 927 verifyCharm{}, 928 929 createCharm{revision: 1}, 930 serveCharm{}, 931 upgradeCharm{revision: 1}, 932 waitUnit{ 933 status: params.StatusError, 934 info: "upgrade failed", 935 charm: 1, 936 }, 937 verifyWaiting{}, 938 verifyCharm{attemptedRevision: 1}, 939 } 940 for _, s_ := range steps { 941 step(c, ctx, s_) 942 } 943 } 944 945 type verifyWaitingUpgradeError struct { 946 revision int 947 } 948 949 func (s verifyWaitingUpgradeError) step(c *gc.C, ctx *context) { 950 verifyCharmSteps := []stepper{ 951 waitUnit{ 952 status: params.StatusError, 953 info: "upgrade failed", 954 charm: s.revision, 955 }, 956 verifyCharm{attemptedRevision: s.revision}, 957 } 958 verifyWaitingSteps := []stepper{ 959 stopUniter{}, 960 custom{func(c *gc.C, ctx *context) { 961 // By setting status to Started, and waiting for the restarted uniter 962 // to reset the error status, we can avoid a race in which a subsequent 963 // fixUpgradeError lands just before the restarting uniter retries the 964 // upgrade; and thus puts us in an unexpected state for future steps. 965 ctx.unit.SetStatus(state.StatusActive, "", nil) 966 }}, 967 startUniter{}, 968 } 969 allSteps := append(verifyCharmSteps, verifyWaitingSteps...) 970 allSteps = append(allSteps, verifyCharmSteps...) 971 for _, s_ := range allSteps { 972 step(c, ctx, s_) 973 } 974 } 975 976 type fixUpgradeError struct{} 977 978 func (s fixUpgradeError) step(c *gc.C, ctx *context) { 979 charmPath := filepath.Join(ctx.path, "charm") 980 err := os.Chmod(charmPath, 0755) 981 c.Assert(err, jc.ErrorIsNil) 982 } 983 984 type addRelation struct { 985 waitJoin bool 986 } 987 988 func (s addRelation) step(c *gc.C, ctx *context) { 989 if ctx.relation != nil { 990 panic("don't add two relations!") 991 } 992 if ctx.relatedSvc == nil { 993 ctx.relatedSvc = ctx.s.AddTestingService(c, "mysql", ctx.s.AddTestingCharm(c, "mysql")) 994 } 995 eps, err := ctx.st.InferEndpoints("u", "mysql") 996 c.Assert(err, jc.ErrorIsNil) 997 ctx.relation, err = ctx.st.AddRelation(eps...) 998 c.Assert(err, jc.ErrorIsNil) 999 ctx.relationUnits = map[string]*state.RelationUnit{} 1000 if !s.waitJoin { 1001 return 1002 } 1003 1004 // It's hard to do this properly (watching scope) without perturbing other tests. 1005 ru, err := ctx.relation.Unit(ctx.unit) 1006 c.Assert(err, jc.ErrorIsNil) 1007 timeout := time.After(worstCase) 1008 for { 1009 c.Logf("waiting to join relation") 1010 select { 1011 case <-timeout: 1012 c.Fatalf("failed to join relation") 1013 case <-time.After(coretesting.ShortWait): 1014 inScope, err := ru.InScope() 1015 c.Assert(err, jc.ErrorIsNil) 1016 if inScope { 1017 return 1018 } 1019 } 1020 } 1021 } 1022 1023 type addRelationUnit struct{} 1024 1025 func (s addRelationUnit) step(c *gc.C, ctx *context) { 1026 u, err := ctx.relatedSvc.AddUnit() 1027 c.Assert(err, jc.ErrorIsNil) 1028 ru, err := ctx.relation.Unit(u) 1029 c.Assert(err, jc.ErrorIsNil) 1030 err = ru.EnterScope(nil) 1031 c.Assert(err, jc.ErrorIsNil) 1032 ctx.relationUnits[u.Name()] = ru 1033 } 1034 1035 type changeRelationUnit struct { 1036 name string 1037 } 1038 1039 func (s changeRelationUnit) step(c *gc.C, ctx *context) { 1040 settings, err := ctx.relationUnits[s.name].Settings() 1041 c.Assert(err, jc.ErrorIsNil) 1042 key := "madness?" 1043 raw, _ := settings.Get(key) 1044 val, _ := raw.(string) 1045 if val == "" { 1046 val = "this is juju" 1047 } else { 1048 val += "u" 1049 } 1050 settings.Set(key, val) 1051 _, err = settings.Write() 1052 c.Assert(err, jc.ErrorIsNil) 1053 } 1054 1055 type removeRelationUnit struct { 1056 name string 1057 } 1058 1059 func (s removeRelationUnit) step(c *gc.C, ctx *context) { 1060 err := ctx.relationUnits[s.name].LeaveScope() 1061 c.Assert(err, jc.ErrorIsNil) 1062 ctx.relationUnits[s.name] = nil 1063 } 1064 1065 type relationState struct { 1066 removed bool 1067 life state.Life 1068 } 1069 1070 func (s relationState) step(c *gc.C, ctx *context) { 1071 err := ctx.relation.Refresh() 1072 if s.removed { 1073 c.Assert(err, jc.Satisfies, errors.IsNotFound) 1074 return 1075 } 1076 c.Assert(err, jc.ErrorIsNil) 1077 c.Assert(ctx.relation.Life(), gc.Equals, s.life) 1078 1079 } 1080 1081 type addSubordinateRelation struct { 1082 ifce string 1083 } 1084 1085 func (s addSubordinateRelation) step(c *gc.C, ctx *context) { 1086 if _, err := ctx.st.Service("logging"); errors.IsNotFound(err) { 1087 ctx.s.AddTestingService(c, "logging", ctx.s.AddTestingCharm(c, "logging")) 1088 } 1089 eps, err := ctx.st.InferEndpoints("logging", "u:"+s.ifce) 1090 c.Assert(err, jc.ErrorIsNil) 1091 _, err = ctx.st.AddRelation(eps...) 1092 c.Assert(err, jc.ErrorIsNil) 1093 } 1094 1095 type removeSubordinateRelation struct { 1096 ifce string 1097 } 1098 1099 func (s removeSubordinateRelation) step(c *gc.C, ctx *context) { 1100 eps, err := ctx.st.InferEndpoints("logging", "u:"+s.ifce) 1101 c.Assert(err, jc.ErrorIsNil) 1102 rel, err := ctx.st.EndpointsRelation(eps...) 1103 c.Assert(err, jc.ErrorIsNil) 1104 err = rel.Destroy() 1105 c.Assert(err, jc.ErrorIsNil) 1106 } 1107 1108 type waitSubordinateExists struct { 1109 name string 1110 } 1111 1112 func (s waitSubordinateExists) step(c *gc.C, ctx *context) { 1113 timeout := time.After(worstCase) 1114 for { 1115 ctx.s.BackingState.StartSync() 1116 select { 1117 case <-timeout: 1118 c.Fatalf("subordinate was not created") 1119 case <-time.After(coretesting.ShortWait): 1120 var err error 1121 ctx.subordinate, err = ctx.st.Unit(s.name) 1122 if errors.IsNotFound(err) { 1123 continue 1124 } 1125 c.Assert(err, jc.ErrorIsNil) 1126 return 1127 } 1128 } 1129 } 1130 1131 type waitSubordinateDying struct{} 1132 1133 func (waitSubordinateDying) step(c *gc.C, ctx *context) { 1134 timeout := time.After(worstCase) 1135 for { 1136 ctx.s.BackingState.StartSync() 1137 select { 1138 case <-timeout: 1139 c.Fatalf("subordinate was not made Dying") 1140 case <-time.After(coretesting.ShortWait): 1141 err := ctx.subordinate.Refresh() 1142 c.Assert(err, jc.ErrorIsNil) 1143 if ctx.subordinate.Life() != state.Dying { 1144 continue 1145 } 1146 } 1147 break 1148 } 1149 } 1150 1151 type removeSubordinate struct{} 1152 1153 func (removeSubordinate) step(c *gc.C, ctx *context) { 1154 err := ctx.subordinate.EnsureDead() 1155 c.Assert(err, jc.ErrorIsNil) 1156 err = ctx.subordinate.Remove() 1157 c.Assert(err, jc.ErrorIsNil) 1158 ctx.subordinate = nil 1159 } 1160 1161 type assertYaml struct { 1162 path string 1163 expect map[string]interface{} 1164 } 1165 1166 func (s assertYaml) step(c *gc.C, ctx *context) { 1167 data, err := ioutil.ReadFile(filepath.Join(ctx.path, s.path)) 1168 c.Assert(err, jc.ErrorIsNil) 1169 actual := make(map[string]interface{}) 1170 err = goyaml.Unmarshal(data, &actual) 1171 c.Assert(err, jc.ErrorIsNil) 1172 c.Assert(actual, gc.DeepEquals, s.expect) 1173 } 1174 1175 type writeFile struct { 1176 path string 1177 mode os.FileMode 1178 } 1179 1180 func (s writeFile) step(c *gc.C, ctx *context) { 1181 path := filepath.Join(ctx.path, s.path) 1182 dir := filepath.Dir(path) 1183 err := os.MkdirAll(dir, 0755) 1184 c.Assert(err, jc.ErrorIsNil) 1185 err = ioutil.WriteFile(path, nil, s.mode) 1186 c.Assert(err, jc.ErrorIsNil) 1187 } 1188 1189 type chmod struct { 1190 path string 1191 mode os.FileMode 1192 } 1193 1194 func (s chmod) step(c *gc.C, ctx *context) { 1195 path := filepath.Join(ctx.path, s.path) 1196 err := os.Chmod(path, s.mode) 1197 c.Assert(err, jc.ErrorIsNil) 1198 } 1199 1200 type custom struct { 1201 f func(*gc.C, *context) 1202 } 1203 1204 func (s custom) step(c *gc.C, ctx *context) { 1205 s.f(c, ctx) 1206 } 1207 1208 var serviceDying = custom{func(c *gc.C, ctx *context) { 1209 c.Assert(ctx.svc.Destroy(), gc.IsNil) 1210 }} 1211 1212 var relationDying = custom{func(c *gc.C, ctx *context) { 1213 c.Assert(ctx.relation.Destroy(), gc.IsNil) 1214 }} 1215 1216 var unitDying = custom{func(c *gc.C, ctx *context) { 1217 c.Assert(ctx.unit.Destroy(), gc.IsNil) 1218 }} 1219 1220 var unitDead = custom{func(c *gc.C, ctx *context) { 1221 c.Assert(ctx.unit.EnsureDead(), gc.IsNil) 1222 }} 1223 1224 var subordinateDying = custom{func(c *gc.C, ctx *context) { 1225 c.Assert(ctx.subordinate.Destroy(), gc.IsNil) 1226 }} 1227 1228 func curl(revision int) *corecharm.URL { 1229 return corecharm.MustParseURL("cs:quantal/wordpress").WithRevision(revision) 1230 } 1231 1232 func appendHook(c *gc.C, charm, name, data string) { 1233 path := filepath.Join(charm, "hooks", name) 1234 f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND, 0755) 1235 c.Assert(err, jc.ErrorIsNil) 1236 defer f.Close() 1237 _, err = f.Write([]byte(data)) 1238 c.Assert(err, jc.ErrorIsNil) 1239 } 1240 1241 func renameRelation(c *gc.C, charmPath, oldName, newName string) { 1242 path := filepath.Join(charmPath, "metadata.yaml") 1243 f, err := os.Open(path) 1244 c.Assert(err, jc.ErrorIsNil) 1245 defer f.Close() 1246 meta, err := corecharm.ReadMeta(f) 1247 c.Assert(err, jc.ErrorIsNil) 1248 1249 replace := func(what map[string]corecharm.Relation) bool { 1250 for relName, relation := range what { 1251 if relName == oldName { 1252 what[newName] = relation 1253 delete(what, oldName) 1254 return true 1255 } 1256 } 1257 return false 1258 } 1259 replaced := replace(meta.Provides) || replace(meta.Requires) || replace(meta.Peers) 1260 c.Assert(replaced, gc.Equals, true, gc.Commentf("charm %q does not implement relation %q", charmPath, oldName)) 1261 1262 newmeta, err := goyaml.Marshal(meta) 1263 c.Assert(err, jc.ErrorIsNil) 1264 ioutil.WriteFile(path, newmeta, 0644) 1265 1266 f, err = os.Open(path) 1267 c.Assert(err, jc.ErrorIsNil) 1268 defer f.Close() 1269 meta, err = corecharm.ReadMeta(f) 1270 c.Assert(err, jc.ErrorIsNil) 1271 } 1272 1273 func createHookLock(c *gc.C, dataDir string) *fslock.Lock { 1274 lockDir := filepath.Join(dataDir, "locks") 1275 lock, err := fslock.NewLock(lockDir, "uniter-hook-execution") 1276 c.Assert(err, jc.ErrorIsNil) 1277 return lock 1278 } 1279 1280 type acquireHookSyncLock struct { 1281 message string 1282 } 1283 1284 func (s acquireHookSyncLock) step(c *gc.C, ctx *context) { 1285 lock := createHookLock(c, ctx.dataDir) 1286 c.Assert(lock.IsLocked(), jc.IsFalse) 1287 err := lock.Lock(s.message) 1288 c.Assert(err, jc.ErrorIsNil) 1289 } 1290 1291 var releaseHookSyncLock = custom{func(c *gc.C, ctx *context) { 1292 lock := createHookLock(c, ctx.dataDir) 1293 // Force the release. 1294 err := lock.BreakLock() 1295 c.Assert(err, jc.ErrorIsNil) 1296 }} 1297 1298 var verifyHookSyncLockUnlocked = custom{func(c *gc.C, ctx *context) { 1299 lock := createHookLock(c, ctx.dataDir) 1300 c.Assert(lock.IsLocked(), jc.IsFalse) 1301 }} 1302 1303 var verifyHookSyncLockLocked = custom{func(c *gc.C, ctx *context) { 1304 lock := createHookLock(c, ctx.dataDir) 1305 c.Assert(lock.IsLocked(), jc.IsTrue) 1306 }} 1307 1308 type setProxySettings proxy.Settings 1309 1310 func (s setProxySettings) step(c *gc.C, ctx *context) { 1311 attrs := map[string]interface{}{ 1312 "http-proxy": s.Http, 1313 "https-proxy": s.Https, 1314 "ftp-proxy": s.Ftp, 1315 "no-proxy": s.NoProxy, 1316 } 1317 err := ctx.st.UpdateEnvironConfig(attrs, nil, nil) 1318 c.Assert(err, jc.ErrorIsNil) 1319 } 1320 1321 type relationRunCommands []string 1322 1323 func (cmds relationRunCommands) step(c *gc.C, ctx *context) { 1324 commands := strings.Join(cmds, "\n") 1325 args := uniter.RunCommandsArgs{ 1326 Commands: commands, 1327 RelationId: 0, 1328 RemoteUnitName: "", 1329 } 1330 result, err := ctx.uniter.RunCommands(args) 1331 c.Assert(err, jc.ErrorIsNil) 1332 c.Check(result.Code, gc.Equals, 0) 1333 c.Check(string(result.Stdout), gc.Equals, "") 1334 c.Check(string(result.Stderr), gc.Equals, "") 1335 } 1336 1337 type runCommands []string 1338 1339 func (cmds runCommands) step(c *gc.C, ctx *context) { 1340 commands := strings.Join(cmds, "\n") 1341 args := uniter.RunCommandsArgs{ 1342 Commands: commands, 1343 RelationId: -1, 1344 RemoteUnitName: "", 1345 } 1346 result, err := ctx.uniter.RunCommands(args) 1347 c.Assert(err, jc.ErrorIsNil) 1348 c.Check(result.Code, gc.Equals, 0) 1349 c.Check(string(result.Stdout), gc.Equals, "") 1350 c.Check(string(result.Stderr), gc.Equals, "") 1351 } 1352 1353 type asyncRunCommands []string 1354 1355 func (cmds asyncRunCommands) step(c *gc.C, ctx *context) { 1356 commands := strings.Join(cmds, "\n") 1357 args := uniter.RunCommandsArgs{ 1358 Commands: commands, 1359 RelationId: -1, 1360 RemoteUnitName: "", 1361 } 1362 1363 socketPath := filepath.Join(ctx.path, "run.socket") 1364 1365 go func() { 1366 // make sure the socket exists 1367 client, err := rpc.Dial("unix", socketPath) 1368 c.Assert(err, jc.ErrorIsNil) 1369 defer client.Close() 1370 1371 var result utilexec.ExecResponse 1372 err = client.Call(uniter.JujuRunEndpoint, args, &result) 1373 c.Assert(err, jc.ErrorIsNil) 1374 c.Check(result.Code, gc.Equals, 0) 1375 c.Check(string(result.Stdout), gc.Equals, "") 1376 c.Check(string(result.Stderr), gc.Equals, "") 1377 }() 1378 } 1379 1380 type verifyFile struct { 1381 filename string 1382 content string 1383 } 1384 1385 func (verify verifyFile) fileExists() bool { 1386 _, err := os.Stat(verify.filename) 1387 return err == nil 1388 } 1389 1390 func (verify verifyFile) checkContent(c *gc.C) { 1391 content, err := ioutil.ReadFile(verify.filename) 1392 c.Assert(err, jc.ErrorIsNil) 1393 c.Assert(string(content), gc.Equals, verify.content) 1394 } 1395 1396 func (verify verifyFile) step(c *gc.C, ctx *context) { 1397 if verify.fileExists() { 1398 verify.checkContent(c) 1399 return 1400 } 1401 c.Logf("waiting for file: %s", verify.filename) 1402 timeout := time.After(worstCase) 1403 for { 1404 select { 1405 case <-time.After(coretesting.ShortWait): 1406 if verify.fileExists() { 1407 verify.checkContent(c) 1408 return 1409 } 1410 case <-timeout: 1411 c.Fatalf("file does not exist") 1412 } 1413 } 1414 } 1415 1416 // verify that the file does not exist 1417 type verifyNoFile struct { 1418 filename string 1419 } 1420 1421 func (verify verifyNoFile) step(c *gc.C, ctx *context) { 1422 c.Assert(verify.filename, jc.DoesNotExist) 1423 // Wait a short time and check again. 1424 time.Sleep(coretesting.ShortWait) 1425 c.Assert(verify.filename, jc.DoesNotExist) 1426 } 1427 1428 // prepareGitUniter runs a sequence of uniter tests with the manifest deployer 1429 // replacement logic patched out, simulating the effect of running an older 1430 // version of juju that exclusively used a git deployer. This is useful both 1431 // for testing the new deployer-replacement code *and* for running the old 1432 // tests against the new, patched code to check that the tweaks made to 1433 // accommodate the manifest deployer do not change the original behaviour as 1434 // simulated by the patched-out code. 1435 type prepareGitUniter struct { 1436 prepSteps []stepper 1437 } 1438 1439 func (s prepareGitUniter) step(c *gc.C, ctx *context) { 1440 c.Assert(ctx.uniter, gc.IsNil, gc.Commentf("please don't try to patch stuff while the uniter's running")) 1441 newDeployer := func(charmPath, dataPath string, bundles charm.BundleReader) (charm.Deployer, error) { 1442 return charm.NewGitDeployer(charmPath, dataPath, bundles), nil 1443 } 1444 restoreNewDeployer := gt.PatchValue(&charm.NewDeployer, newDeployer) 1445 defer restoreNewDeployer() 1446 1447 fixDeployer := func(deployer *charm.Deployer) error { 1448 return nil 1449 } 1450 restoreFixDeployer := gt.PatchValue(&charm.FixDeployer, fixDeployer) 1451 defer restoreFixDeployer() 1452 1453 for _, prepStep := range s.prepSteps { 1454 step(c, ctx, prepStep) 1455 } 1456 if ctx.uniter != nil { 1457 step(c, ctx, stopUniter{}) 1458 } 1459 } 1460 1461 func ugt(summary string, steps ...stepper) uniterTest { 1462 return ut(summary, prepareGitUniter{steps}) 1463 } 1464 1465 type verifyGitCharm struct { 1466 revision int 1467 dirty bool 1468 } 1469 1470 func (s verifyGitCharm) step(c *gc.C, ctx *context) { 1471 charmPath := filepath.Join(ctx.path, "charm") 1472 if !s.dirty { 1473 revisionPath := filepath.Join(charmPath, "revision") 1474 content, err := ioutil.ReadFile(revisionPath) 1475 c.Assert(err, jc.ErrorIsNil) 1476 c.Assert(string(content), gc.Equals, strconv.Itoa(s.revision)) 1477 err = ctx.unit.Refresh() 1478 c.Assert(err, jc.ErrorIsNil) 1479 url, ok := ctx.unit.CharmURL() 1480 c.Assert(ok, jc.IsTrue) 1481 c.Assert(url, gc.DeepEquals, curl(s.revision)) 1482 } 1483 1484 // Before we try to check the git status, make sure expected hooks are all 1485 // complete, to prevent the test and the uniter interfering with each other. 1486 step(c, ctx, waitHooks{}) 1487 step(c, ctx, waitHooks{}) 1488 cmd := exec.Command("git", "status") 1489 cmd.Dir = filepath.Join(ctx.path, "charm") 1490 out, err := cmd.CombinedOutput() 1491 c.Assert(err, jc.ErrorIsNil) 1492 cmp := gc.Matches 1493 if s.dirty { 1494 cmp = gc.Not(gc.Matches) 1495 } 1496 c.Assert(string(out), cmp, "(# )?On branch master\nnothing to commit.*\n") 1497 } 1498 1499 type startGitUpgradeError struct{} 1500 1501 func (s startGitUpgradeError) step(c *gc.C, ctx *context) { 1502 steps := []stepper{ 1503 createCharm{ 1504 customize: func(c *gc.C, ctx *context, path string) { 1505 appendHook(c, path, "start", "echo STARTDATA > data") 1506 }, 1507 }, 1508 serveCharm{}, 1509 createUniter{}, 1510 waitUnit{ 1511 status: params.StatusActive, 1512 }, 1513 waitHooks{"install", "config-changed", "start"}, 1514 verifyGitCharm{dirty: true}, 1515 1516 createCharm{ 1517 revision: 1, 1518 customize: func(c *gc.C, ctx *context, path string) { 1519 ft.File{"data", "<nelson>ha ha</nelson>", 0644}.Create(c, path) 1520 ft.File{"ignore", "anything", 0644}.Create(c, path) 1521 }, 1522 }, 1523 serveCharm{}, 1524 upgradeCharm{revision: 1}, 1525 waitUnit{ 1526 status: params.StatusError, 1527 info: "upgrade failed", 1528 charm: 1, 1529 }, 1530 verifyWaiting{}, 1531 verifyGitCharm{dirty: true}, 1532 } 1533 for _, s_ := range steps { 1534 step(c, ctx, s_) 1535 } 1536 }