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