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