github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"reflect"
    14  	"runtime"
    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  	"github.com/juju/utils/clock"
    27  	utilexec "github.com/juju/utils/exec"
    28  	"github.com/juju/utils/fslock"
    29  	"github.com/juju/utils/proxy"
    30  	gc "gopkg.in/check.v1"
    31  	corecharm "gopkg.in/juju/charm.v6-unstable"
    32  	goyaml "gopkg.in/yaml.v2"
    33  
    34  	apiuniter "github.com/juju/juju/api/uniter"
    35  	"github.com/juju/juju/core/leadership"
    36  	coreleadership "github.com/juju/juju/core/leadership"
    37  	"github.com/juju/juju/juju/sockets"
    38  	"github.com/juju/juju/juju/testing"
    39  	"github.com/juju/juju/network"
    40  	"github.com/juju/juju/resource/resourcetesting"
    41  	"github.com/juju/juju/state"
    42  	"github.com/juju/juju/state/storage"
    43  	"github.com/juju/juju/status"
    44  	"github.com/juju/juju/testcharms"
    45  	coretesting "github.com/juju/juju/testing"
    46  	"github.com/juju/juju/worker"
    47  	"github.com/juju/juju/worker/fortress"
    48  	"github.com/juju/juju/worker/uniter"
    49  	"github.com/juju/juju/worker/uniter/charm"
    50  	"github.com/juju/juju/worker/uniter/operation"
    51  )
    52  
    53  // worstCase is used for timeouts when timing out
    54  // will fail the test. Raising this value should
    55  // not affect the overall running time of the tests
    56  // unless they fail.
    57  const worstCase = coretesting.LongWait
    58  
    59  // Assign the unit to a provisioned machine with dummy addresses set.
    60  func assertAssignUnit(c *gc.C, st *state.State, u *state.Unit) {
    61  	err := u.AssignToNewMachine()
    62  	c.Assert(err, jc.ErrorIsNil)
    63  	mid, err := u.AssignedMachineId()
    64  	c.Assert(err, jc.ErrorIsNil)
    65  	machine, err := st.Machine(mid)
    66  	c.Assert(err, jc.ErrorIsNil)
    67  	err = machine.SetProvisioned("i-exist", "fake_nonce", nil)
    68  	c.Assert(err, jc.ErrorIsNil)
    69  	err = machine.SetProviderAddresses(network.Address{
    70  		Type:  network.IPv4Address,
    71  		Scope: network.ScopeCloudLocal,
    72  		Value: "private.address.example.com",
    73  	}, network.Address{
    74  		Type:  network.IPv4Address,
    75  		Scope: network.ScopePublic,
    76  		Value: "public.address.example.com",
    77  	})
    78  	c.Assert(err, jc.ErrorIsNil)
    79  }
    80  
    81  type context struct {
    82  	uuid                   string
    83  	path                   string
    84  	dataDir                string
    85  	s                      *UniterSuite
    86  	st                     *state.State
    87  	api                    *apiuniter.State
    88  	leaderClaimer          coreleadership.Claimer
    89  	leaderTracker          *mockLeaderTracker
    90  	charmDirGuard          *mockCharmDirGuard
    91  	charms                 map[string][]byte
    92  	hooks                  []string
    93  	sch                    *state.Charm
    94  	svc                    *state.Service
    95  	unit                   *state.Unit
    96  	uniter                 *uniter.Uniter
    97  	relatedSvc             *state.Service
    98  	relation               *state.Relation
    99  	relationUnits          map[string]*state.RelationUnit
   100  	subordinate            *state.Unit
   101  	updateStatusHookTicker *manualTicker
   102  	err                    string
   103  
   104  	wg             sync.WaitGroup
   105  	mu             sync.Mutex
   106  	hooksCompleted []string
   107  }
   108  
   109  var _ uniter.UniterExecutionObserver = (*context)(nil)
   110  
   111  // HookCompleted implements the UniterExecutionObserver interface.
   112  func (ctx *context) HookCompleted(hookName string) {
   113  	ctx.mu.Lock()
   114  	ctx.hooksCompleted = append(ctx.hooksCompleted, hookName)
   115  	ctx.mu.Unlock()
   116  }
   117  
   118  // HookFailed implements the UniterExecutionObserver interface.
   119  func (ctx *context) HookFailed(hookName string) {
   120  	ctx.mu.Lock()
   121  	ctx.hooksCompleted = append(ctx.hooksCompleted, "fail-"+hookName)
   122  	ctx.mu.Unlock()
   123  }
   124  
   125  func (ctx *context) setExpectedError(err string) {
   126  	ctx.mu.Lock()
   127  	ctx.err = err
   128  	ctx.mu.Unlock()
   129  }
   130  
   131  func (ctx *context) run(c *gc.C, steps []stepper) {
   132  	defer func() {
   133  		if ctx.uniter != nil {
   134  			err := worker.Stop(ctx.uniter)
   135  			if ctx.err == "" {
   136  				c.Assert(err, jc.ErrorIsNil)
   137  			} else {
   138  				c.Assert(err, gc.ErrorMatches, ctx.err)
   139  			}
   140  		}
   141  	}()
   142  	for i, s := range steps {
   143  		c.Logf("step %d:\n", i)
   144  		step(c, ctx, s)
   145  	}
   146  }
   147  
   148  func (ctx *context) apiLogin(c *gc.C) {
   149  	password, err := utils.RandomPassword()
   150  	c.Assert(err, jc.ErrorIsNil)
   151  	err = ctx.unit.SetPassword(password)
   152  	c.Assert(err, jc.ErrorIsNil)
   153  	st := ctx.s.OpenAPIAs(c, ctx.unit.Tag(), password)
   154  	c.Assert(st, gc.NotNil)
   155  	c.Logf("API: login as %q successful", ctx.unit.Tag())
   156  	ctx.api, err = st.Uniter()
   157  	c.Assert(err, jc.ErrorIsNil)
   158  	c.Assert(ctx.api, gc.NotNil)
   159  	ctx.leaderClaimer = ctx.st.LeadershipClaimer()
   160  	ctx.leaderTracker = newMockLeaderTracker(ctx)
   161  	ctx.leaderTracker.setLeader(c, true)
   162  }
   163  
   164  func (ctx *context) writeExplicitHook(c *gc.C, path string, contents string) {
   165  	err := ioutil.WriteFile(path+cmdSuffix, []byte(contents), 0755)
   166  	c.Assert(err, jc.ErrorIsNil)
   167  }
   168  
   169  func (ctx *context) writeHook(c *gc.C, path string, good bool) {
   170  	hook := badHook
   171  	if good {
   172  		hook = goodHook
   173  	}
   174  	content := fmt.Sprintf(hook, filepath.Base(path))
   175  	ctx.writeExplicitHook(c, path, content)
   176  }
   177  
   178  func (ctx *context) writeActions(c *gc.C, path string, names []string) {
   179  	for _, name := range names {
   180  		ctx.writeAction(c, path, name)
   181  	}
   182  }
   183  
   184  func (ctx *context) writeMetricsYaml(c *gc.C, path string) {
   185  	metricsYamlPath := filepath.Join(path, "metrics.yaml")
   186  	var metricsYamlFull []byte = []byte(`
   187  metrics:
   188    pings:
   189      type: gauge
   190      description: sample metric
   191  `)
   192  	err := ioutil.WriteFile(metricsYamlPath, []byte(metricsYamlFull), 0755)
   193  	c.Assert(err, jc.ErrorIsNil)
   194  }
   195  
   196  func (ctx *context) writeAction(c *gc.C, path, name string) {
   197  	actionPath := filepath.Join(path, "actions", name)
   198  	action := actions[name]
   199  	err := ioutil.WriteFile(actionPath+cmdSuffix, []byte(action), 0755)
   200  	c.Assert(err, jc.ErrorIsNil)
   201  }
   202  
   203  func (ctx *context) writeActionsYaml(c *gc.C, path string, names ...string) {
   204  	var actionsYaml = map[string]string{
   205  		"base": "",
   206  		"snapshot": `
   207  snapshot:
   208     description: Take a snapshot of the database.
   209     params:
   210        outfile:
   211           description: "The file to write out to."
   212           type: string
   213     required: ["outfile"]
   214  `[1:],
   215  		"action-log": `
   216  action-log:
   217  `[1:],
   218  		"action-log-fail": `
   219  action-log-fail:
   220  `[1:],
   221  		"action-log-fail-error": `
   222  action-log-fail-error:
   223  `[1:],
   224  		"action-reboot": `
   225  action-reboot:
   226  `[1:],
   227  	}
   228  	actionsYamlPath := filepath.Join(path, "actions.yaml")
   229  	var actionsYamlFull string
   230  	// Build an appropriate actions.yaml
   231  	if names[0] != "base" {
   232  		names = append([]string{"base"}, names...)
   233  	}
   234  	for _, name := range names {
   235  		actionsYamlFull = strings.Join(
   236  			[]string{actionsYamlFull, actionsYaml[name]}, "\n")
   237  	}
   238  	err := ioutil.WriteFile(actionsYamlPath, []byte(actionsYamlFull), 0755)
   239  	c.Assert(err, jc.ErrorIsNil)
   240  }
   241  
   242  func (ctx *context) matchHooks(c *gc.C) (match bool, overshoot bool) {
   243  	ctx.mu.Lock()
   244  	defer ctx.mu.Unlock()
   245  	c.Logf("  actual hooks: %#v", ctx.hooksCompleted)
   246  	c.Logf("expected hooks: %#v", ctx.hooks)
   247  	if len(ctx.hooksCompleted) < len(ctx.hooks) {
   248  		return false, false
   249  	}
   250  	for i, e := range ctx.hooks {
   251  		if ctx.hooksCompleted[i] != e {
   252  			return false, false
   253  		}
   254  	}
   255  	return true, len(ctx.hooksCompleted) > len(ctx.hooks)
   256  }
   257  
   258  type uniterTest struct {
   259  	summary string
   260  	steps   []stepper
   261  }
   262  
   263  func ut(summary string, steps ...stepper) uniterTest {
   264  	return uniterTest{summary, steps}
   265  }
   266  
   267  type stepper interface {
   268  	step(c *gc.C, ctx *context)
   269  }
   270  
   271  func step(c *gc.C, ctx *context, s stepper) {
   272  	c.Logf("%#v", s)
   273  	s.step(c, ctx)
   274  }
   275  
   276  type ensureStateWorker struct{}
   277  
   278  func (s ensureStateWorker) step(c *gc.C, ctx *context) {
   279  	addresses, err := ctx.st.Addresses()
   280  	if err != nil || len(addresses) == 0 {
   281  		addControllerMachine(c, ctx.st)
   282  	}
   283  	addresses, err = ctx.st.APIAddressesFromMachines()
   284  	c.Assert(err, jc.ErrorIsNil)
   285  	c.Assert(addresses, gc.HasLen, 1)
   286  }
   287  
   288  func addControllerMachine(c *gc.C, st *state.State) {
   289  	// The AddControllerMachine call will update the API host ports
   290  	// to made-up addresses. We need valid addresses so that the uniter
   291  	// can download charms from the API server.
   292  	apiHostPorts, err := st.APIHostPorts()
   293  	c.Assert(err, gc.IsNil)
   294  	testing.AddControllerMachine(c, st)
   295  	err = st.SetAPIHostPorts(apiHostPorts)
   296  	c.Assert(err, gc.IsNil)
   297  }
   298  
   299  type createCharm struct {
   300  	revision  int
   301  	badHooks  []string
   302  	customize func(*gc.C, *context, string)
   303  }
   304  
   305  var (
   306  	baseCharmHooks = []string{
   307  		"install", "start", "config-changed", "upgrade-charm", "stop",
   308  		"db-relation-joined", "db-relation-changed", "db-relation-departed",
   309  		"db-relation-broken", "meter-status-changed", "collect-metrics", "update-status",
   310  	}
   311  	leaderCharmHooks = []string{
   312  		"leader-elected", "leader-deposed", "leader-settings-changed",
   313  	}
   314  	storageCharmHooks = []string{
   315  		"wp-content-storage-attached", "wp-content-storage-detaching",
   316  	}
   317  )
   318  
   319  func startupHooks(minion bool) []string {
   320  	leaderHook := "leader-elected"
   321  	if minion {
   322  		leaderHook = "leader-settings-changed"
   323  	}
   324  	return []string{"install", leaderHook, "config-changed", "start"}
   325  }
   326  
   327  func (s createCharm) step(c *gc.C, ctx *context) {
   328  	base := testcharms.Repo.ClonedDirPath(c.MkDir(), "wordpress")
   329  
   330  	allCharmHooks := baseCharmHooks
   331  	allCharmHooks = append(allCharmHooks, leaderCharmHooks...)
   332  	allCharmHooks = append(allCharmHooks, storageCharmHooks...)
   333  
   334  	for _, name := range allCharmHooks {
   335  		path := filepath.Join(base, "hooks", name)
   336  		good := true
   337  		for _, bad := range s.badHooks {
   338  			if name == bad {
   339  				good = false
   340  			}
   341  		}
   342  		ctx.writeHook(c, path, good)
   343  	}
   344  	if s.customize != nil {
   345  		s.customize(c, ctx, base)
   346  	}
   347  	dir, err := corecharm.ReadCharmDir(base)
   348  	c.Assert(err, jc.ErrorIsNil)
   349  	err = dir.SetDiskRevision(s.revision)
   350  	c.Assert(err, jc.ErrorIsNil)
   351  	step(c, ctx, addCharm{dir, curl(s.revision)})
   352  }
   353  
   354  func (s createCharm) charmURL() string {
   355  	return curl(s.revision).String()
   356  }
   357  
   358  type addCharm struct {
   359  	dir  *corecharm.CharmDir
   360  	curl *corecharm.URL
   361  }
   362  
   363  func (s addCharm) step(c *gc.C, ctx *context) {
   364  	var buf bytes.Buffer
   365  	err := s.dir.ArchiveTo(&buf)
   366  	c.Assert(err, jc.ErrorIsNil)
   367  	body := buf.Bytes()
   368  	hash, _, err := utils.ReadSHA256(&buf)
   369  	c.Assert(err, jc.ErrorIsNil)
   370  
   371  	storagePath := fmt.Sprintf("/charms/%s/%d", s.dir.Meta().Name, s.dir.Revision())
   372  	ctx.charms[storagePath] = body
   373  	info := state.CharmInfo{
   374  		Charm:       s.dir,
   375  		ID:          s.curl,
   376  		StoragePath: storagePath,
   377  		SHA256:      hash,
   378  	}
   379  
   380  	ctx.sch, err = ctx.st.AddCharm(info)
   381  	c.Assert(err, jc.ErrorIsNil)
   382  }
   383  
   384  type serveCharm struct{}
   385  
   386  func (s serveCharm) step(c *gc.C, ctx *context) {
   387  	storage := storage.NewStorage(ctx.st.ModelUUID(), ctx.st.MongoSession())
   388  	for storagePath, data := range ctx.charms {
   389  		err := storage.Put(storagePath, bytes.NewReader(data), int64(len(data)))
   390  		c.Assert(err, jc.ErrorIsNil)
   391  		delete(ctx.charms, storagePath)
   392  	}
   393  }
   394  
   395  type createServiceAndUnit struct {
   396  	serviceName string
   397  }
   398  
   399  func (csau createServiceAndUnit) step(c *gc.C, ctx *context) {
   400  	if csau.serviceName == "" {
   401  		csau.serviceName = "u"
   402  	}
   403  	sch, err := ctx.st.Charm(curl(0))
   404  	c.Assert(err, jc.ErrorIsNil)
   405  	svc := ctx.s.AddTestingService(c, csau.serviceName, sch)
   406  	unit, err := svc.AddUnit()
   407  	c.Assert(err, jc.ErrorIsNil)
   408  
   409  	// Assign the unit to a provisioned machine to match expected state.
   410  	assertAssignUnit(c, ctx.st, unit)
   411  	ctx.svc = svc
   412  	ctx.unit = unit
   413  
   414  	ctx.apiLogin(c)
   415  }
   416  
   417  type createUniter struct {
   418  	minion       bool
   419  	executorFunc uniter.NewExecutorFunc
   420  }
   421  
   422  func (s createUniter) step(c *gc.C, ctx *context) {
   423  	step(c, ctx, ensureStateWorker{})
   424  	step(c, ctx, createServiceAndUnit{})
   425  	if s.minion {
   426  		step(c, ctx, forceMinion{})
   427  	}
   428  	step(c, ctx, startUniter{newExecutorFunc: s.executorFunc})
   429  	step(c, ctx, waitAddresses{})
   430  }
   431  
   432  type waitAddresses struct{}
   433  
   434  func (waitAddresses) step(c *gc.C, ctx *context) {
   435  	timeout := time.After(worstCase)
   436  	for {
   437  		select {
   438  		case <-timeout:
   439  			c.Fatalf("timed out waiting for unit addresses")
   440  		case <-time.After(coretesting.ShortWait):
   441  			err := ctx.unit.Refresh()
   442  			if err != nil {
   443  				c.Fatalf("unit refresh failed: %v", err)
   444  			}
   445  			// GZ 2013-07-10: Hardcoded values from dummy environ
   446  			//                special cased here, questionable.
   447  			private, _ := ctx.unit.PrivateAddress()
   448  			if private.Value != "private.address.example.com" {
   449  				continue
   450  			}
   451  			public, _ := ctx.unit.PublicAddress()
   452  			if public.Value != "public.address.example.com" {
   453  				continue
   454  			}
   455  			return
   456  		}
   457  	}
   458  }
   459  
   460  type startUniter struct {
   461  	unitTag         string
   462  	newExecutorFunc uniter.NewExecutorFunc
   463  }
   464  
   465  func (s startUniter) step(c *gc.C, ctx *context) {
   466  	if s.unitTag == "" {
   467  		s.unitTag = "unit-u-0"
   468  	}
   469  	if ctx.uniter != nil {
   470  		panic("don't start two uniters!")
   471  	}
   472  	if ctx.api == nil {
   473  		panic("API connection not established")
   474  	}
   475  	tag, err := names.ParseUnitTag(s.unitTag)
   476  	if err != nil {
   477  		panic(err.Error())
   478  	}
   479  	locksDir := filepath.Join(ctx.dataDir, "locks")
   480  	lock, err := fslock.NewLock(locksDir, "uniter-hook-execution", fslock.Defaults())
   481  	c.Assert(err, jc.ErrorIsNil)
   482  	operationExecutor := operation.NewExecutor
   483  	if s.newExecutorFunc != nil {
   484  		operationExecutor = s.newExecutorFunc
   485  	}
   486  
   487  	uniterParams := uniter.UniterParams{
   488  		UniterFacade:         ctx.api,
   489  		UnitTag:              tag,
   490  		LeadershipTracker:    ctx.leaderTracker,
   491  		CharmDirGuard:        ctx.charmDirGuard,
   492  		DataDir:              ctx.dataDir,
   493  		MachineLock:          lock,
   494  		UpdateStatusSignal:   ctx.updateStatusHookTicker.ReturnTimer,
   495  		NewOperationExecutor: operationExecutor,
   496  		Observer:             ctx,
   497  		// TODO(axw) 2015-11-02 #1512191
   498  		// update tests that rely on timing to advance clock
   499  		// appropriately.
   500  		Clock: clock.WallClock,
   501  	}
   502  	ctx.uniter, err = uniter.NewUniter(&uniterParams)
   503  	c.Assert(err, jc.ErrorIsNil)
   504  }
   505  
   506  type waitUniterDead struct {
   507  	err string
   508  }
   509  
   510  func (s waitUniterDead) step(c *gc.C, ctx *context) {
   511  	if s.err != "" {
   512  		err := s.waitDead(c, ctx)
   513  		c.Assert(err, gc.ErrorMatches, s.err)
   514  		return
   515  	}
   516  
   517  	// In the default case, we're waiting for worker.ErrTerminateAgent, but
   518  	// the path to that error can be tricky. If the unit becomes Dead at an
   519  	// inconvenient time, unrelated calls can fail -- as they should -- but
   520  	// not be detected as worker.ErrTerminateAgent. In this case, we restart
   521  	// the uniter and check that it fails as expected when starting up; this
   522  	// mimics the behaviour of the unit agent and verifies that the UA will,
   523  	// eventually, see the correct error and respond appropriately.
   524  	err := s.waitDead(c, ctx)
   525  	if err != worker.ErrTerminateAgent {
   526  		step(c, ctx, startUniter{})
   527  		err = s.waitDead(c, ctx)
   528  	}
   529  	c.Assert(err, gc.Equals, worker.ErrTerminateAgent)
   530  	err = ctx.unit.Refresh()
   531  	c.Assert(err, jc.ErrorIsNil)
   532  	c.Assert(ctx.unit.Life(), gc.Equals, state.Dead)
   533  }
   534  
   535  func (s waitUniterDead) waitDead(c *gc.C, ctx *context) error {
   536  	u := ctx.uniter
   537  	ctx.uniter = nil
   538  
   539  	wait := make(chan error, 1)
   540  	go func() {
   541  		wait <- u.Wait()
   542  	}()
   543  
   544  	ctx.s.BackingState.StartSync()
   545  	select {
   546  	case err := <-wait:
   547  		return err
   548  	case <-time.After(worstCase):
   549  		u.Kill()
   550  		c.Fatalf("uniter still alive")
   551  	}
   552  	panic("unreachable")
   553  }
   554  
   555  type stopUniter struct {
   556  	err string
   557  }
   558  
   559  func (s stopUniter) step(c *gc.C, ctx *context) {
   560  	u := ctx.uniter
   561  	if u == nil {
   562  		c.Logf("uniter not started, skipping stopUniter{}")
   563  		return
   564  	}
   565  	ctx.uniter = nil
   566  	err := worker.Stop(u)
   567  	if s.err == "" {
   568  		c.Assert(err, jc.ErrorIsNil)
   569  	} else {
   570  		c.Assert(err, gc.ErrorMatches, s.err)
   571  	}
   572  }
   573  
   574  type verifyWaiting struct{}
   575  
   576  func (s verifyWaiting) step(c *gc.C, ctx *context) {
   577  	step(c, ctx, stopUniter{})
   578  	step(c, ctx, startUniter{})
   579  	step(c, ctx, waitHooks{})
   580  }
   581  
   582  type verifyRunning struct {
   583  	minion bool
   584  }
   585  
   586  func (s verifyRunning) step(c *gc.C, ctx *context) {
   587  	step(c, ctx, stopUniter{})
   588  	step(c, ctx, startUniter{})
   589  	var hooks []string
   590  	if s.minion {
   591  		hooks = append(hooks, "leader-settings-changed")
   592  	}
   593  	hooks = append(hooks, "config-changed")
   594  	step(c, ctx, waitHooks(hooks))
   595  }
   596  
   597  type startupErrorWithCustomCharm struct {
   598  	badHook   string
   599  	customize func(*gc.C, *context, string)
   600  }
   601  
   602  func (s startupErrorWithCustomCharm) step(c *gc.C, ctx *context) {
   603  	step(c, ctx, createCharm{
   604  		badHooks:  []string{s.badHook},
   605  		customize: s.customize,
   606  	})
   607  	step(c, ctx, serveCharm{})
   608  	step(c, ctx, createUniter{})
   609  	step(c, ctx, waitUnitAgent{
   610  		statusGetter: unitStatusGetter,
   611  		status:       status.StatusError,
   612  		info:         fmt.Sprintf(`hook failed: %q`, s.badHook),
   613  	})
   614  	for _, hook := range startupHooks(false) {
   615  		if hook == s.badHook {
   616  			step(c, ctx, waitHooks{"fail-" + hook})
   617  			break
   618  		}
   619  		step(c, ctx, waitHooks{hook})
   620  	}
   621  	step(c, ctx, verifyCharm{})
   622  }
   623  
   624  type startupError struct {
   625  	badHook string
   626  }
   627  
   628  func (s startupError) step(c *gc.C, ctx *context) {
   629  	step(c, ctx, createCharm{badHooks: []string{s.badHook}})
   630  	step(c, ctx, serveCharm{})
   631  	step(c, ctx, createUniter{})
   632  	step(c, ctx, waitUnitAgent{
   633  		statusGetter: unitStatusGetter,
   634  		status:       status.StatusError,
   635  		info:         fmt.Sprintf(`hook failed: %q`, s.badHook),
   636  	})
   637  	for _, hook := range startupHooks(false) {
   638  		if hook == s.badHook {
   639  			step(c, ctx, waitHooks{"fail-" + hook})
   640  			break
   641  		}
   642  		step(c, ctx, waitHooks{hook})
   643  	}
   644  	step(c, ctx, verifyCharm{})
   645  }
   646  
   647  type quickStart struct {
   648  	minion bool
   649  }
   650  
   651  func (s quickStart) step(c *gc.C, ctx *context) {
   652  	step(c, ctx, createCharm{})
   653  	step(c, ctx, serveCharm{})
   654  	step(c, ctx, createUniter{minion: s.minion})
   655  	step(c, ctx, waitUnitAgent{status: status.StatusIdle})
   656  	step(c, ctx, waitHooks(startupHooks(s.minion)))
   657  	step(c, ctx, verifyCharm{})
   658  }
   659  
   660  type quickStartRelation struct{}
   661  
   662  func (s quickStartRelation) step(c *gc.C, ctx *context) {
   663  	step(c, ctx, quickStart{})
   664  	step(c, ctx, addRelation{})
   665  	step(c, ctx, addRelationUnit{})
   666  	step(c, ctx, waitHooks{"db-relation-joined mysql/0 db:0", "db-relation-changed mysql/0 db:0"})
   667  	step(c, ctx, verifyRunning{})
   668  }
   669  
   670  type startupRelationError struct {
   671  	badHook string
   672  }
   673  
   674  func (s startupRelationError) step(c *gc.C, ctx *context) {
   675  	step(c, ctx, createCharm{badHooks: []string{s.badHook}})
   676  	step(c, ctx, serveCharm{})
   677  	step(c, ctx, createUniter{})
   678  	step(c, ctx, waitUnitAgent{status: status.StatusIdle})
   679  	step(c, ctx, waitHooks(startupHooks(false)))
   680  	step(c, ctx, verifyCharm{})
   681  	step(c, ctx, addRelation{})
   682  	step(c, ctx, addRelationUnit{})
   683  }
   684  
   685  type resolveError struct {
   686  	resolved state.ResolvedMode
   687  }
   688  
   689  func (s resolveError) step(c *gc.C, ctx *context) {
   690  	err := ctx.unit.SetResolved(s.resolved)
   691  	c.Assert(err, jc.ErrorIsNil)
   692  }
   693  
   694  type statusfunc func() (status.StatusInfo, error)
   695  
   696  type statusfuncGetter func(ctx *context) statusfunc
   697  
   698  var unitStatusGetter = func(ctx *context) statusfunc {
   699  	return func() (status.StatusInfo, error) {
   700  		return ctx.unit.Status()
   701  	}
   702  }
   703  
   704  var agentStatusGetter = func(ctx *context) statusfunc {
   705  	return func() (status.StatusInfo, error) {
   706  		return ctx.unit.AgentStatus()
   707  	}
   708  }
   709  
   710  type waitUnitAgent struct {
   711  	statusGetter func(ctx *context) statusfunc
   712  	status       status.Status
   713  	info         string
   714  	data         map[string]interface{}
   715  	charm        int
   716  	resolved     state.ResolvedMode
   717  }
   718  
   719  func (s waitUnitAgent) step(c *gc.C, ctx *context) {
   720  	if s.statusGetter == nil {
   721  		s.statusGetter = agentStatusGetter
   722  	}
   723  	timeout := time.After(worstCase)
   724  	for {
   725  		ctx.s.BackingState.StartSync()
   726  		select {
   727  		case <-time.After(coretesting.ShortWait):
   728  			err := ctx.unit.Refresh()
   729  			if err != nil {
   730  				c.Fatalf("cannot refresh unit: %v", err)
   731  			}
   732  			resolved := ctx.unit.Resolved()
   733  			if resolved != s.resolved {
   734  				c.Logf("want resolved mode %q, got %q; still waiting", s.resolved, resolved)
   735  				continue
   736  			}
   737  			url, ok := ctx.unit.CharmURL()
   738  			if !ok || *url != *curl(s.charm) {
   739  				var got string
   740  				if ok {
   741  					got = url.String()
   742  				}
   743  				c.Logf("want unit charm %q, got %q; still waiting", curl(s.charm), got)
   744  				continue
   745  			}
   746  			statusInfo, err := s.statusGetter(ctx)()
   747  			c.Assert(err, jc.ErrorIsNil)
   748  			if string(statusInfo.Status) != string(s.status) {
   749  				c.Logf("want unit status %q, got %q; still waiting", s.status, statusInfo.Status)
   750  				continue
   751  			}
   752  			if statusInfo.Message != s.info {
   753  				c.Logf("want unit status info %q, got %q; still waiting", s.info, statusInfo.Message)
   754  				continue
   755  			}
   756  			if s.data != nil {
   757  				if len(statusInfo.Data) != len(s.data) {
   758  					c.Logf("want %d status data value(s), got %d; still waiting", len(s.data), len(statusInfo.Data))
   759  					continue
   760  				}
   761  				for key, value := range s.data {
   762  					if statusInfo.Data[key] != value {
   763  						c.Logf("want status data value %q for key %q, got %q; still waiting",
   764  							value, key, statusInfo.Data[key])
   765  						continue
   766  					}
   767  				}
   768  			}
   769  			return
   770  		case <-timeout:
   771  			c.Fatalf("never reached desired status")
   772  		}
   773  	}
   774  }
   775  
   776  type waitHooks []string
   777  
   778  func (s waitHooks) step(c *gc.C, ctx *context) {
   779  	if len(s) == 0 {
   780  		// Give unwanted hooks a moment to run...
   781  		ctx.s.BackingState.StartSync()
   782  		time.Sleep(coretesting.ShortWait)
   783  	}
   784  	ctx.hooks = append(ctx.hooks, s...)
   785  	c.Logf("waiting for hooks: %#v", ctx.hooks)
   786  	match, overshoot := ctx.matchHooks(c)
   787  	if overshoot && len(s) == 0 {
   788  		c.Fatalf("ran more hooks than expected")
   789  	}
   790  	waitExecutionLockReleased := func() {
   791  		lock := createHookLock(c, ctx.dataDir)
   792  		if err := lock.LockWithTimeout(worstCase, "waiting for lock"); err != nil {
   793  			c.Fatalf("failed to acquire execution lock: %v", err)
   794  		}
   795  		if err := lock.Unlock(); err != nil {
   796  			c.Fatalf("failed to release execution lock: %v", err)
   797  		}
   798  	}
   799  	if match {
   800  		if len(s) > 0 {
   801  			// only check for lock release if there were hooks
   802  			// run; hooks *not* running may be due to the lock
   803  			// being held.
   804  			waitExecutionLockReleased()
   805  		}
   806  		return
   807  	}
   808  	timeout := time.After(worstCase)
   809  	for {
   810  		ctx.s.BackingState.StartSync()
   811  		select {
   812  		case <-time.After(coretesting.ShortWait):
   813  			if match, _ = ctx.matchHooks(c); match {
   814  				waitExecutionLockReleased()
   815  				return
   816  			}
   817  		case <-timeout:
   818  			c.Fatalf("never got expected hooks")
   819  		}
   820  	}
   821  }
   822  
   823  type actionResult struct {
   824  	name    string
   825  	results map[string]interface{}
   826  	status  string
   827  	message string
   828  }
   829  
   830  type waitActionResults struct {
   831  	expectedResults []actionResult
   832  }
   833  
   834  func (s waitActionResults) step(c *gc.C, ctx *context) {
   835  	resultsWatcher := ctx.st.WatchActionResults()
   836  	defer func() {
   837  		c.Assert(resultsWatcher.Stop(), gc.IsNil)
   838  	}()
   839  	timeout := time.After(worstCase)
   840  	for {
   841  		ctx.s.BackingState.StartSync()
   842  		select {
   843  		case <-time.After(coretesting.ShortWait):
   844  			continue
   845  		case <-timeout:
   846  			c.Fatalf("timed out waiting for action results")
   847  		case changes, ok := <-resultsWatcher.Changes():
   848  			c.Logf("Got changes: %#v", changes)
   849  			c.Assert(ok, jc.IsTrue)
   850  			stateActionResults, err := ctx.unit.CompletedActions()
   851  			c.Assert(err, jc.ErrorIsNil)
   852  			if len(stateActionResults) != len(s.expectedResults) {
   853  				continue
   854  			}
   855  			actualResults := make([]actionResult, len(stateActionResults))
   856  			for i, result := range stateActionResults {
   857  				results, message := result.Results()
   858  				actualResults[i] = actionResult{
   859  					name:    result.Name(),
   860  					results: results,
   861  					status:  string(result.Status()),
   862  					message: message,
   863  				}
   864  			}
   865  			assertActionResultsMatch(c, actualResults, s.expectedResults)
   866  			return
   867  		}
   868  	}
   869  }
   870  
   871  func assertActionResultsMatch(c *gc.C, actualIn []actionResult, expectIn []actionResult) {
   872  	matches := 0
   873  	desiredMatches := len(actualIn)
   874  	c.Assert(len(actualIn), gc.Equals, len(expectIn))
   875  findMatch:
   876  	for _, expectedItem := range expectIn {
   877  		// find expectedItem in actualIn
   878  		for j, actualItem := range actualIn {
   879  			// If we find a match, remove both items from their
   880  			// respective slices, increment match count, and restart.
   881  			if reflect.DeepEqual(actualItem, expectedItem) {
   882  				actualIn = append(actualIn[:j], actualIn[j+1:]...)
   883  				matches++
   884  				continue findMatch
   885  			}
   886  		}
   887  		// if we finish the whole thing without finding a match, we failed.
   888  		c.Assert(actualIn, jc.DeepEquals, expectIn)
   889  	}
   890  
   891  	c.Assert(matches, gc.Equals, desiredMatches)
   892  }
   893  
   894  type verifyNoActionResults struct{}
   895  
   896  func (s verifyNoActionResults) step(c *gc.C, ctx *context) {
   897  	time.Sleep(coretesting.ShortWait)
   898  	result, err := ctx.unit.CompletedActions()
   899  	c.Assert(err, jc.ErrorIsNil)
   900  	c.Assert(result, gc.HasLen, 0)
   901  }
   902  
   903  type fixHook struct {
   904  	name string
   905  }
   906  
   907  func (s fixHook) step(c *gc.C, ctx *context) {
   908  	path := filepath.Join(ctx.path, "charm", "hooks", s.name)
   909  	ctx.writeHook(c, path, true)
   910  }
   911  
   912  type changeMeterStatus struct {
   913  	code string
   914  	info string
   915  }
   916  
   917  func (s changeMeterStatus) step(c *gc.C, ctx *context) {
   918  	err := ctx.unit.SetMeterStatus(s.code, s.info)
   919  	c.Assert(err, jc.ErrorIsNil)
   920  }
   921  
   922  type updateStatusHookTick struct{}
   923  
   924  func (s updateStatusHookTick) step(c *gc.C, ctx *context) {
   925  	err := ctx.updateStatusHookTicker.Tick()
   926  	c.Assert(err, jc.ErrorIsNil)
   927  }
   928  
   929  type changeConfig map[string]interface{}
   930  
   931  func (s changeConfig) step(c *gc.C, ctx *context) {
   932  	err := ctx.svc.UpdateConfigSettings(corecharm.Settings(s))
   933  	c.Assert(err, jc.ErrorIsNil)
   934  }
   935  
   936  type addAction struct {
   937  	name   string
   938  	params map[string]interface{}
   939  }
   940  
   941  func (s addAction) step(c *gc.C, ctx *context) {
   942  	_, err := ctx.st.EnqueueAction(ctx.unit.Tag(), s.name, s.params)
   943  	c.Assert(err, jc.ErrorIsNil)
   944  }
   945  
   946  type upgradeCharm struct {
   947  	revision int
   948  	forced   bool
   949  }
   950  
   951  func (s upgradeCharm) step(c *gc.C, ctx *context) {
   952  	curl := curl(s.revision)
   953  	sch, err := ctx.st.Charm(curl)
   954  	c.Assert(err, jc.ErrorIsNil)
   955  	cfg := state.SetCharmConfig{
   956  		Charm:      sch,
   957  		ForceUnits: s.forced,
   958  	}
   959  	err = ctx.svc.SetCharm(cfg)
   960  	c.Assert(err, jc.ErrorIsNil)
   961  	serveCharm{}.step(c, ctx)
   962  }
   963  
   964  type verifyCharm struct {
   965  	revision          int
   966  	attemptedRevision int
   967  	checkFiles        ft.Entries
   968  }
   969  
   970  func (s verifyCharm) step(c *gc.C, ctx *context) {
   971  	s.checkFiles.Check(c, filepath.Join(ctx.path, "charm"))
   972  	path := filepath.Join(ctx.path, "charm", "revision")
   973  	content, err := ioutil.ReadFile(path)
   974  	c.Assert(err, jc.ErrorIsNil)
   975  	c.Assert(string(content), gc.Equals, strconv.Itoa(s.revision))
   976  	checkRevision := s.revision
   977  	if s.attemptedRevision > checkRevision {
   978  		checkRevision = s.attemptedRevision
   979  	}
   980  	err = ctx.unit.Refresh()
   981  	c.Assert(err, jc.ErrorIsNil)
   982  	url, ok := ctx.unit.CharmURL()
   983  	c.Assert(ok, jc.IsTrue)
   984  	c.Assert(url, gc.DeepEquals, curl(checkRevision))
   985  }
   986  
   987  type pushResource struct{}
   988  
   989  func (s pushResource) step(c *gc.C, ctx *context) {
   990  	opened := resourcetesting.NewResource(c, &gt.Stub{}, "data", ctx.unit.ServiceName(), "the bytes")
   991  
   992  	res, err := ctx.st.Resources()
   993  	c.Assert(err, jc.ErrorIsNil)
   994  	_, err = res.SetResource(
   995  		ctx.unit.ServiceName(),
   996  		opened.Username,
   997  		opened.Resource.Resource,
   998  		opened.ReadCloser,
   999  	)
  1000  	c.Assert(err, jc.ErrorIsNil)
  1001  }
  1002  
  1003  type startUpgradeError struct{}
  1004  
  1005  func (s startUpgradeError) step(c *gc.C, ctx *context) {
  1006  	steps := []stepper{
  1007  		createCharm{
  1008  			customize: func(c *gc.C, ctx *context, path string) {
  1009  				appendHook(c, path, "start", "chmod 555 $CHARM_DIR")
  1010  			},
  1011  		},
  1012  		serveCharm{},
  1013  		createUniter{},
  1014  		waitUnitAgent{
  1015  			status: status.StatusIdle,
  1016  		},
  1017  		waitHooks(startupHooks(false)),
  1018  		verifyCharm{},
  1019  
  1020  		createCharm{revision: 1},
  1021  		serveCharm{},
  1022  		upgradeCharm{revision: 1},
  1023  		waitUnitAgent{
  1024  			statusGetter: unitStatusGetter,
  1025  			status:       status.StatusError,
  1026  			info:         "upgrade failed",
  1027  			charm:        1,
  1028  		},
  1029  		verifyWaiting{},
  1030  		verifyCharm{attemptedRevision: 1},
  1031  	}
  1032  	for _, s_ := range steps {
  1033  		step(c, ctx, s_)
  1034  	}
  1035  }
  1036  
  1037  type verifyWaitingUpgradeError struct {
  1038  	revision int
  1039  }
  1040  
  1041  func (s verifyWaitingUpgradeError) step(c *gc.C, ctx *context) {
  1042  	verifyCharmSteps := []stepper{
  1043  		waitUnitAgent{
  1044  			statusGetter: unitStatusGetter,
  1045  			status:       status.StatusError,
  1046  			info:         "upgrade failed",
  1047  			charm:        s.revision,
  1048  		},
  1049  		verifyCharm{attemptedRevision: s.revision},
  1050  	}
  1051  	verifyWaitingSteps := []stepper{
  1052  		stopUniter{},
  1053  		custom{func(c *gc.C, ctx *context) {
  1054  			// By setting status to Idle, and waiting for the restarted uniter
  1055  			// to reset the error status, we can avoid a race in which a subsequent
  1056  			// fixUpgradeError lands just before the restarting uniter retries the
  1057  			// upgrade; and thus puts us in an unexpected state for future steps.
  1058  			err := ctx.unit.SetAgentStatus(status.StatusIdle, "", nil)
  1059  			c.Check(err, jc.ErrorIsNil)
  1060  		}},
  1061  		startUniter{},
  1062  	}
  1063  	allSteps := append(verifyCharmSteps, verifyWaitingSteps...)
  1064  	allSteps = append(allSteps, verifyCharmSteps...)
  1065  	for _, s_ := range allSteps {
  1066  		step(c, ctx, s_)
  1067  	}
  1068  }
  1069  
  1070  type fixUpgradeError struct{}
  1071  
  1072  func (s fixUpgradeError) step(c *gc.C, ctx *context) {
  1073  	charmPath := filepath.Join(ctx.path, "charm")
  1074  	err := os.Chmod(charmPath, 0755)
  1075  	c.Assert(err, jc.ErrorIsNil)
  1076  }
  1077  
  1078  type addRelation struct {
  1079  	waitJoin bool
  1080  }
  1081  
  1082  func (s addRelation) step(c *gc.C, ctx *context) {
  1083  	if ctx.relation != nil {
  1084  		panic("don't add two relations!")
  1085  	}
  1086  	if ctx.relatedSvc == nil {
  1087  		ctx.relatedSvc = ctx.s.AddTestingService(c, "mysql", ctx.s.AddTestingCharm(c, "mysql"))
  1088  	}
  1089  	eps, err := ctx.st.InferEndpoints("u", "mysql")
  1090  	c.Assert(err, jc.ErrorIsNil)
  1091  	ctx.relation, err = ctx.st.AddRelation(eps...)
  1092  	c.Assert(err, jc.ErrorIsNil)
  1093  	ctx.relationUnits = map[string]*state.RelationUnit{}
  1094  	if !s.waitJoin {
  1095  		return
  1096  	}
  1097  
  1098  	// It's hard to do this properly (watching scope) without perturbing other tests.
  1099  	ru, err := ctx.relation.Unit(ctx.unit)
  1100  	c.Assert(err, jc.ErrorIsNil)
  1101  	timeout := time.After(worstCase)
  1102  	for {
  1103  		c.Logf("waiting to join relation")
  1104  		select {
  1105  		case <-timeout:
  1106  			c.Fatalf("failed to join relation")
  1107  		case <-time.After(coretesting.ShortWait):
  1108  			inScope, err := ru.InScope()
  1109  			c.Assert(err, jc.ErrorIsNil)
  1110  			if inScope {
  1111  				return
  1112  			}
  1113  		}
  1114  	}
  1115  }
  1116  
  1117  type addRelationUnit struct{}
  1118  
  1119  func (s addRelationUnit) step(c *gc.C, ctx *context) {
  1120  	u, err := ctx.relatedSvc.AddUnit()
  1121  	c.Assert(err, jc.ErrorIsNil)
  1122  	ru, err := ctx.relation.Unit(u)
  1123  	c.Assert(err, jc.ErrorIsNil)
  1124  	err = ru.EnterScope(nil)
  1125  	c.Assert(err, jc.ErrorIsNil)
  1126  	ctx.relationUnits[u.Name()] = ru
  1127  }
  1128  
  1129  type changeRelationUnit struct {
  1130  	name string
  1131  }
  1132  
  1133  func (s changeRelationUnit) step(c *gc.C, ctx *context) {
  1134  	settings, err := ctx.relationUnits[s.name].Settings()
  1135  	c.Assert(err, jc.ErrorIsNil)
  1136  	key := "madness?"
  1137  	raw, _ := settings.Get(key)
  1138  	val, _ := raw.(string)
  1139  	if val == "" {
  1140  		val = "this is juju"
  1141  	} else {
  1142  		val += "u"
  1143  	}
  1144  	settings.Set(key, val)
  1145  	_, err = settings.Write()
  1146  	c.Assert(err, jc.ErrorIsNil)
  1147  }
  1148  
  1149  type removeRelationUnit struct {
  1150  	name string
  1151  }
  1152  
  1153  func (s removeRelationUnit) step(c *gc.C, ctx *context) {
  1154  	err := ctx.relationUnits[s.name].LeaveScope()
  1155  	c.Assert(err, jc.ErrorIsNil)
  1156  	ctx.relationUnits[s.name] = nil
  1157  }
  1158  
  1159  type relationState struct {
  1160  	removed bool
  1161  	life    state.Life
  1162  }
  1163  
  1164  func (s relationState) step(c *gc.C, ctx *context) {
  1165  	err := ctx.relation.Refresh()
  1166  	if s.removed {
  1167  		c.Assert(err, jc.Satisfies, errors.IsNotFound)
  1168  		return
  1169  	}
  1170  	c.Assert(err, jc.ErrorIsNil)
  1171  	c.Assert(ctx.relation.Life(), gc.Equals, s.life)
  1172  
  1173  }
  1174  
  1175  type addSubordinateRelation struct {
  1176  	ifce string
  1177  }
  1178  
  1179  func (s addSubordinateRelation) step(c *gc.C, ctx *context) {
  1180  	if _, err := ctx.st.Service("logging"); errors.IsNotFound(err) {
  1181  		ctx.s.AddTestingService(c, "logging", ctx.s.AddTestingCharm(c, "logging"))
  1182  	}
  1183  	eps, err := ctx.st.InferEndpoints("logging", "u:"+s.ifce)
  1184  	c.Assert(err, jc.ErrorIsNil)
  1185  	_, err = ctx.st.AddRelation(eps...)
  1186  	c.Assert(err, jc.ErrorIsNil)
  1187  }
  1188  
  1189  type removeSubordinateRelation struct {
  1190  	ifce string
  1191  }
  1192  
  1193  func (s removeSubordinateRelation) step(c *gc.C, ctx *context) {
  1194  	eps, err := ctx.st.InferEndpoints("logging", "u:"+s.ifce)
  1195  	c.Assert(err, jc.ErrorIsNil)
  1196  	rel, err := ctx.st.EndpointsRelation(eps...)
  1197  	c.Assert(err, jc.ErrorIsNil)
  1198  	err = rel.Destroy()
  1199  	c.Assert(err, jc.ErrorIsNil)
  1200  }
  1201  
  1202  type waitSubordinateExists struct {
  1203  	name string
  1204  }
  1205  
  1206  func (s waitSubordinateExists) step(c *gc.C, ctx *context) {
  1207  	timeout := time.After(worstCase)
  1208  	for {
  1209  		ctx.s.BackingState.StartSync()
  1210  		select {
  1211  		case <-timeout:
  1212  			c.Fatalf("subordinate was not created")
  1213  		case <-time.After(coretesting.ShortWait):
  1214  			var err error
  1215  			ctx.subordinate, err = ctx.st.Unit(s.name)
  1216  			if errors.IsNotFound(err) {
  1217  				continue
  1218  			}
  1219  			c.Assert(err, jc.ErrorIsNil)
  1220  			return
  1221  		}
  1222  	}
  1223  }
  1224  
  1225  type waitSubordinateDying struct{}
  1226  
  1227  func (waitSubordinateDying) step(c *gc.C, ctx *context) {
  1228  	timeout := time.After(worstCase)
  1229  	for {
  1230  		ctx.s.BackingState.StartSync()
  1231  		select {
  1232  		case <-timeout:
  1233  			c.Fatalf("subordinate was not made Dying")
  1234  		case <-time.After(coretesting.ShortWait):
  1235  			err := ctx.subordinate.Refresh()
  1236  			c.Assert(err, jc.ErrorIsNil)
  1237  			if ctx.subordinate.Life() != state.Dying {
  1238  				continue
  1239  			}
  1240  		}
  1241  		break
  1242  	}
  1243  }
  1244  
  1245  type removeSubordinate struct{}
  1246  
  1247  func (removeSubordinate) step(c *gc.C, ctx *context) {
  1248  	err := ctx.subordinate.EnsureDead()
  1249  	c.Assert(err, jc.ErrorIsNil)
  1250  	err = ctx.subordinate.Remove()
  1251  	c.Assert(err, jc.ErrorIsNil)
  1252  	ctx.subordinate = nil
  1253  }
  1254  
  1255  type assertYaml struct {
  1256  	path   string
  1257  	expect map[string]interface{}
  1258  }
  1259  
  1260  func (s assertYaml) step(c *gc.C, ctx *context) {
  1261  	data, err := ioutil.ReadFile(filepath.Join(ctx.path, s.path))
  1262  	c.Assert(err, jc.ErrorIsNil)
  1263  	actual := make(map[string]interface{})
  1264  	err = goyaml.Unmarshal(data, &actual)
  1265  	c.Assert(err, jc.ErrorIsNil)
  1266  	c.Assert(actual, gc.DeepEquals, s.expect)
  1267  }
  1268  
  1269  type writeFile struct {
  1270  	path string
  1271  	mode os.FileMode
  1272  }
  1273  
  1274  func (s writeFile) step(c *gc.C, ctx *context) {
  1275  	path := filepath.Join(ctx.path, s.path)
  1276  	dir := filepath.Dir(path)
  1277  	err := os.MkdirAll(dir, 0755)
  1278  	c.Assert(err, jc.ErrorIsNil)
  1279  	err = ioutil.WriteFile(path, nil, s.mode)
  1280  	c.Assert(err, jc.ErrorIsNil)
  1281  }
  1282  
  1283  type chmod struct {
  1284  	path string
  1285  	mode os.FileMode
  1286  }
  1287  
  1288  func (s chmod) step(c *gc.C, ctx *context) {
  1289  	path := filepath.Join(ctx.path, s.path)
  1290  	err := os.Chmod(path, s.mode)
  1291  	c.Assert(err, jc.ErrorIsNil)
  1292  }
  1293  
  1294  type custom struct {
  1295  	f func(*gc.C, *context)
  1296  }
  1297  
  1298  func (s custom) step(c *gc.C, ctx *context) {
  1299  	s.f(c, ctx)
  1300  }
  1301  
  1302  var relationDying = custom{func(c *gc.C, ctx *context) {
  1303  	c.Assert(ctx.relation.Destroy(), gc.IsNil)
  1304  }}
  1305  
  1306  var unitDying = custom{func(c *gc.C, ctx *context) {
  1307  	c.Assert(ctx.unit.Destroy(), gc.IsNil)
  1308  }}
  1309  
  1310  var unitDead = custom{func(c *gc.C, ctx *context) {
  1311  	c.Assert(ctx.unit.EnsureDead(), gc.IsNil)
  1312  }}
  1313  
  1314  var subordinateDying = custom{func(c *gc.C, ctx *context) {
  1315  	c.Assert(ctx.subordinate.Destroy(), gc.IsNil)
  1316  }}
  1317  
  1318  func curl(revision int) *corecharm.URL {
  1319  	return corecharm.MustParseURL("cs:quantal/wordpress").WithRevision(revision)
  1320  }
  1321  
  1322  func appendHook(c *gc.C, charm, name, data string) {
  1323  	path := filepath.Join(charm, "hooks", name+cmdSuffix)
  1324  	f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND, 0755)
  1325  	c.Assert(err, jc.ErrorIsNil)
  1326  	defer f.Close()
  1327  	_, err = f.Write([]byte(data))
  1328  	c.Assert(err, jc.ErrorIsNil)
  1329  }
  1330  
  1331  func renameRelation(c *gc.C, charmPath, oldName, newName string) {
  1332  	path := filepath.Join(charmPath, "metadata.yaml")
  1333  	f, err := os.Open(path)
  1334  	c.Assert(err, jc.ErrorIsNil)
  1335  	defer f.Close()
  1336  	meta, err := corecharm.ReadMeta(f)
  1337  	c.Assert(err, jc.ErrorIsNil)
  1338  
  1339  	replace := func(what map[string]corecharm.Relation) bool {
  1340  		for relName, relation := range what {
  1341  			if relName == oldName {
  1342  				what[newName] = relation
  1343  				delete(what, oldName)
  1344  				return true
  1345  			}
  1346  		}
  1347  		return false
  1348  	}
  1349  	replaced := replace(meta.Provides) || replace(meta.Requires) || replace(meta.Peers)
  1350  	c.Assert(replaced, gc.Equals, true, gc.Commentf("charm %q does not implement relation %q", charmPath, oldName))
  1351  
  1352  	newmeta, err := goyaml.Marshal(meta)
  1353  	c.Assert(err, jc.ErrorIsNil)
  1354  	ioutil.WriteFile(path, newmeta, 0644)
  1355  
  1356  	f, err = os.Open(path)
  1357  	c.Assert(err, jc.ErrorIsNil)
  1358  	defer f.Close()
  1359  	_, err = corecharm.ReadMeta(f)
  1360  	c.Assert(err, jc.ErrorIsNil)
  1361  }
  1362  
  1363  func createHookLock(c *gc.C, dataDir string) *fslock.Lock {
  1364  	lockDir := filepath.Join(dataDir, "locks")
  1365  	lock, err := fslock.NewLock(lockDir, "uniter-hook-execution", fslock.Defaults())
  1366  	c.Assert(err, jc.ErrorIsNil)
  1367  	return lock
  1368  }
  1369  
  1370  type acquireHookSyncLock struct {
  1371  	message string
  1372  }
  1373  
  1374  func (s acquireHookSyncLock) step(c *gc.C, ctx *context) {
  1375  	lock := createHookLock(c, ctx.dataDir)
  1376  	c.Assert(lock.IsLocked(), jc.IsFalse)
  1377  	err := lock.Lock(s.message)
  1378  	c.Assert(err, jc.ErrorIsNil)
  1379  }
  1380  
  1381  var releaseHookSyncLock = custom{func(c *gc.C, ctx *context) {
  1382  	lock := createHookLock(c, ctx.dataDir)
  1383  	// Force the release.
  1384  	err := lock.BreakLock()
  1385  	c.Assert(err, jc.ErrorIsNil)
  1386  }}
  1387  
  1388  var verifyHookSyncLockUnlocked = custom{func(c *gc.C, ctx *context) {
  1389  	lock := createHookLock(c, ctx.dataDir)
  1390  	c.Assert(lock.IsLocked(), jc.IsFalse)
  1391  }}
  1392  
  1393  var verifyHookSyncLockLocked = custom{func(c *gc.C, ctx *context) {
  1394  	lock := createHookLock(c, ctx.dataDir)
  1395  	c.Assert(lock.IsLocked(), jc.IsTrue)
  1396  }}
  1397  
  1398  type setProxySettings proxy.Settings
  1399  
  1400  func (s setProxySettings) step(c *gc.C, ctx *context) {
  1401  	attrs := map[string]interface{}{
  1402  		"http-proxy":  s.Http,
  1403  		"https-proxy": s.Https,
  1404  		"ftp-proxy":   s.Ftp,
  1405  		"no-proxy":    s.NoProxy,
  1406  	}
  1407  	err := ctx.st.UpdateModelConfig(attrs, nil, nil)
  1408  	c.Assert(err, jc.ErrorIsNil)
  1409  }
  1410  
  1411  type relationRunCommands []string
  1412  
  1413  func (cmds relationRunCommands) step(c *gc.C, ctx *context) {
  1414  	commands := strings.Join(cmds, "\n")
  1415  	args := uniter.RunCommandsArgs{
  1416  		Commands:       commands,
  1417  		RelationId:     0,
  1418  		RemoteUnitName: "",
  1419  	}
  1420  	result, err := ctx.uniter.RunCommands(args)
  1421  	c.Assert(err, jc.ErrorIsNil)
  1422  	c.Check(result.Code, gc.Equals, 0)
  1423  	c.Check(string(result.Stdout), gc.Equals, "")
  1424  	c.Check(string(result.Stderr), gc.Equals, "")
  1425  }
  1426  
  1427  type runCommands []string
  1428  
  1429  func (cmds runCommands) step(c *gc.C, ctx *context) {
  1430  	commands := strings.Join(cmds, "\n")
  1431  	args := uniter.RunCommandsArgs{
  1432  		Commands:       commands,
  1433  		RelationId:     -1,
  1434  		RemoteUnitName: "",
  1435  	}
  1436  	result, err := ctx.uniter.RunCommands(args)
  1437  	c.Assert(err, jc.ErrorIsNil)
  1438  	c.Check(result.Code, gc.Equals, 0)
  1439  	c.Check(string(result.Stdout), gc.Equals, "")
  1440  	c.Check(string(result.Stderr), gc.Equals, "")
  1441  }
  1442  
  1443  type asyncRunCommands []string
  1444  
  1445  func (cmds asyncRunCommands) step(c *gc.C, ctx *context) {
  1446  	commands := strings.Join(cmds, "\n")
  1447  	args := uniter.RunCommandsArgs{
  1448  		Commands:       commands,
  1449  		RelationId:     -1,
  1450  		RemoteUnitName: "",
  1451  	}
  1452  
  1453  	var socketPath string
  1454  	if runtime.GOOS == "windows" {
  1455  		socketPath = `\\.\pipe\unit-u-0-run`
  1456  	} else {
  1457  		socketPath = filepath.Join(ctx.path, "run.socket")
  1458  	}
  1459  
  1460  	ctx.wg.Add(1)
  1461  	go func() {
  1462  		defer ctx.wg.Done()
  1463  		// make sure the socket exists
  1464  		client, err := sockets.Dial(socketPath)
  1465  		// Don't use asserts in go routines.
  1466  		if !c.Check(err, jc.ErrorIsNil) {
  1467  			return
  1468  		}
  1469  		defer client.Close()
  1470  
  1471  		var result utilexec.ExecResponse
  1472  		err = client.Call(uniter.JujuRunEndpoint, args, &result)
  1473  		if c.Check(err, jc.ErrorIsNil) {
  1474  			c.Check(result.Code, gc.Equals, 0)
  1475  			c.Check(string(result.Stdout), gc.Equals, "")
  1476  			c.Check(string(result.Stderr), gc.Equals, "")
  1477  		}
  1478  	}()
  1479  }
  1480  
  1481  type waitContextWaitGroup struct{}
  1482  
  1483  func (waitContextWaitGroup) step(c *gc.C, ctx *context) {
  1484  	ctx.wg.Wait()
  1485  }
  1486  
  1487  type forceMinion struct{}
  1488  
  1489  func (forceMinion) step(c *gc.C, ctx *context) {
  1490  	ctx.leaderTracker.setLeader(c, false)
  1491  }
  1492  
  1493  type forceLeader struct{}
  1494  
  1495  func (forceLeader) step(c *gc.C, ctx *context) {
  1496  	ctx.leaderTracker.setLeader(c, true)
  1497  }
  1498  
  1499  func newMockLeaderTracker(ctx *context) *mockLeaderTracker {
  1500  	return &mockLeaderTracker{
  1501  		ctx: ctx,
  1502  	}
  1503  }
  1504  
  1505  type mockLeaderTracker struct {
  1506  	mu       sync.Mutex
  1507  	ctx      *context
  1508  	isLeader bool
  1509  	waiting  []chan struct{}
  1510  }
  1511  
  1512  func (mock *mockLeaderTracker) ServiceName() string {
  1513  	return mock.ctx.svc.Name()
  1514  }
  1515  
  1516  func (mock *mockLeaderTracker) ClaimDuration() time.Duration {
  1517  	return 30 * time.Second
  1518  }
  1519  
  1520  func (mock *mockLeaderTracker) ClaimLeader() leadership.Ticket {
  1521  	mock.mu.Lock()
  1522  	defer mock.mu.Unlock()
  1523  	if mock.isLeader {
  1524  		return fastTicket{true}
  1525  	}
  1526  	return fastTicket{}
  1527  }
  1528  
  1529  func (mock *mockLeaderTracker) WaitLeader() leadership.Ticket {
  1530  	mock.mu.Lock()
  1531  	defer mock.mu.Unlock()
  1532  	if mock.isLeader {
  1533  		return fastTicket{}
  1534  	}
  1535  	return mock.waitTicket()
  1536  }
  1537  
  1538  func (mock *mockLeaderTracker) WaitMinion() leadership.Ticket {
  1539  	mock.mu.Lock()
  1540  	defer mock.mu.Unlock()
  1541  	if !mock.isLeader {
  1542  		return fastTicket{}
  1543  	}
  1544  	return mock.waitTicket()
  1545  }
  1546  
  1547  func (mock *mockLeaderTracker) waitTicket() leadership.Ticket {
  1548  	// very internal, expects mu to be locked already
  1549  	ch := make(chan struct{})
  1550  	mock.waiting = append(mock.waiting, ch)
  1551  	return waitTicket{ch}
  1552  }
  1553  
  1554  func (mock *mockLeaderTracker) setLeader(c *gc.C, isLeader bool) {
  1555  	mock.mu.Lock()
  1556  	defer mock.mu.Unlock()
  1557  	if mock.isLeader == isLeader {
  1558  		return
  1559  	}
  1560  	if isLeader {
  1561  		err := mock.ctx.leaderClaimer.ClaimLeadership(
  1562  			mock.ctx.svc.Name(), mock.ctx.unit.Name(), time.Minute,
  1563  		)
  1564  		c.Assert(err, jc.ErrorIsNil)
  1565  	} else {
  1566  		leaseClock.Advance(61 * time.Second)
  1567  		time.Sleep(coretesting.ShortWait)
  1568  	}
  1569  	mock.isLeader = isLeader
  1570  	for _, ch := range mock.waiting {
  1571  		close(ch)
  1572  	}
  1573  	mock.waiting = nil
  1574  }
  1575  
  1576  type waitTicket struct {
  1577  	ch chan struct{}
  1578  }
  1579  
  1580  func (t waitTicket) Ready() <-chan struct{} {
  1581  	return t.ch
  1582  }
  1583  
  1584  func (t waitTicket) Wait() bool {
  1585  	return false
  1586  }
  1587  
  1588  type fastTicket struct {
  1589  	value bool
  1590  }
  1591  
  1592  func (fastTicket) Ready() <-chan struct{} {
  1593  	ch := make(chan struct{})
  1594  	close(ch)
  1595  	return ch
  1596  }
  1597  
  1598  func (t fastTicket) Wait() bool {
  1599  	return t.value
  1600  }
  1601  
  1602  type setLeaderSettings map[string]string
  1603  
  1604  func (s setLeaderSettings) step(c *gc.C, ctx *context) {
  1605  	// We do this directly on State, not the API, so we don't have to worry
  1606  	// about getting an API conn for whatever unit's meant to be leader.
  1607  	err := ctx.svc.UpdateLeaderSettings(successToken{}, s)
  1608  	c.Assert(err, jc.ErrorIsNil)
  1609  	ctx.s.BackingState.StartSync()
  1610  }
  1611  
  1612  type successToken struct{}
  1613  
  1614  func (successToken) Check(interface{}) error {
  1615  	return nil
  1616  }
  1617  
  1618  type verifyLeaderSettings map[string]string
  1619  
  1620  func (verify verifyLeaderSettings) step(c *gc.C, ctx *context) {
  1621  	actual, err := ctx.svc.LeaderSettings()
  1622  	c.Assert(err, jc.ErrorIsNil)
  1623  	c.Assert(actual, jc.DeepEquals, map[string]string(verify))
  1624  }
  1625  
  1626  type verifyFile struct {
  1627  	filename string
  1628  	content  string
  1629  }
  1630  
  1631  func (verify verifyFile) fileExists() bool {
  1632  	_, err := os.Stat(verify.filename)
  1633  	return err == nil
  1634  }
  1635  
  1636  func (verify verifyFile) checkContent(c *gc.C) {
  1637  	content, err := ioutil.ReadFile(verify.filename)
  1638  	c.Assert(err, jc.ErrorIsNil)
  1639  	c.Assert(string(content), gc.Equals, verify.content)
  1640  }
  1641  
  1642  func (verify verifyFile) step(c *gc.C, ctx *context) {
  1643  	if verify.fileExists() {
  1644  		verify.checkContent(c)
  1645  		return
  1646  	}
  1647  	c.Logf("waiting for file: %s", verify.filename)
  1648  	timeout := time.After(worstCase)
  1649  	for {
  1650  		select {
  1651  		case <-time.After(coretesting.ShortWait):
  1652  			if verify.fileExists() {
  1653  				verify.checkContent(c)
  1654  				return
  1655  			}
  1656  		case <-timeout:
  1657  			c.Fatalf("file does not exist")
  1658  		}
  1659  	}
  1660  }
  1661  
  1662  // verify that the file does not exist
  1663  type verifyNoFile struct {
  1664  	filename string
  1665  }
  1666  
  1667  func (verify verifyNoFile) step(c *gc.C, ctx *context) {
  1668  	c.Assert(verify.filename, jc.DoesNotExist)
  1669  	// Wait a short time and check again.
  1670  	time.Sleep(coretesting.ShortWait)
  1671  	c.Assert(verify.filename, jc.DoesNotExist)
  1672  }
  1673  
  1674  type mockCharmDirGuard struct{}
  1675  
  1676  // Unlock implements fortress.Guard.
  1677  func (*mockCharmDirGuard) Unlock() error { return nil }
  1678  
  1679  // Lockdown implements fortress.Guard.
  1680  func (*mockCharmDirGuard) Lockdown(_ fortress.Abort) error { return nil }
  1681  
  1682  // prepareGitUniter runs a sequence of uniter tests with the manifest deployer
  1683  // replacement logic patched out, simulating the effect of running an older
  1684  // version of juju that exclusively used a git deployer. This is useful both
  1685  // for testing the new deployer-replacement code *and* for running the old
  1686  // tests against the new, patched code to check that the tweaks made to
  1687  // accommodate the manifest deployer do not change the original behaviour as
  1688  // simulated by the patched-out code.
  1689  type prepareGitUniter struct {
  1690  	prepSteps []stepper
  1691  }
  1692  
  1693  func (s prepareGitUniter) step(c *gc.C, ctx *context) {
  1694  	c.Assert(ctx.uniter, gc.IsNil, gc.Commentf("please don't try to patch stuff while the uniter's running"))
  1695  	newDeployer := func(charmPath, dataPath string, bundles charm.BundleReader) (charm.Deployer, error) {
  1696  		return charm.NewGitDeployer(charmPath, dataPath, bundles), nil
  1697  	}
  1698  	restoreNewDeployer := gt.PatchValue(&charm.NewDeployer, newDeployer)
  1699  	defer restoreNewDeployer()
  1700  
  1701  	fixDeployer := func(deployer *charm.Deployer) error {
  1702  		return nil
  1703  	}
  1704  	restoreFixDeployer := gt.PatchValue(&charm.FixDeployer, fixDeployer)
  1705  	defer restoreFixDeployer()
  1706  
  1707  	for _, prepStep := range s.prepSteps {
  1708  		step(c, ctx, prepStep)
  1709  	}
  1710  	if ctx.uniter != nil {
  1711  		step(c, ctx, stopUniter{})
  1712  	}
  1713  }
  1714  
  1715  func ugt(summary string, steps ...stepper) uniterTest {
  1716  	return ut(summary, prepareGitUniter{steps})
  1717  }
  1718  
  1719  type verifyGitCharm struct {
  1720  	revision int
  1721  	dirty    bool
  1722  }
  1723  
  1724  func (s verifyGitCharm) step(c *gc.C, ctx *context) {
  1725  	charmPath := filepath.Join(ctx.path, "charm")
  1726  	if !s.dirty {
  1727  		revisionPath := filepath.Join(charmPath, "revision")
  1728  		content, err := ioutil.ReadFile(revisionPath)
  1729  		c.Assert(err, jc.ErrorIsNil)
  1730  		c.Assert(string(content), gc.Equals, strconv.Itoa(s.revision))
  1731  		err = ctx.unit.Refresh()
  1732  		c.Assert(err, jc.ErrorIsNil)
  1733  		url, ok := ctx.unit.CharmURL()
  1734  		c.Assert(ok, jc.IsTrue)
  1735  		c.Assert(url, gc.DeepEquals, curl(s.revision))
  1736  	}
  1737  
  1738  	// Before we try to check the git status, make sure expected hooks are all
  1739  	// complete, to prevent the test and the uniter interfering with each other.
  1740  	step(c, ctx, waitHooks{})
  1741  	step(c, ctx, waitHooks{})
  1742  	cmd := exec.Command("git", "status")
  1743  	cmd.Dir = filepath.Join(ctx.path, "charm")
  1744  	out, err := cmd.CombinedOutput()
  1745  	c.Assert(err, jc.ErrorIsNil)
  1746  	cmp := gc.Matches
  1747  	if s.dirty {
  1748  		cmp = gc.Not(gc.Matches)
  1749  	}
  1750  	c.Assert(string(out), cmp, "(# )?On branch master\nnothing to commit.*\n")
  1751  }
  1752  
  1753  type startGitUpgradeError struct{}
  1754  
  1755  func (s startGitUpgradeError) step(c *gc.C, ctx *context) {
  1756  	steps := []stepper{
  1757  		createCharm{
  1758  			customize: func(c *gc.C, ctx *context, path string) {
  1759  				appendHook(c, path, "start", "echo STARTDATA > data")
  1760  			},
  1761  		},
  1762  		serveCharm{},
  1763  		createUniter{},
  1764  		waitUnitAgent{
  1765  			status: status.StatusIdle,
  1766  		},
  1767  		waitHooks(startupHooks(false)),
  1768  		verifyGitCharm{dirty: true},
  1769  
  1770  		createCharm{
  1771  			revision: 1,
  1772  			customize: func(c *gc.C, ctx *context, path string) {
  1773  				ft.File{"data", "<nelson>ha ha</nelson>", 0644}.Create(c, path)
  1774  				ft.File{"ignore", "anything", 0644}.Create(c, path)
  1775  			},
  1776  		},
  1777  		serveCharm{},
  1778  		upgradeCharm{revision: 1},
  1779  		waitUnitAgent{
  1780  			statusGetter: unitStatusGetter,
  1781  			status:       status.StatusError,
  1782  			info:         "upgrade failed",
  1783  			charm:        1,
  1784  		},
  1785  		verifyWaiting{},
  1786  		verifyGitCharm{dirty: true},
  1787  	}
  1788  	for _, s_ := range steps {
  1789  		step(c, ctx, s_)
  1790  	}
  1791  }
  1792  
  1793  type provisionStorage struct{}
  1794  
  1795  func (s provisionStorage) step(c *gc.C, ctx *context) {
  1796  	storageAttachments, err := ctx.st.UnitStorageAttachments(ctx.unit.UnitTag())
  1797  	c.Assert(err, jc.ErrorIsNil)
  1798  	c.Assert(storageAttachments, gc.HasLen, 1)
  1799  
  1800  	filesystem, err := ctx.st.StorageInstanceFilesystem(storageAttachments[0].StorageInstance())
  1801  	c.Assert(err, jc.ErrorIsNil)
  1802  
  1803  	filesystemInfo := state.FilesystemInfo{
  1804  		Size:         1024,
  1805  		FilesystemId: "fs-id",
  1806  	}
  1807  	err = ctx.st.SetFilesystemInfo(filesystem.FilesystemTag(), filesystemInfo)
  1808  	c.Assert(err, jc.ErrorIsNil)
  1809  
  1810  	machineId, err := ctx.unit.AssignedMachineId()
  1811  	c.Assert(err, jc.ErrorIsNil)
  1812  
  1813  	filesystemAttachmentInfo := state.FilesystemAttachmentInfo{
  1814  		MountPoint: "/srv/wordpress/content",
  1815  	}
  1816  	err = ctx.st.SetFilesystemAttachmentInfo(
  1817  		names.NewMachineTag(machineId),
  1818  		filesystem.FilesystemTag(),
  1819  		filesystemAttachmentInfo,
  1820  	)
  1821  	c.Assert(err, jc.ErrorIsNil)
  1822  }
  1823  
  1824  type destroyStorageAttachment struct{}
  1825  
  1826  func (s destroyStorageAttachment) step(c *gc.C, ctx *context) {
  1827  	storageAttachments, err := ctx.st.UnitStorageAttachments(ctx.unit.UnitTag())
  1828  	c.Assert(err, jc.ErrorIsNil)
  1829  	c.Assert(storageAttachments, gc.HasLen, 1)
  1830  	err = ctx.st.DestroyStorageAttachment(
  1831  		storageAttachments[0].StorageInstance(),
  1832  		ctx.unit.UnitTag(),
  1833  	)
  1834  	c.Assert(err, jc.ErrorIsNil)
  1835  }
  1836  
  1837  type verifyStorageDetached struct{}
  1838  
  1839  func (s verifyStorageDetached) step(c *gc.C, ctx *context) {
  1840  	storageAttachments, err := ctx.st.UnitStorageAttachments(ctx.unit.UnitTag())
  1841  	c.Assert(err, jc.ErrorIsNil)
  1842  	c.Assert(storageAttachments, gc.HasLen, 0)
  1843  }
  1844  
  1845  type expectError struct {
  1846  	err string
  1847  }
  1848  
  1849  func (s expectError) step(c *gc.C, ctx *context) {
  1850  	ctx.setExpectedError(s.err)
  1851  }
  1852  
  1853  // manualTicker will be used to generate collect-metrics events
  1854  // in a time-independent manner for testing.
  1855  type manualTicker struct {
  1856  	c chan time.Time
  1857  }
  1858  
  1859  // Tick sends a signal on the ticker channel.
  1860  func (t *manualTicker) Tick() error {
  1861  	select {
  1862  	case t.c <- time.Now():
  1863  	case <-time.After(worstCase):
  1864  		return fmt.Errorf("ticker channel blocked")
  1865  	}
  1866  	return nil
  1867  }
  1868  
  1869  // ReturnTimer can be used to replace the update status signal generator.
  1870  func (t *manualTicker) ReturnTimer() <-chan time.Time {
  1871  	return t.c
  1872  }
  1873  
  1874  func newManualTicker() *manualTicker {
  1875  	return &manualTicker{
  1876  		c: make(chan time.Time),
  1877  	}
  1878  }