github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/worker/uniter/util_test.go (about)

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