launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/worker/uniter/uniter_test.go (about)

     1  // Copyright 2012, 2013 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  	"net/url"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"strconv"
    16  	"strings"
    17  	stdtesting "testing"
    18  	"time"
    19  
    20  	errgo "launchpad.net/errgo/errors"
    21  	gc "launchpad.net/gocheck"
    22  	"launchpad.net/goyaml"
    23  
    24  	"launchpad.net/juju-core/agent/tools"
    25  	"launchpad.net/juju-core/charm"
    26  	"launchpad.net/juju-core/errors"
    27  	"launchpad.net/juju-core/juju/osenv"
    28  	"launchpad.net/juju-core/juju/testing"
    29  	"launchpad.net/juju-core/state"
    30  	"launchpad.net/juju-core/state/api"
    31  	"launchpad.net/juju-core/state/api/params"
    32  	apiuniter "launchpad.net/juju-core/state/api/uniter"
    33  	coretesting "launchpad.net/juju-core/testing"
    34  	jc "launchpad.net/juju-core/testing/checkers"
    35  	"launchpad.net/juju-core/utils"
    36  	utilexec "launchpad.net/juju-core/utils/exec"
    37  	"launchpad.net/juju-core/utils/fslock"
    38  	"launchpad.net/juju-core/worker"
    39  	"launchpad.net/juju-core/worker/uniter"
    40  )
    41  
    42  // worstCase is used for timeouts when timing out
    43  // will fail the test. Raising this value should
    44  // not affect the overall running time of the tests
    45  // unless they fail.
    46  const worstCase = coretesting.LongWait
    47  
    48  func TestPackage(t *stdtesting.T) {
    49  	coretesting.MgoTestPackage(t)
    50  }
    51  
    52  type UniterSuite struct {
    53  	coretesting.GitSuite
    54  	testing.JujuConnSuite
    55  	coretesting.HTTPSuite
    56  	dataDir  string
    57  	oldLcAll string
    58  	unitDir  string
    59  
    60  	st     *api.State
    61  	uniter *apiuniter.State
    62  }
    63  
    64  var _ = gc.Suite(&UniterSuite{})
    65  
    66  func (s *UniterSuite) SetUpSuite(c *gc.C) {
    67  	s.JujuConnSuite.SetUpSuite(c)
    68  	s.HTTPSuite.SetUpSuite(c)
    69  	s.dataDir = c.MkDir()
    70  	toolsDir := tools.ToolsDir(s.dataDir, "unit-u-0")
    71  	err := os.MkdirAll(toolsDir, 0755)
    72  	c.Assert(err, gc.IsNil)
    73  	cmd := exec.Command("go", "build", "launchpad.net/juju-core/cmd/jujud")
    74  	cmd.Dir = toolsDir
    75  	out, err := cmd.CombinedOutput()
    76  	c.Logf(string(out))
    77  	c.Assert(err, gc.IsNil)
    78  	s.oldLcAll = os.Getenv("LC_ALL")
    79  	os.Setenv("LC_ALL", "en_US")
    80  	s.unitDir = filepath.Join(s.dataDir, "agents", "unit-u-0")
    81  }
    82  
    83  func (s *UniterSuite) TearDownSuite(c *gc.C) {
    84  	os.Setenv("LC_ALL", s.oldLcAll)
    85  	s.HTTPSuite.TearDownSuite(c)
    86  	s.JujuConnSuite.TearDownSuite(c)
    87  }
    88  
    89  func (s *UniterSuite) SetUpTest(c *gc.C) {
    90  	s.GitSuite.SetUpTest(c)
    91  	s.JujuConnSuite.SetUpTest(c)
    92  	s.HTTPSuite.SetUpTest(c)
    93  }
    94  
    95  func (s *UniterSuite) TearDownTest(c *gc.C) {
    96  	s.ResetContext(c)
    97  	s.HTTPSuite.TearDownTest(c)
    98  	s.JujuConnSuite.TearDownTest(c)
    99  	s.GitSuite.TearDownTest(c)
   100  }
   101  
   102  func (s *UniterSuite) Reset(c *gc.C) {
   103  	s.JujuConnSuite.Reset(c)
   104  	s.ResetContext(c)
   105  }
   106  
   107  func (s *UniterSuite) ResetContext(c *gc.C) {
   108  	coretesting.Server.Flush()
   109  	err := os.RemoveAll(s.unitDir)
   110  	c.Assert(err, gc.IsNil)
   111  }
   112  
   113  func (s *UniterSuite) APILogin(c *gc.C, unit *state.Unit) {
   114  	password, err := utils.RandomPassword()
   115  	c.Assert(err, gc.IsNil)
   116  	err = unit.SetPassword(password)
   117  	c.Assert(err, gc.IsNil)
   118  	s.st = s.OpenAPIAs(c, unit.Tag(), password)
   119  	c.Assert(s.st, gc.NotNil)
   120  	c.Logf("API: login as %q successful", unit.Tag())
   121  	s.uniter = s.st.Uniter()
   122  	c.Assert(s.uniter, gc.NotNil)
   123  }
   124  
   125  var _ worker.Worker = (*uniter.Uniter)(nil)
   126  
   127  type uniterTest struct {
   128  	summary string
   129  	steps   []stepper
   130  }
   131  
   132  func ut(summary string, steps ...stepper) uniterTest {
   133  	return uniterTest{summary, steps}
   134  }
   135  
   136  type stepper interface {
   137  	step(c *gc.C, ctx *context)
   138  }
   139  
   140  type context struct {
   141  	uuid          string
   142  	path          string
   143  	dataDir       string
   144  	s             *UniterSuite
   145  	st            *state.State
   146  	charms        coretesting.ResponseMap
   147  	hooks         []string
   148  	sch           *state.Charm
   149  	svc           *state.Service
   150  	unit          *state.Unit
   151  	uniter        *uniter.Uniter
   152  	relatedSvc    *state.Service
   153  	relation      *state.Relation
   154  	relationUnits map[string]*state.RelationUnit
   155  	subordinate   *state.Unit
   156  
   157  	hooksCompleted []string
   158  }
   159  
   160  var _ uniter.UniterExecutionObserver = (*context)(nil)
   161  
   162  func (ctx *context) HookCompleted(hookName string) {
   163  	ctx.hooksCompleted = append(ctx.hooksCompleted, hookName)
   164  }
   165  
   166  func (ctx *context) HookFailed(hookName string) {
   167  	ctx.hooksCompleted = append(ctx.hooksCompleted, "fail-"+hookName)
   168  }
   169  
   170  func (ctx *context) run(c *gc.C, steps []stepper) {
   171  	defer func() {
   172  		if ctx.uniter != nil {
   173  			err := ctx.uniter.Stop()
   174  			c.Assert(err, gc.IsNil)
   175  		}
   176  	}()
   177  	for i, s := range steps {
   178  		c.Logf("step %d:\n", i)
   179  		step(c, ctx, s)
   180  	}
   181  }
   182  
   183  var goodHook = `
   184  #!/bin/bash --norc
   185  juju-log $JUJU_ENV_UUID %s $JUJU_REMOTE_UNIT
   186  `[1:]
   187  
   188  var badHook = `
   189  #!/bin/bash --norc
   190  juju-log $JUJU_ENV_UUID fail-%s $JUJU_REMOTE_UNIT
   191  exit 1
   192  `[1:]
   193  
   194  func (ctx *context) writeHook(c *gc.C, path string, good bool) {
   195  	hook := badHook
   196  	if good {
   197  		hook = goodHook
   198  	}
   199  	content := fmt.Sprintf(hook, filepath.Base(path))
   200  	err := ioutil.WriteFile(path, []byte(content), 0755)
   201  	c.Assert(err, gc.IsNil)
   202  }
   203  
   204  func (ctx *context) matchHooks(c *gc.C) (match bool, overshoot bool) {
   205  	c.Logf("ctx.hooksCompleted: %#v", ctx.hooksCompleted)
   206  	if len(ctx.hooksCompleted) < len(ctx.hooks) {
   207  		return false, false
   208  	}
   209  	for i, e := range ctx.hooks {
   210  		if ctx.hooksCompleted[i] != e {
   211  			return false, false
   212  		}
   213  	}
   214  	return true, len(ctx.hooksCompleted) > len(ctx.hooks)
   215  }
   216  
   217  var startupTests = []uniterTest{
   218  	// Check conditions that can cause the uniter to fail to start.
   219  	ut(
   220  		"unable to create state dir",
   221  		writeFile{"state", 0644},
   222  		createCharm{},
   223  		createServiceAndUnit{},
   224  		startUniter{},
   225  		waitUniterDead{`failed to initialize uniter for "unit-u-0": .*not a directory`},
   226  	), ut(
   227  		"unknown unit",
   228  		// We still need to create a unit, because that's when we also
   229  		// connect to the API, but here we use a different service
   230  		// (and hence unit) name.
   231  		createCharm{},
   232  		createServiceAndUnit{serviceName: "w"},
   233  		startUniter{"unit-u-0"},
   234  		waitUniterDead{`failed to initialize uniter for "unit-u-0": permission denied`},
   235  	),
   236  }
   237  
   238  func (s *UniterSuite) TestUniterStartup(c *gc.C) {
   239  	s.runUniterTests(c, startupTests)
   240  }
   241  
   242  var bootstrapTests = []uniterTest{
   243  	// Check error conditions during unit bootstrap phase.
   244  	ut(
   245  		"insane deployment",
   246  		createCharm{},
   247  		serveCharm{},
   248  		writeFile{"charm", 0644},
   249  		createUniter{},
   250  		waitUniterDead{`ModeInstalling cs:quantal/wordpress-0: charm deployment failed: ".*charm" is not a directory`},
   251  	), ut(
   252  		"charm cannot be downloaded",
   253  		createCharm{},
   254  		custom{func(c *gc.C, ctx *context) {
   255  			coretesting.Server.Response(404, nil, nil)
   256  		}},
   257  		createUniter{},
   258  		waitUniterDead{`ModeInstalling cs:quantal/wordpress-0: failed to download charm .* 404 Not Found`},
   259  	),
   260  }
   261  
   262  func (s *UniterSuite) TestUniterBootstrap(c *gc.C) {
   263  	s.runUniterTests(c, bootstrapTests)
   264  }
   265  
   266  var installHookTests = []uniterTest{
   267  	ut(
   268  		"install hook fail and resolve",
   269  		startupError{"install"},
   270  		verifyWaiting{},
   271  
   272  		resolveError{state.ResolvedNoHooks},
   273  		waitUnit{
   274  			status: params.StatusStarted,
   275  		},
   276  		waitHooks{"config-changed", "start"},
   277  	), ut(
   278  		"install hook fail and retry",
   279  		startupError{"install"},
   280  		verifyWaiting{},
   281  
   282  		resolveError{state.ResolvedRetryHooks},
   283  		waitUnit{
   284  			status: params.StatusError,
   285  			info:   `hook failed: "install"`,
   286  			data: params.StatusData{
   287  				"hook": "install",
   288  			},
   289  		},
   290  		waitHooks{"fail-install"},
   291  		fixHook{"install"},
   292  		verifyWaiting{},
   293  
   294  		resolveError{state.ResolvedRetryHooks},
   295  		waitUnit{
   296  			status: params.StatusStarted,
   297  		},
   298  		waitHooks{"install", "config-changed", "start"},
   299  	),
   300  }
   301  
   302  func (s *UniterSuite) TestUniterInstallHook(c *gc.C) {
   303  	s.runUniterTests(c, installHookTests)
   304  }
   305  
   306  var startHookTests = []uniterTest{
   307  	ut(
   308  		"start hook fail and resolve",
   309  		startupError{"start"},
   310  		verifyWaiting{},
   311  
   312  		resolveError{state.ResolvedNoHooks},
   313  		waitUnit{
   314  			status: params.StatusStarted,
   315  		},
   316  		waitHooks{"config-changed"},
   317  		verifyRunning{},
   318  	), ut(
   319  		"start hook fail and retry",
   320  		startupError{"start"},
   321  		verifyWaiting{},
   322  
   323  		resolveError{state.ResolvedRetryHooks},
   324  		waitUnit{
   325  			status: params.StatusError,
   326  			info:   `hook failed: "start"`,
   327  			data: params.StatusData{
   328  				"hook": "start",
   329  			},
   330  		},
   331  		waitHooks{"fail-start"},
   332  		verifyWaiting{},
   333  
   334  		fixHook{"start"},
   335  		resolveError{state.ResolvedRetryHooks},
   336  		waitUnit{
   337  			status: params.StatusStarted,
   338  		},
   339  		waitHooks{"start", "config-changed"},
   340  		verifyRunning{},
   341  	),
   342  }
   343  
   344  func (s *UniterSuite) TestUniterStartHook(c *gc.C) {
   345  	s.runUniterTests(c, startHookTests)
   346  }
   347  
   348  var multipleErrorsTests = []uniterTest{
   349  	ut(
   350  		"resolved is cleared before moving on to next hook",
   351  		createCharm{badHooks: []string{"install", "config-changed", "start"}},
   352  		serveCharm{},
   353  		createUniter{},
   354  		waitUnit{
   355  			status: params.StatusError,
   356  			info:   `hook failed: "install"`,
   357  			data: params.StatusData{
   358  				"hook": "install",
   359  			},
   360  		},
   361  		resolveError{state.ResolvedNoHooks},
   362  		waitUnit{
   363  			status: params.StatusError,
   364  			info:   `hook failed: "config-changed"`,
   365  			data: params.StatusData{
   366  				"hook": "config-changed",
   367  			},
   368  		},
   369  		resolveError{state.ResolvedNoHooks},
   370  		waitUnit{
   371  			status: params.StatusError,
   372  			info:   `hook failed: "start"`,
   373  			data: params.StatusData{
   374  				"hook": "start",
   375  			},
   376  		},
   377  	),
   378  }
   379  
   380  func (s *UniterSuite) TestUniterMultipleErrors(c *gc.C) {
   381  	s.runUniterTests(c, multipleErrorsTests)
   382  }
   383  
   384  var configChangedHookTests = []uniterTest{
   385  	ut(
   386  		"config-changed hook fail and resolve",
   387  		startupError{"config-changed"},
   388  		verifyWaiting{},
   389  
   390  		// Note: we'll run another config-changed as soon as we hit the
   391  		// started state, so the broken hook would actually prevent us
   392  		// from advancing at all if we didn't fix it.
   393  		fixHook{"config-changed"},
   394  		resolveError{state.ResolvedNoHooks},
   395  		waitUnit{
   396  			status: params.StatusStarted,
   397  		},
   398  		waitHooks{"start", "config-changed"},
   399  		// If we'd accidentally retried that hook, somehow, we would get
   400  		// an extra config-changed as we entered started; see that we don't.
   401  		waitHooks{},
   402  		verifyRunning{},
   403  	), ut(
   404  		"config-changed hook fail and retry",
   405  		startupError{"config-changed"},
   406  		verifyWaiting{},
   407  
   408  		resolveError{state.ResolvedRetryHooks},
   409  		waitUnit{
   410  			status: params.StatusError,
   411  			info:   `hook failed: "config-changed"`,
   412  			data: params.StatusData{
   413  				"hook": "config-changed",
   414  			},
   415  		},
   416  		waitHooks{"fail-config-changed"},
   417  		verifyWaiting{},
   418  
   419  		fixHook{"config-changed"},
   420  		resolveError{state.ResolvedRetryHooks},
   421  		waitUnit{
   422  			status: params.StatusStarted,
   423  		},
   424  		waitHooks{"config-changed", "start"},
   425  		verifyRunning{},
   426  	),
   427  	ut(
   428  		"steady state config change with config-get verification",
   429  		createCharm{
   430  			customize: func(c *gc.C, ctx *context, path string) {
   431  				appendHook(c, path, "config-changed", "config-get --format yaml --output config.out")
   432  			},
   433  		},
   434  		serveCharm{},
   435  		createUniter{},
   436  		waitUnit{
   437  			status: params.StatusStarted,
   438  		},
   439  		waitHooks{"install", "config-changed", "start"},
   440  		assertYaml{"charm/config.out", map[string]interface{}{
   441  			"blog-title": "My Title",
   442  		}},
   443  		changeConfig{"blog-title": "Goodness Gracious Me"},
   444  		waitHooks{"config-changed"},
   445  		verifyRunning{},
   446  		assertYaml{"charm/config.out", map[string]interface{}{
   447  			"blog-title": "Goodness Gracious Me",
   448  		}},
   449  	)}
   450  
   451  func (s *UniterSuite) TestUniterConfigChangedHook(c *gc.C) {
   452  	s.runUniterTests(c, configChangedHookTests)
   453  }
   454  
   455  var hookSynchronizationTests = []uniterTest{
   456  	ut(
   457  		"verify config change hook not run while lock held",
   458  		quickStart{},
   459  		acquireHookSyncLock{},
   460  		changeConfig{"blog-title": "Goodness Gracious Me"},
   461  		waitHooks{},
   462  		releaseHookSyncLock,
   463  		waitHooks{"config-changed"},
   464  	),
   465  	ut(
   466  		"verify held lock by this unit is broken",
   467  		acquireHookSyncLock{"u/0:fake"},
   468  		quickStart{},
   469  		verifyHookSyncLockUnlocked,
   470  	),
   471  	ut(
   472  		"verify held lock by another unit is not broken",
   473  		acquireHookSyncLock{"u/1:fake"},
   474  		// Can't use quickstart as it has a built in waitHooks.
   475  		createCharm{},
   476  		serveCharm{},
   477  		ensureStateWorker{},
   478  		createServiceAndUnit{},
   479  		startUniter{},
   480  		waitAddresses{},
   481  		waitHooks{},
   482  		verifyHookSyncLockLocked,
   483  		releaseHookSyncLock,
   484  		waitUnit{status: params.StatusStarted},
   485  		waitHooks{"install", "config-changed", "start"},
   486  	),
   487  }
   488  
   489  func (s *UniterSuite) TestUniterHookSynchronisation(c *gc.C) {
   490  	s.runUniterTests(c, hookSynchronizationTests)
   491  }
   492  
   493  var dyingReactionTests = []uniterTest{
   494  	// Reaction to entity deaths.
   495  	ut(
   496  		"steady state service dying",
   497  		quickStart{},
   498  		serviceDying,
   499  		waitHooks{"stop"},
   500  		waitUniterDead{},
   501  	), ut(
   502  		"steady state unit dying",
   503  		quickStart{},
   504  		unitDying,
   505  		waitHooks{"stop"},
   506  		waitUniterDead{},
   507  	), ut(
   508  		"steady state unit dead",
   509  		quickStart{},
   510  		unitDead,
   511  		waitUniterDead{},
   512  		waitHooks{},
   513  	), ut(
   514  		"hook error service dying",
   515  		startupError{"start"},
   516  		serviceDying,
   517  		verifyWaiting{},
   518  		fixHook{"start"},
   519  		resolveError{state.ResolvedRetryHooks},
   520  		waitHooks{"start", "config-changed", "stop"},
   521  		waitUniterDead{},
   522  	), ut(
   523  		"hook error unit dying",
   524  		startupError{"start"},
   525  		unitDying,
   526  		verifyWaiting{},
   527  		fixHook{"start"},
   528  		resolveError{state.ResolvedRetryHooks},
   529  		waitHooks{"start", "config-changed", "stop"},
   530  		waitUniterDead{},
   531  	), ut(
   532  		"hook error unit dead",
   533  		startupError{"start"},
   534  		unitDead,
   535  		waitUniterDead{},
   536  		waitHooks{},
   537  	),
   538  }
   539  
   540  func (s *UniterSuite) TestUniterDyingReaction(c *gc.C) {
   541  	s.runUniterTests(c, dyingReactionTests)
   542  }
   543  
   544  var steadyUpgradeTests = []uniterTest{
   545  	// Upgrade scenarios from steady state.
   546  	ut(
   547  		"steady state upgrade",
   548  		quickStart{},
   549  		createCharm{revision: 1},
   550  		upgradeCharm{revision: 1},
   551  		waitUnit{
   552  			status: params.StatusStarted,
   553  			charm:  1,
   554  		},
   555  		waitHooks{"upgrade-charm", "config-changed"},
   556  		verifyCharm{revision: 1},
   557  		verifyRunning{},
   558  	), ut(
   559  		"steady state forced upgrade (identical behaviour)",
   560  		quickStart{},
   561  		createCharm{revision: 1},
   562  		upgradeCharm{revision: 1, forced: true},
   563  		waitUnit{
   564  			status: params.StatusStarted,
   565  			charm:  1,
   566  		},
   567  		waitHooks{"upgrade-charm", "config-changed"},
   568  		verifyCharm{revision: 1},
   569  		verifyRunning{},
   570  	), ut(
   571  		"steady state upgrade hook fail and resolve",
   572  		quickStart{},
   573  		createCharm{revision: 1, badHooks: []string{"upgrade-charm"}},
   574  		upgradeCharm{revision: 1},
   575  		waitUnit{
   576  			status: params.StatusError,
   577  			info:   `hook failed: "upgrade-charm"`,
   578  			data: params.StatusData{
   579  				"hook": "upgrade-charm",
   580  			},
   581  			charm: 1,
   582  		},
   583  		waitHooks{"fail-upgrade-charm"},
   584  		verifyCharm{revision: 1},
   585  		verifyWaiting{},
   586  
   587  		resolveError{state.ResolvedNoHooks},
   588  		waitUnit{
   589  			status: params.StatusStarted,
   590  			charm:  1,
   591  		},
   592  		waitHooks{"config-changed"},
   593  		verifyRunning{},
   594  	), ut(
   595  		"steady state upgrade hook fail and retry",
   596  		quickStart{},
   597  		createCharm{revision: 1, badHooks: []string{"upgrade-charm"}},
   598  		upgradeCharm{revision: 1},
   599  		waitUnit{
   600  			status: params.StatusError,
   601  			info:   `hook failed: "upgrade-charm"`,
   602  			data: params.StatusData{
   603  				"hook": "upgrade-charm",
   604  			},
   605  			charm: 1,
   606  		},
   607  		waitHooks{"fail-upgrade-charm"},
   608  		verifyCharm{revision: 1},
   609  		verifyWaiting{},
   610  
   611  		resolveError{state.ResolvedRetryHooks},
   612  		waitUnit{
   613  			status: params.StatusError,
   614  			info:   `hook failed: "upgrade-charm"`,
   615  			data: params.StatusData{
   616  				"hook": "upgrade-charm",
   617  			},
   618  			charm: 1,
   619  		},
   620  		waitHooks{"fail-upgrade-charm"},
   621  		verifyWaiting{},
   622  
   623  		fixHook{"upgrade-charm"},
   624  		resolveError{state.ResolvedRetryHooks},
   625  		waitUnit{
   626  			status: params.StatusStarted,
   627  			charm:  1,
   628  		},
   629  		waitHooks{"upgrade-charm", "config-changed"},
   630  		verifyRunning{},
   631  	),
   632  	ut(
   633  		// This test does an add-relation as quickly as possible
   634  		// after an upgrade-charm, in the hope that the scheduler will
   635  		// deliver the events in the wrong order. The observed
   636  		// behaviour should be the same in either case.
   637  		"ignore unknown relations until upgrade is done",
   638  		quickStart{},
   639  		createCharm{
   640  			revision: 2,
   641  			customize: func(c *gc.C, ctx *context, path string) {
   642  				renameRelation(c, path, "db", "db2")
   643  				hpath := filepath.Join(path, "hooks", "db2-relation-joined")
   644  				ctx.writeHook(c, hpath, true)
   645  			},
   646  		},
   647  		serveCharm{},
   648  		upgradeCharm{revision: 2},
   649  		addRelation{},
   650  		addRelationUnit{},
   651  		waitHooks{"upgrade-charm", "config-changed", "db2-relation-joined mysql/0 db2:0"},
   652  		verifyCharm{revision: 2},
   653  	),
   654  }
   655  
   656  func (s *UniterSuite) TestUniterSteadyStateUpgrade(c *gc.C) {
   657  	s.runUniterTests(c, steadyUpgradeTests)
   658  }
   659  
   660  var errorUpgradeTests = []uniterTest{
   661  	// Upgrade scenarios from error state.
   662  	ut(
   663  		"error state unforced upgrade (ignored until started state)",
   664  		startupError{"start"},
   665  		createCharm{revision: 1},
   666  		upgradeCharm{revision: 1},
   667  		waitUnit{
   668  			status: params.StatusError,
   669  			info:   `hook failed: "start"`,
   670  			data: params.StatusData{
   671  				"hook": "start",
   672  			},
   673  		},
   674  		waitHooks{},
   675  		verifyCharm{},
   676  		verifyWaiting{},
   677  
   678  		resolveError{state.ResolvedNoHooks},
   679  		waitUnit{
   680  			status: params.StatusStarted,
   681  			charm:  1,
   682  		},
   683  		waitHooks{"config-changed", "upgrade-charm", "config-changed"},
   684  		verifyCharm{revision: 1},
   685  		verifyRunning{},
   686  	), ut(
   687  		"error state forced upgrade",
   688  		startupError{"start"},
   689  		createCharm{revision: 1},
   690  		upgradeCharm{revision: 1, forced: true},
   691  		// It's not possible to tell directly from state when the upgrade is
   692  		// complete, because the new unit charm URL is set at the upgrade
   693  		// process's point of no return (before actually deploying, but after
   694  		// the charm has been downloaded and verified). However, it's still
   695  		// useful to wait until that point...
   696  		waitUnit{
   697  			status: params.StatusError,
   698  			info:   `hook failed: "start"`,
   699  			data: params.StatusData{
   700  				"hook": "start",
   701  			},
   702  			charm: 1,
   703  		},
   704  		// ...because the uniter *will* complete a started deployment even if
   705  		// we stop it from outside. So, by stopping and starting, we can be
   706  		// sure that the operation has completed and can safely verify that
   707  		// the charm state on disk is as we expect.
   708  		verifyWaiting{},
   709  		verifyCharm{revision: 1},
   710  
   711  		resolveError{state.ResolvedNoHooks},
   712  		waitUnit{
   713  			status: params.StatusStarted,
   714  			charm:  1,
   715  		},
   716  		waitHooks{"config-changed"},
   717  		verifyRunning{},
   718  	),
   719  }
   720  
   721  func (s *UniterSuite) TestUniterErrorStateUpgrade(c *gc.C) {
   722  	s.runUniterTests(c, errorUpgradeTests)
   723  }
   724  
   725  var upgradeConflictsTests = []uniterTest{
   726  	// Upgrade scenarios - handling conflicts.
   727  	ut(
   728  		"upgrade: conflicting files",
   729  		startUpgradeError{},
   730  
   731  		// NOTE: this is just dumbly committing the conflicts, but AFAICT this
   732  		// is the only reasonable solution; if the user tells us it's resolved
   733  		// we have to take their word for it.
   734  		resolveError{state.ResolvedNoHooks},
   735  		waitHooks{"upgrade-charm", "config-changed"},
   736  		waitUnit{
   737  			status: params.StatusStarted,
   738  			charm:  1,
   739  		},
   740  		verifyCharm{revision: 1},
   741  	), ut(
   742  		`upgrade: conflicting directories`,
   743  		createCharm{
   744  			customize: func(c *gc.C, ctx *context, path string) {
   745  				err := os.Mkdir(filepath.Join(path, "data"), 0755)
   746  				c.Assert(err, gc.IsNil)
   747  				appendHook(c, path, "start", "echo DATA > data/newfile")
   748  			},
   749  		},
   750  		serveCharm{},
   751  		createUniter{},
   752  		waitUnit{
   753  			status: params.StatusStarted,
   754  		},
   755  		waitHooks{"install", "config-changed", "start"},
   756  		verifyCharm{},
   757  
   758  		createCharm{
   759  			revision: 1,
   760  			customize: func(c *gc.C, ctx *context, path string) {
   761  				data := filepath.Join(path, "data")
   762  				err := ioutil.WriteFile(data, []byte("<nelson>ha ha</nelson>"), 0644)
   763  				c.Assert(err, gc.IsNil)
   764  			},
   765  		},
   766  		serveCharm{},
   767  		upgradeCharm{revision: 1},
   768  		waitUnit{
   769  			status: params.StatusError,
   770  			info:   "upgrade failed",
   771  			charm:  1,
   772  		},
   773  		verifyWaiting{},
   774  		verifyCharm{dirty: true},
   775  
   776  		resolveError{state.ResolvedNoHooks},
   777  		waitHooks{"upgrade-charm", "config-changed"},
   778  		waitUnit{
   779  			status: params.StatusStarted,
   780  			charm:  1,
   781  		},
   782  		verifyCharm{revision: 1},
   783  	), ut(
   784  		"upgrade conflict resolved with forced upgrade",
   785  		startUpgradeError{},
   786  		createCharm{
   787  			revision: 2,
   788  			customize: func(c *gc.C, ctx *context, path string) {
   789  				otherdata := filepath.Join(path, "otherdata")
   790  				err := ioutil.WriteFile(otherdata, []byte("blah"), 0644)
   791  				c.Assert(err, gc.IsNil)
   792  			},
   793  		},
   794  		serveCharm{},
   795  		upgradeCharm{revision: 2, forced: true},
   796  		waitUnit{
   797  			status: params.StatusStarted,
   798  			charm:  2,
   799  		},
   800  		waitHooks{"upgrade-charm", "config-changed"},
   801  		verifyCharm{revision: 2},
   802  		custom{func(c *gc.C, ctx *context) {
   803  			// otherdata should exist (in v2)
   804  			otherdata, err := ioutil.ReadFile(filepath.Join(ctx.path, "charm", "otherdata"))
   805  			c.Assert(err, gc.IsNil)
   806  			c.Assert(string(otherdata), gc.Equals, "blah")
   807  
   808  			// ignore should not (only in v1)
   809  			_, err = os.Stat(filepath.Join(ctx.path, "charm", "ignore"))
   810  			c.Assert(err, jc.Satisfies, os.IsNotExist)
   811  
   812  			// data should contain what was written in the start hook
   813  			data, err := ioutil.ReadFile(filepath.Join(ctx.path, "charm", "data"))
   814  			c.Assert(err, gc.IsNil)
   815  			c.Assert(string(data), gc.Equals, "STARTDATA\n")
   816  		}},
   817  	), ut(
   818  		"upgrade conflict service dying",
   819  		startUpgradeError{},
   820  		serviceDying,
   821  		verifyWaiting{},
   822  		resolveError{state.ResolvedNoHooks},
   823  		waitHooks{"upgrade-charm", "config-changed", "stop"},
   824  		waitUniterDead{},
   825  	), ut(
   826  		"upgrade conflict unit dying",
   827  		startUpgradeError{},
   828  		unitDying,
   829  		verifyWaiting{},
   830  		resolveError{state.ResolvedNoHooks},
   831  		waitHooks{"upgrade-charm", "config-changed", "stop"},
   832  		waitUniterDead{},
   833  	), ut(
   834  		"upgrade conflict unit dead",
   835  		startUpgradeError{},
   836  		unitDead,
   837  		waitUniterDead{},
   838  		waitHooks{},
   839  	),
   840  }
   841  
   842  func (s *UniterSuite) TestUniterUpgradeConflicts(c *gc.C) {
   843  	s.runUniterTests(c, upgradeConflictsTests)
   844  }
   845  
   846  func (s *UniterSuite) TestRunCommand(c *gc.C) {
   847  	testDir := c.MkDir()
   848  	testFile := func(name string) string {
   849  		return filepath.Join(testDir, name)
   850  	}
   851  	echoUnitNameToFile := func(name string) string {
   852  		return fmt.Sprintf("echo juju run ${JUJU_UNIT_NAME} > %s", filepath.Join(testDir, name))
   853  	}
   854  	tests := []uniterTest{
   855  		ut(
   856  			"run commands: environment",
   857  			quickStart{},
   858  			runCommands{echoUnitNameToFile("run.output")},
   859  			verifyFile{filepath.Join(testDir, "run.output"), "juju run u/0\n"},
   860  		), ut(
   861  			"run commands: jujuc commands",
   862  			quickStartRelation{},
   863  			runCommands{
   864  				fmt.Sprintf("owner-get tag > %s", testFile("jujuc.output")),
   865  				fmt.Sprintf("unit-get private-address >> %s", testFile("jujuc.output")),
   866  				fmt.Sprintf("unit-get public-address >> %s", testFile("jujuc.output")),
   867  			},
   868  			verifyFile{
   869  				testFile("jujuc.output"),
   870  				"user-admin\nprivate.dummy.address.example.com\npublic.dummy.address.example.com\n",
   871  			},
   872  		), ut(
   873  			"run commands: proxy settings set",
   874  			quickStartRelation{},
   875  			setProxySettings{Http: "http", Https: "https", Ftp: "ftp"},
   876  			runCommands{
   877  				fmt.Sprintf("echo $http_proxy > %s", testFile("proxy.output")),
   878  				fmt.Sprintf("echo $HTTP_PROXY >> %s", testFile("proxy.output")),
   879  				fmt.Sprintf("echo $https_proxy >> %s", testFile("proxy.output")),
   880  				fmt.Sprintf("echo $HTTPS_PROXY >> %s", testFile("proxy.output")),
   881  				fmt.Sprintf("echo $ftp_proxy >> %s", testFile("proxy.output")),
   882  				fmt.Sprintf("echo $FTP_PROXY >> %s", testFile("proxy.output")),
   883  			},
   884  			verifyFile{
   885  				testFile("proxy.output"),
   886  				"http\nhttp\nhttps\nhttps\nftp\nftp\n",
   887  			},
   888  		), ut(
   889  			"run commands: async using rpc client",
   890  			quickStart{},
   891  			asyncRunCommands{echoUnitNameToFile("run.output")},
   892  			verifyFile{testFile("run.output"), "juju run u/0\n"},
   893  		), ut(
   894  			"run commands: waits for lock",
   895  			quickStart{},
   896  			acquireHookSyncLock{},
   897  			asyncRunCommands{echoUnitNameToFile("wait.output")},
   898  			verifyNoFile{testFile("wait.output")},
   899  			releaseHookSyncLock,
   900  			verifyFile{testFile("wait.output"), "juju run u/0\n"},
   901  		),
   902  	}
   903  	s.runUniterTests(c, tests)
   904  }
   905  
   906  var relationsTests = []uniterTest{
   907  	// Relations.
   908  	ut(
   909  		"simple joined/changed/departed",
   910  		quickStartRelation{},
   911  		addRelationUnit{},
   912  		waitHooks{"db-relation-joined mysql/1 db:0", "db-relation-changed mysql/1 db:0"},
   913  		changeRelationUnit{"mysql/0"},
   914  		waitHooks{"db-relation-changed mysql/0 db:0"},
   915  		removeRelationUnit{"mysql/1"},
   916  		waitHooks{"db-relation-departed mysql/1 db:0"},
   917  		verifyRunning{},
   918  	), ut(
   919  		"relation becomes dying; unit is not last remaining member",
   920  		quickStartRelation{},
   921  		relationDying,
   922  		waitHooks{"db-relation-departed mysql/0 db:0", "db-relation-broken db:0"},
   923  		verifyRunning{},
   924  		relationState{life: state.Dying},
   925  		removeRelationUnit{"mysql/0"},
   926  		verifyRunning{},
   927  		relationState{removed: true},
   928  		verifyRunning{},
   929  	), ut(
   930  		"relation becomes dying; unit is last remaining member",
   931  		quickStartRelation{},
   932  		removeRelationUnit{"mysql/0"},
   933  		waitHooks{"db-relation-departed mysql/0 db:0"},
   934  		relationDying,
   935  		waitHooks{"db-relation-broken db:0"},
   936  		verifyRunning{},
   937  		relationState{removed: true},
   938  		verifyRunning{},
   939  	), ut(
   940  		"service becomes dying while in a relation",
   941  		quickStartRelation{},
   942  		serviceDying,
   943  		waitHooks{"db-relation-departed mysql/0 db:0", "db-relation-broken db:0", "stop"},
   944  		waitUniterDead{},
   945  		relationState{life: state.Dying},
   946  		removeRelationUnit{"mysql/0"},
   947  		relationState{removed: true},
   948  	), ut(
   949  		"unit becomes dying while in a relation",
   950  		quickStartRelation{},
   951  		unitDying,
   952  		waitHooks{"db-relation-departed mysql/0 db:0", "db-relation-broken db:0", "stop"},
   953  		waitUniterDead{},
   954  		relationState{life: state.Alive},
   955  		removeRelationUnit{"mysql/0"},
   956  		relationState{life: state.Alive},
   957  	), ut(
   958  		"unit becomes dead while in a relation",
   959  		quickStartRelation{},
   960  		unitDead,
   961  		waitUniterDead{},
   962  		waitHooks{},
   963  		// TODO BUG(?): the unit doesn't leave the scope, leaving the relation
   964  		// unkillable without direct intervention. I'm pretty sure it's not a
   965  		// uniter bug -- it should be the responisbility of `juju remove-unit
   966  		// --force` to cause the unit to leave any relation scopes it may be
   967  		// in -- but it's worth noting here all the same.
   968  	),
   969  }
   970  
   971  func (s *UniterSuite) TestUniterRelations(c *gc.C) {
   972  	s.runUniterTests(c, relationsTests)
   973  }
   974  
   975  var relationsErrorTests = []uniterTest{
   976  	ut(
   977  		"hook error during join of a relation",
   978  		startupRelationError{"db-relation-joined"},
   979  		waitUnit{
   980  			status: params.StatusError,
   981  			info:   `hook failed: "db-relation-joined"`,
   982  			data: params.StatusData{
   983  				"hook":        "db-relation-joined",
   984  				"relation-id": 0,
   985  				"remote-unit": "mysql/0",
   986  			},
   987  		},
   988  	), ut(
   989  		"hook error during change of a relation",
   990  		startupRelationError{"db-relation-changed"},
   991  		waitUnit{
   992  			status: params.StatusError,
   993  			info:   `hook failed: "db-relation-changed"`,
   994  			data: params.StatusData{
   995  				"hook":        "db-relation-changed",
   996  				"relation-id": 0,
   997  				"remote-unit": "mysql/0",
   998  			},
   999  		},
  1000  	), ut(
  1001  		"hook error after a unit departed",
  1002  		startupRelationError{"db-relation-departed"},
  1003  		waitHooks{"db-relation-joined mysql/0 db:0", "db-relation-changed mysql/0 db:0"},
  1004  		removeRelationUnit{"mysql/0"},
  1005  		waitUnit{
  1006  			status: params.StatusError,
  1007  			info:   `hook failed: "db-relation-departed"`,
  1008  			data: params.StatusData{
  1009  				"hook":        "db-relation-departed",
  1010  				"relation-id": 0,
  1011  				"remote-unit": "mysql/0",
  1012  			},
  1013  		},
  1014  	),
  1015  	ut(
  1016  		"hook error after a relation died",
  1017  		startupRelationError{"db-relation-broken"},
  1018  		waitHooks{"db-relation-joined mysql/0 db:0", "db-relation-changed mysql/0 db:0"},
  1019  		relationDying,
  1020  		waitUnit{
  1021  			status: params.StatusError,
  1022  			info:   `hook failed: "db-relation-broken"`,
  1023  			data: params.StatusData{
  1024  				"hook":        "db-relation-broken",
  1025  				"relation-id": 0,
  1026  			},
  1027  		},
  1028  	),
  1029  }
  1030  
  1031  func (s *UniterSuite) TestUniterRelationErrors(c *gc.C) {
  1032  	s.runUniterTests(c, relationsErrorTests)
  1033  }
  1034  
  1035  var subordinatesTests = []uniterTest{
  1036  	// Subordinates.
  1037  	ut(
  1038  		"unit becomes dying while subordinates exist",
  1039  		quickStart{},
  1040  		addSubordinateRelation{"juju-info"},
  1041  		waitSubordinateExists{"logging/0"},
  1042  		unitDying,
  1043  		waitSubordinateDying{},
  1044  		waitHooks{"stop"},
  1045  		verifyRunning{true},
  1046  		removeSubordinate{},
  1047  		waitUniterDead{},
  1048  	), ut(
  1049  		"new subordinate becomes necessary while old one is dying",
  1050  		quickStart{},
  1051  		addSubordinateRelation{"juju-info"},
  1052  		waitSubordinateExists{"logging/0"},
  1053  		removeSubordinateRelation{"juju-info"},
  1054  		// The subordinate Uniter would usually set Dying in this situation.
  1055  		subordinateDying,
  1056  		addSubordinateRelation{"logging-dir"},
  1057  		verifyRunning{},
  1058  		removeSubordinate{},
  1059  		waitSubordinateExists{"logging/1"},
  1060  	),
  1061  }
  1062  
  1063  func (s *UniterSuite) TestUniterSubordinates(c *gc.C) {
  1064  	s.runUniterTests(c, subordinatesTests)
  1065  }
  1066  
  1067  func (s *UniterSuite) runUniterTests(c *gc.C, uniterTests []uniterTest) {
  1068  	for i, t := range uniterTests {
  1069  		c.Logf("\ntest %d: %s\n", i, t.summary)
  1070  		func() {
  1071  			defer s.Reset(c)
  1072  			env, err := s.State.Environment()
  1073  			c.Assert(err, gc.IsNil)
  1074  			ctx := &context{
  1075  				s:       s,
  1076  				st:      s.State,
  1077  				uuid:    env.UUID(),
  1078  				path:    s.unitDir,
  1079  				dataDir: s.dataDir,
  1080  				charms:  coretesting.ResponseMap{},
  1081  			}
  1082  			ctx.run(c, t.steps)
  1083  		}()
  1084  	}
  1085  }
  1086  
  1087  func (s *UniterSuite) TestSubordinateDying(c *gc.C) {
  1088  	// Create a test context for later use.
  1089  	ctx := &context{
  1090  		s:       s,
  1091  		st:      s.State,
  1092  		path:    filepath.Join(s.dataDir, "agents", "unit-u-0"),
  1093  		dataDir: s.dataDir,
  1094  		charms:  coretesting.ResponseMap{},
  1095  	}
  1096  
  1097  	testing.AddStateServerMachine(c, ctx.st)
  1098  
  1099  	// Create the subordinate service.
  1100  	dir := coretesting.Charms.ClonedDir(c.MkDir(), "logging")
  1101  	curl, err := charm.ParseURL("cs:quantal/logging")
  1102  	c.Assert(err, gc.IsNil)
  1103  	curl = curl.WithRevision(dir.Revision())
  1104  	step(c, ctx, addCharm{dir, curl})
  1105  	ctx.svc = s.AddTestingService(c, "u", ctx.sch)
  1106  
  1107  	// Create the principal service and add a relation.
  1108  	wps := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
  1109  	wpu, err := wps.AddUnit()
  1110  	c.Assert(err, gc.IsNil)
  1111  	eps, err := s.State.InferEndpoints([]string{"wordpress", "u"})
  1112  	c.Assert(err, gc.IsNil)
  1113  	rel, err := s.State.AddRelation(eps...)
  1114  	c.Assert(err, gc.IsNil)
  1115  
  1116  	// Create the subordinate unit by entering scope as the principal.
  1117  	wpru, err := rel.Unit(wpu)
  1118  	c.Assert(err, gc.IsNil)
  1119  	err = wpru.EnterScope(nil)
  1120  	c.Assert(err, gc.IsNil)
  1121  	ctx.unit, err = s.State.Unit("u/0")
  1122  	c.Assert(err, gc.IsNil)
  1123  
  1124  	s.APILogin(c, ctx.unit)
  1125  
  1126  	// Run the actual test.
  1127  	ctx.run(c, []stepper{
  1128  		serveCharm{},
  1129  		startUniter{},
  1130  		waitAddresses{},
  1131  		custom{func(c *gc.C, ctx *context) {
  1132  			c.Assert(rel.Destroy(), gc.IsNil)
  1133  		}},
  1134  		waitUniterDead{},
  1135  	})
  1136  }
  1137  
  1138  func step(c *gc.C, ctx *context, s stepper) {
  1139  	c.Logf("%#v", s)
  1140  	s.step(c, ctx)
  1141  }
  1142  
  1143  type ensureStateWorker struct {
  1144  }
  1145  
  1146  func (s ensureStateWorker) step(c *gc.C, ctx *context) {
  1147  	addresses, err := ctx.st.Addresses()
  1148  	if err != nil || len(addresses) == 0 {
  1149  		testing.AddStateServerMachine(c, ctx.st)
  1150  	}
  1151  	addresses, err = ctx.st.APIAddresses()
  1152  	c.Assert(err, gc.IsNil)
  1153  	c.Assert(addresses, gc.HasLen, 1)
  1154  }
  1155  
  1156  type createCharm struct {
  1157  	revision  int
  1158  	badHooks  []string
  1159  	customize func(*gc.C, *context, string)
  1160  }
  1161  
  1162  var charmHooks = []string{
  1163  	"install", "start", "config-changed", "upgrade-charm", "stop",
  1164  	"db-relation-joined", "db-relation-changed", "db-relation-departed",
  1165  	"db-relation-broken",
  1166  }
  1167  
  1168  func (s createCharm) step(c *gc.C, ctx *context) {
  1169  	base := coretesting.Charms.ClonedDirPath(c.MkDir(), "wordpress")
  1170  	for _, name := range charmHooks {
  1171  		path := filepath.Join(base, "hooks", name)
  1172  		good := true
  1173  		for _, bad := range s.badHooks {
  1174  			if name == bad {
  1175  				good = false
  1176  			}
  1177  		}
  1178  		ctx.writeHook(c, path, good)
  1179  	}
  1180  	if s.customize != nil {
  1181  		s.customize(c, ctx, base)
  1182  	}
  1183  	dir, err := charm.ReadDir(base)
  1184  	c.Assert(err, gc.IsNil)
  1185  	err = dir.SetDiskRevision(s.revision)
  1186  	c.Assert(err, gc.IsNil)
  1187  	step(c, ctx, addCharm{dir, curl(s.revision)})
  1188  }
  1189  
  1190  type addCharm struct {
  1191  	dir  *charm.Dir
  1192  	curl *charm.URL
  1193  }
  1194  
  1195  func (s addCharm) step(c *gc.C, ctx *context) {
  1196  	var buf bytes.Buffer
  1197  	err := s.dir.BundleTo(&buf)
  1198  	c.Assert(err, gc.IsNil)
  1199  	body := buf.Bytes()
  1200  	hash, _, err := utils.ReadSHA256(&buf)
  1201  	c.Assert(err, gc.IsNil)
  1202  	key := fmt.Sprintf("/charms/%s/%d", s.dir.Meta().Name, s.dir.Revision())
  1203  	hurl, err := url.Parse(coretesting.Server.URL + key)
  1204  	c.Assert(err, gc.IsNil)
  1205  	ctx.charms[key] = coretesting.Response{200, nil, body}
  1206  	ctx.sch, err = ctx.st.AddCharm(s.dir, s.curl, hurl, hash)
  1207  	c.Assert(err, gc.IsNil)
  1208  }
  1209  
  1210  type serveCharm struct{}
  1211  
  1212  func (serveCharm) step(c *gc.C, ctx *context) {
  1213  	coretesting.Server.ResponseMap(1, ctx.charms)
  1214  }
  1215  
  1216  type createServiceAndUnit struct {
  1217  	serviceName string
  1218  }
  1219  
  1220  func (csau createServiceAndUnit) step(c *gc.C, ctx *context) {
  1221  	if csau.serviceName == "" {
  1222  		csau.serviceName = "u"
  1223  	}
  1224  	sch, err := ctx.st.Charm(curl(0))
  1225  	c.Assert(err, gc.IsNil)
  1226  	svc := ctx.s.AddTestingService(c, csau.serviceName, sch)
  1227  	unit, err := svc.AddUnit()
  1228  	c.Assert(err, gc.IsNil)
  1229  
  1230  	// Assign the unit to a provisioned machine to match expected state.
  1231  	err = unit.AssignToNewMachine()
  1232  	c.Assert(err, gc.IsNil)
  1233  	mid, err := unit.AssignedMachineId()
  1234  	c.Assert(err, gc.IsNil)
  1235  	machine, err := ctx.st.Machine(mid)
  1236  	c.Assert(err, gc.IsNil)
  1237  	err = machine.SetProvisioned("i-exist", "fake_nonce", nil)
  1238  	c.Assert(err, gc.IsNil)
  1239  	ctx.svc = svc
  1240  	ctx.unit = unit
  1241  
  1242  	ctx.s.APILogin(c, unit)
  1243  }
  1244  
  1245  type createUniter struct{}
  1246  
  1247  func (createUniter) step(c *gc.C, ctx *context) {
  1248  	step(c, ctx, ensureStateWorker{})
  1249  	step(c, ctx, createServiceAndUnit{})
  1250  	step(c, ctx, startUniter{})
  1251  	step(c, ctx, waitAddresses{})
  1252  }
  1253  
  1254  type waitAddresses struct{}
  1255  
  1256  func (waitAddresses) step(c *gc.C, ctx *context) {
  1257  	timeout := time.After(worstCase)
  1258  	for {
  1259  		select {
  1260  		case <-timeout:
  1261  			c.Fatalf("timed out waiting for unit addresses")
  1262  		case <-time.After(coretesting.ShortWait):
  1263  			err := ctx.unit.Refresh()
  1264  			if err != nil {
  1265  				c.Fatalf("unit refresh failed: %v", err)
  1266  			}
  1267  			// GZ 2013-07-10: Hardcoded values from dummy environ
  1268  			//                special cased here, questionable.
  1269  			private, _ := ctx.unit.PrivateAddress()
  1270  			if private != "private.dummy.address.example.com" {
  1271  				continue
  1272  			}
  1273  			public, _ := ctx.unit.PublicAddress()
  1274  			if public != "public.dummy.address.example.com" {
  1275  				continue
  1276  			}
  1277  			return
  1278  		}
  1279  	}
  1280  }
  1281  
  1282  type startUniter struct {
  1283  	unitTag string
  1284  }
  1285  
  1286  func (s startUniter) step(c *gc.C, ctx *context) {
  1287  	if s.unitTag == "" {
  1288  		s.unitTag = "unit-u-0"
  1289  	}
  1290  	if ctx.uniter != nil {
  1291  		panic("don't start two uniters!")
  1292  	}
  1293  	if ctx.s.uniter == nil {
  1294  		panic("API connection not established")
  1295  	}
  1296  	ctx.uniter = uniter.NewUniter(ctx.s.uniter, s.unitTag, ctx.dataDir)
  1297  	uniter.SetUniterObserver(ctx.uniter, ctx)
  1298  }
  1299  
  1300  type waitUniterDead struct {
  1301  	err string
  1302  }
  1303  
  1304  func (s waitUniterDead) step(c *gc.C, ctx *context) {
  1305  	if s.err != "" {
  1306  		err := s.waitDead(c, ctx)
  1307  		c.Assert(err, gc.ErrorMatches, s.err)
  1308  		return
  1309  	}
  1310  	// In the default case, we're waiting for worker.ErrTerminateAgent, but
  1311  	// the path to that error can be tricky. If the unit becomes Dead at an
  1312  	// inconvenient time, unrelated calls can fail -- as they should -- but
  1313  	// not be detected as worker.ErrTerminateAgent. In this case, we restart
  1314  	// the uniter and check that it fails as expected when starting up; this
  1315  	// mimics the behaviour of the unit agent and verifies that the UA will,
  1316  	// eventually, see the correct error and respond appropriately.
  1317  	err := s.waitDead(c, ctx)
  1318  	if err != worker.ErrTerminateAgent {
  1319  		step(c, ctx, startUniter{})
  1320  		err = s.waitDead(c, ctx)
  1321  	}
  1322  	c.Assert(errgo.Cause(err), gc.Equals, worker.ErrTerminateAgent)
  1323  	err = ctx.unit.Refresh()
  1324  	c.Assert(err, gc.IsNil)
  1325  	c.Assert(ctx.unit.Life(), gc.Equals, state.Dead)
  1326  }
  1327  
  1328  func (s waitUniterDead) waitDead(c *gc.C, ctx *context) error {
  1329  	u := ctx.uniter
  1330  	ctx.uniter = nil
  1331  	timeout := time.After(worstCase)
  1332  	for {
  1333  		// The repeated StartSync is to ensure timely completion of this method
  1334  		// in the case(s) where a state change causes a uniter action which
  1335  		// causes a state change which causes a uniter action, in which case we
  1336  		// need more than one sync. At the moment there's only one situation
  1337  		// that causes this -- setting the unit's service to Dying -- but it's
  1338  		// not an intrinsically insane pattern of action (and helps to simplify
  1339  		// the filter code) so this test seems like a small price to pay.
  1340  		ctx.s.BackingState.StartSync()
  1341  		select {
  1342  		case <-u.Dead():
  1343  			return u.Wait()
  1344  		case <-time.After(coretesting.ShortWait):
  1345  			continue
  1346  		case <-timeout:
  1347  			c.Fatalf("uniter still alive")
  1348  		}
  1349  	}
  1350  }
  1351  
  1352  type stopUniter struct {
  1353  	err string
  1354  }
  1355  
  1356  func (s stopUniter) step(c *gc.C, ctx *context) {
  1357  	u := ctx.uniter
  1358  	ctx.uniter = nil
  1359  	err := u.Stop()
  1360  	if s.err == "" {
  1361  		c.Assert(err, gc.IsNil)
  1362  	} else {
  1363  		c.Assert(err, gc.ErrorMatches, s.err)
  1364  	}
  1365  }
  1366  
  1367  type verifyWaiting struct{}
  1368  
  1369  func (s verifyWaiting) step(c *gc.C, ctx *context) {
  1370  	step(c, ctx, stopUniter{})
  1371  	step(c, ctx, startUniter{})
  1372  	step(c, ctx, waitHooks{})
  1373  }
  1374  
  1375  type verifyRunning struct {
  1376  	noHooks bool
  1377  }
  1378  
  1379  func (s verifyRunning) step(c *gc.C, ctx *context) {
  1380  	step(c, ctx, stopUniter{})
  1381  	step(c, ctx, startUniter{})
  1382  	if s.noHooks {
  1383  		step(c, ctx, waitHooks{})
  1384  	} else {
  1385  		step(c, ctx, waitHooks{"config-changed"})
  1386  	}
  1387  }
  1388  
  1389  type startupError struct {
  1390  	badHook string
  1391  }
  1392  
  1393  func (s startupError) step(c *gc.C, ctx *context) {
  1394  	step(c, ctx, createCharm{badHooks: []string{s.badHook}})
  1395  	step(c, ctx, serveCharm{})
  1396  	step(c, ctx, createUniter{})
  1397  	step(c, ctx, waitUnit{
  1398  		status: params.StatusError,
  1399  		info:   fmt.Sprintf(`hook failed: %q`, s.badHook),
  1400  	})
  1401  	for _, hook := range []string{"install", "config-changed", "start"} {
  1402  		if hook == s.badHook {
  1403  			step(c, ctx, waitHooks{"fail-" + hook})
  1404  			break
  1405  		}
  1406  		step(c, ctx, waitHooks{hook})
  1407  	}
  1408  	step(c, ctx, verifyCharm{})
  1409  }
  1410  
  1411  type quickStart struct{}
  1412  
  1413  func (s quickStart) step(c *gc.C, ctx *context) {
  1414  	step(c, ctx, createCharm{})
  1415  	step(c, ctx, serveCharm{})
  1416  	step(c, ctx, createUniter{})
  1417  	step(c, ctx, waitUnit{status: params.StatusStarted})
  1418  	step(c, ctx, waitHooks{"install", "config-changed", "start"})
  1419  	step(c, ctx, verifyCharm{})
  1420  }
  1421  
  1422  type quickStartRelation struct{}
  1423  
  1424  func (s quickStartRelation) step(c *gc.C, ctx *context) {
  1425  	step(c, ctx, quickStart{})
  1426  	step(c, ctx, addRelation{})
  1427  	step(c, ctx, addRelationUnit{})
  1428  	step(c, ctx, waitHooks{"db-relation-joined mysql/0 db:0", "db-relation-changed mysql/0 db:0"})
  1429  	step(c, ctx, verifyRunning{})
  1430  }
  1431  
  1432  type startupRelationError struct {
  1433  	badHook string
  1434  }
  1435  
  1436  func (s startupRelationError) step(c *gc.C, ctx *context) {
  1437  	step(c, ctx, createCharm{badHooks: []string{s.badHook}})
  1438  	step(c, ctx, serveCharm{})
  1439  	step(c, ctx, createUniter{})
  1440  	step(c, ctx, waitUnit{status: params.StatusStarted})
  1441  	step(c, ctx, waitHooks{"install", "config-changed", "start"})
  1442  	step(c, ctx, verifyCharm{})
  1443  	step(c, ctx, addRelation{})
  1444  	step(c, ctx, addRelationUnit{})
  1445  }
  1446  
  1447  type resolveError struct {
  1448  	resolved state.ResolvedMode
  1449  }
  1450  
  1451  func (s resolveError) step(c *gc.C, ctx *context) {
  1452  	err := ctx.unit.SetResolved(s.resolved)
  1453  	c.Assert(err, gc.IsNil)
  1454  }
  1455  
  1456  type waitUnit struct {
  1457  	status   params.Status
  1458  	info     string
  1459  	data     params.StatusData
  1460  	charm    int
  1461  	resolved state.ResolvedMode
  1462  }
  1463  
  1464  func (s waitUnit) step(c *gc.C, ctx *context) {
  1465  	timeout := time.After(worstCase)
  1466  	for {
  1467  		ctx.s.BackingState.StartSync()
  1468  		select {
  1469  		case <-time.After(coretesting.ShortWait):
  1470  			err := ctx.unit.Refresh()
  1471  			if err != nil {
  1472  				c.Fatalf("cannot refresh unit: %v", err)
  1473  			}
  1474  			resolved := ctx.unit.Resolved()
  1475  			if resolved != s.resolved {
  1476  				c.Logf("want resolved mode %q, got %q; still waiting", s.resolved, resolved)
  1477  				continue
  1478  			}
  1479  			url, ok := ctx.unit.CharmURL()
  1480  			if !ok || *url != *curl(s.charm) {
  1481  				var got string
  1482  				if ok {
  1483  					got = url.String()
  1484  				}
  1485  				c.Logf("want unit charm %q, got %q; still waiting", curl(s.charm), got)
  1486  				continue
  1487  			}
  1488  			status, info, data, err := ctx.unit.Status()
  1489  			c.Assert(err, gc.IsNil)
  1490  			if status != s.status {
  1491  				c.Logf("want unit status %q, got %q; still waiting", s.status, status)
  1492  				continue
  1493  			}
  1494  			if info != s.info {
  1495  				c.Logf("want unit status info %q, got %q; still waiting", s.info, info)
  1496  				continue
  1497  			}
  1498  			if s.data != nil {
  1499  				if len(data) != len(s.data) {
  1500  					c.Logf("want %d unit status data value(s), got %d; still waiting", len(s.data), len(data))
  1501  					continue
  1502  				}
  1503  				for key, value := range s.data {
  1504  					if data[key] != value {
  1505  						c.Logf("want unit status data value %q for key %q, got %q; still waiting",
  1506  							value, key, data[key])
  1507  						continue
  1508  					}
  1509  				}
  1510  			}
  1511  			return
  1512  		case <-timeout:
  1513  			c.Fatalf("never reached desired status")
  1514  		}
  1515  	}
  1516  }
  1517  
  1518  type waitHooks []string
  1519  
  1520  func (s waitHooks) step(c *gc.C, ctx *context) {
  1521  	if len(s) == 0 {
  1522  		// Give unwanted hooks a moment to run...
  1523  		ctx.s.BackingState.StartSync()
  1524  		time.Sleep(coretesting.ShortWait)
  1525  	}
  1526  	ctx.hooks = append(ctx.hooks, s...)
  1527  	c.Logf("waiting for hooks: %#v", ctx.hooks)
  1528  	match, overshoot := ctx.matchHooks(c)
  1529  	if overshoot && len(s) == 0 {
  1530  		c.Fatalf("ran more hooks than expected")
  1531  	}
  1532  	if match {
  1533  		return
  1534  	}
  1535  	timeout := time.After(worstCase)
  1536  	for {
  1537  		ctx.s.BackingState.StartSync()
  1538  		select {
  1539  		case <-time.After(coretesting.ShortWait):
  1540  			if match, _ = ctx.matchHooks(c); match {
  1541  				return
  1542  			}
  1543  		case <-timeout:
  1544  			c.Fatalf("never got expected hooks")
  1545  		}
  1546  	}
  1547  }
  1548  
  1549  type fixHook struct {
  1550  	name string
  1551  }
  1552  
  1553  func (s fixHook) step(c *gc.C, ctx *context) {
  1554  	path := filepath.Join(ctx.path, "charm", "hooks", s.name)
  1555  	ctx.writeHook(c, path, true)
  1556  }
  1557  
  1558  type changeConfig map[string]interface{}
  1559  
  1560  func (s changeConfig) step(c *gc.C, ctx *context) {
  1561  	err := ctx.svc.UpdateConfigSettings(charm.Settings(s))
  1562  	c.Assert(err, gc.IsNil)
  1563  }
  1564  
  1565  type upgradeCharm struct {
  1566  	revision int
  1567  	forced   bool
  1568  }
  1569  
  1570  func (s upgradeCharm) step(c *gc.C, ctx *context) {
  1571  	sch, err := ctx.st.Charm(curl(s.revision))
  1572  	c.Assert(err, gc.IsNil)
  1573  	err = ctx.svc.SetCharm(sch, s.forced)
  1574  	c.Assert(err, gc.IsNil)
  1575  	serveCharm{}.step(c, ctx)
  1576  }
  1577  
  1578  type verifyCharm struct {
  1579  	revision int
  1580  	dirty    bool
  1581  }
  1582  
  1583  func (s verifyCharm) step(c *gc.C, ctx *context) {
  1584  	if !s.dirty {
  1585  		path := filepath.Join(ctx.path, "charm", "revision")
  1586  		content, err := ioutil.ReadFile(path)
  1587  		c.Assert(err, gc.IsNil)
  1588  		c.Assert(string(content), gc.Equals, strconv.Itoa(s.revision))
  1589  		err = ctx.unit.Refresh()
  1590  		c.Assert(err, gc.IsNil)
  1591  		url, ok := ctx.unit.CharmURL()
  1592  		c.Assert(ok, gc.Equals, true)
  1593  		c.Assert(url, gc.DeepEquals, curl(s.revision))
  1594  	}
  1595  
  1596  	// Before we try to check the git status, make sure expected hooks are all
  1597  	// complete, to prevent the test and the uniter interfering with each other.
  1598  	step(c, ctx, waitHooks{})
  1599  	cmd := exec.Command("git", "status")
  1600  	cmd.Dir = filepath.Join(ctx.path, "charm")
  1601  	out, err := cmd.CombinedOutput()
  1602  	c.Assert(err, gc.IsNil)
  1603  	cmp := gc.Matches
  1604  	if s.dirty {
  1605  		cmp = gc.Not(gc.Matches)
  1606  	}
  1607  	c.Assert(string(out), cmp, "(# )?On branch master\nnothing to commit.*\n")
  1608  }
  1609  
  1610  type startUpgradeError struct{}
  1611  
  1612  func (s startUpgradeError) step(c *gc.C, ctx *context) {
  1613  	steps := []stepper{
  1614  		createCharm{
  1615  			customize: func(c *gc.C, ctx *context, path string) {
  1616  				appendHook(c, path, "start", "echo STARTDATA > data")
  1617  			},
  1618  		},
  1619  		serveCharm{},
  1620  		createUniter{},
  1621  		waitUnit{
  1622  			status: params.StatusStarted,
  1623  		},
  1624  		waitHooks{"install", "config-changed", "start"},
  1625  		verifyCharm{},
  1626  
  1627  		createCharm{
  1628  			revision: 1,
  1629  			customize: func(c *gc.C, ctx *context, path string) {
  1630  				data := filepath.Join(path, "data")
  1631  				err := ioutil.WriteFile(data, []byte("<nelson>ha ha</nelson>"), 0644)
  1632  				c.Assert(err, gc.IsNil)
  1633  				ignore := filepath.Join(path, "ignore")
  1634  				err = ioutil.WriteFile(ignore, []byte("anything"), 0644)
  1635  				c.Assert(err, gc.IsNil)
  1636  			},
  1637  		},
  1638  		serveCharm{},
  1639  		upgradeCharm{revision: 1},
  1640  		waitUnit{
  1641  			status: params.StatusError,
  1642  			info:   "upgrade failed",
  1643  			charm:  1,
  1644  		},
  1645  		verifyWaiting{},
  1646  		verifyCharm{dirty: true},
  1647  	}
  1648  	for _, s_ := range steps {
  1649  		step(c, ctx, s_)
  1650  	}
  1651  }
  1652  
  1653  type addRelation struct {
  1654  	testing.JujuConnSuite
  1655  }
  1656  
  1657  func (s addRelation) step(c *gc.C, ctx *context) {
  1658  	if ctx.relation != nil {
  1659  		panic("don't add two relations!")
  1660  	}
  1661  	if ctx.relatedSvc == nil {
  1662  		ctx.relatedSvc = ctx.s.AddTestingService(c, "mysql", ctx.s.AddTestingCharm(c, "mysql"))
  1663  	}
  1664  	eps, err := ctx.st.InferEndpoints([]string{"u", "mysql"})
  1665  	c.Assert(err, gc.IsNil)
  1666  	ctx.relation, err = ctx.st.AddRelation(eps...)
  1667  	c.Assert(err, gc.IsNil)
  1668  	ctx.relationUnits = map[string]*state.RelationUnit{}
  1669  }
  1670  
  1671  type addRelationUnit struct{}
  1672  
  1673  func (s addRelationUnit) step(c *gc.C, ctx *context) {
  1674  	u, err := ctx.relatedSvc.AddUnit()
  1675  	c.Assert(err, gc.IsNil)
  1676  	ru, err := ctx.relation.Unit(u)
  1677  	c.Assert(err, gc.IsNil)
  1678  	err = ru.EnterScope(nil)
  1679  	c.Assert(err, gc.IsNil)
  1680  	ctx.relationUnits[u.Name()] = ru
  1681  }
  1682  
  1683  type changeRelationUnit struct {
  1684  	name string
  1685  }
  1686  
  1687  func (s changeRelationUnit) step(c *gc.C, ctx *context) {
  1688  	settings, err := ctx.relationUnits[s.name].Settings()
  1689  	c.Assert(err, gc.IsNil)
  1690  	key := "madness?"
  1691  	raw, _ := settings.Get(key)
  1692  	val, _ := raw.(string)
  1693  	if val == "" {
  1694  		val = "this is juju"
  1695  	} else {
  1696  		val += "u"
  1697  	}
  1698  	settings.Set(key, val)
  1699  	_, err = settings.Write()
  1700  	c.Assert(err, gc.IsNil)
  1701  }
  1702  
  1703  type removeRelationUnit struct {
  1704  	name string
  1705  }
  1706  
  1707  func (s removeRelationUnit) step(c *gc.C, ctx *context) {
  1708  	err := ctx.relationUnits[s.name].LeaveScope()
  1709  	c.Assert(err, gc.IsNil)
  1710  	ctx.relationUnits[s.name] = nil
  1711  }
  1712  
  1713  type relationState struct {
  1714  	removed bool
  1715  	life    state.Life
  1716  }
  1717  
  1718  func (s relationState) step(c *gc.C, ctx *context) {
  1719  	err := ctx.relation.Refresh()
  1720  	if s.removed {
  1721  		c.Assert(err, jc.Satisfies, errors.IsNotFoundError)
  1722  		return
  1723  	}
  1724  	c.Assert(err, gc.IsNil)
  1725  	c.Assert(ctx.relation.Life(), gc.Equals, s.life)
  1726  
  1727  }
  1728  
  1729  type addSubordinateRelation struct {
  1730  	ifce string
  1731  }
  1732  
  1733  func (s addSubordinateRelation) step(c *gc.C, ctx *context) {
  1734  	if _, err := ctx.st.Service("logging"); errors.IsNotFoundError(err) {
  1735  		ctx.s.AddTestingService(c, "logging", ctx.s.AddTestingCharm(c, "logging"))
  1736  	}
  1737  	eps, err := ctx.st.InferEndpoints([]string{"logging", "u:" + s.ifce})
  1738  	c.Assert(err, gc.IsNil)
  1739  	_, err = ctx.st.AddRelation(eps...)
  1740  	c.Assert(err, gc.IsNil)
  1741  }
  1742  
  1743  type removeSubordinateRelation struct {
  1744  	ifce string
  1745  }
  1746  
  1747  func (s removeSubordinateRelation) step(c *gc.C, ctx *context) {
  1748  	eps, err := ctx.st.InferEndpoints([]string{"logging", "u:" + s.ifce})
  1749  	c.Assert(err, gc.IsNil)
  1750  	rel, err := ctx.st.EndpointsRelation(eps...)
  1751  	c.Assert(err, gc.IsNil)
  1752  	err = rel.Destroy()
  1753  	c.Assert(err, gc.IsNil)
  1754  }
  1755  
  1756  type waitSubordinateExists struct {
  1757  	name string
  1758  }
  1759  
  1760  func (s waitSubordinateExists) step(c *gc.C, ctx *context) {
  1761  	timeout := time.After(worstCase)
  1762  	for {
  1763  		ctx.s.BackingState.StartSync()
  1764  		select {
  1765  		case <-timeout:
  1766  			c.Fatalf("subordinate was not created")
  1767  		case <-time.After(coretesting.ShortWait):
  1768  			var err error
  1769  			ctx.subordinate, err = ctx.st.Unit(s.name)
  1770  			if errors.IsNotFoundError(err) {
  1771  				continue
  1772  			}
  1773  			c.Assert(err, gc.IsNil)
  1774  			return
  1775  		}
  1776  	}
  1777  }
  1778  
  1779  type waitSubordinateDying struct{}
  1780  
  1781  func (waitSubordinateDying) step(c *gc.C, ctx *context) {
  1782  	timeout := time.After(worstCase)
  1783  	for {
  1784  		ctx.s.BackingState.StartSync()
  1785  		select {
  1786  		case <-timeout:
  1787  			c.Fatalf("subordinate was not made Dying")
  1788  		case <-time.After(coretesting.ShortWait):
  1789  			err := ctx.subordinate.Refresh()
  1790  			c.Assert(err, gc.IsNil)
  1791  			if ctx.subordinate.Life() != state.Dying {
  1792  				continue
  1793  			}
  1794  		}
  1795  		break
  1796  	}
  1797  }
  1798  
  1799  type removeSubordinate struct{}
  1800  
  1801  func (removeSubordinate) step(c *gc.C, ctx *context) {
  1802  	err := ctx.subordinate.EnsureDead()
  1803  	c.Assert(err, gc.IsNil)
  1804  	err = ctx.subordinate.Remove()
  1805  	c.Assert(err, gc.IsNil)
  1806  	ctx.subordinate = nil
  1807  }
  1808  
  1809  type assertYaml struct {
  1810  	path   string
  1811  	expect map[string]interface{}
  1812  }
  1813  
  1814  func (s assertYaml) step(c *gc.C, ctx *context) {
  1815  	data, err := ioutil.ReadFile(filepath.Join(ctx.path, s.path))
  1816  	c.Assert(err, gc.IsNil)
  1817  	actual := make(map[string]interface{})
  1818  	err = goyaml.Unmarshal(data, &actual)
  1819  	c.Assert(err, gc.IsNil)
  1820  	c.Assert(actual, gc.DeepEquals, s.expect)
  1821  }
  1822  
  1823  type writeFile struct {
  1824  	path string
  1825  	mode os.FileMode
  1826  }
  1827  
  1828  func (s writeFile) step(c *gc.C, ctx *context) {
  1829  	path := filepath.Join(ctx.path, s.path)
  1830  	dir := filepath.Dir(path)
  1831  	err := os.MkdirAll(dir, 0755)
  1832  	c.Assert(err, gc.IsNil)
  1833  	err = ioutil.WriteFile(path, nil, s.mode)
  1834  	c.Assert(err, gc.IsNil)
  1835  }
  1836  
  1837  type chmod struct {
  1838  	path string
  1839  	mode os.FileMode
  1840  }
  1841  
  1842  func (s chmod) step(c *gc.C, ctx *context) {
  1843  	path := filepath.Join(ctx.path, s.path)
  1844  	err := os.Chmod(path, s.mode)
  1845  	c.Assert(err, gc.IsNil)
  1846  }
  1847  
  1848  type custom struct {
  1849  	f func(*gc.C, *context)
  1850  }
  1851  
  1852  func (s custom) step(c *gc.C, ctx *context) {
  1853  	s.f(c, ctx)
  1854  }
  1855  
  1856  var serviceDying = custom{func(c *gc.C, ctx *context) {
  1857  	c.Assert(ctx.svc.Destroy(), gc.IsNil)
  1858  }}
  1859  
  1860  var relationDying = custom{func(c *gc.C, ctx *context) {
  1861  	c.Assert(ctx.relation.Destroy(), gc.IsNil)
  1862  }}
  1863  
  1864  var unitDying = custom{func(c *gc.C, ctx *context) {
  1865  	c.Assert(ctx.unit.Destroy(), gc.IsNil)
  1866  }}
  1867  
  1868  var unitDead = custom{func(c *gc.C, ctx *context) {
  1869  	c.Assert(ctx.unit.EnsureDead(), gc.IsNil)
  1870  }}
  1871  
  1872  var subordinateDying = custom{func(c *gc.C, ctx *context) {
  1873  	c.Assert(ctx.subordinate.Destroy(), gc.IsNil)
  1874  }}
  1875  
  1876  func curl(revision int) *charm.URL {
  1877  	return charm.MustParseURL("cs:quantal/wordpress").WithRevision(revision)
  1878  }
  1879  
  1880  func appendHook(c *gc.C, charm, name, data string) {
  1881  	path := filepath.Join(charm, "hooks", name)
  1882  	f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND, 0755)
  1883  	c.Assert(err, gc.IsNil)
  1884  	defer f.Close()
  1885  	_, err = f.Write([]byte(data))
  1886  	c.Assert(err, gc.IsNil)
  1887  }
  1888  
  1889  func renameRelation(c *gc.C, charmPath, oldName, newName string) {
  1890  	path := filepath.Join(charmPath, "metadata.yaml")
  1891  	f, err := os.Open(path)
  1892  	c.Assert(err, gc.IsNil)
  1893  	defer f.Close()
  1894  	meta, err := charm.ReadMeta(f)
  1895  	c.Assert(err, gc.IsNil)
  1896  
  1897  	replace := func(what map[string]charm.Relation) bool {
  1898  		for relName, relation := range what {
  1899  			if relName == oldName {
  1900  				what[newName] = relation
  1901  				delete(what, oldName)
  1902  				return true
  1903  			}
  1904  		}
  1905  		return false
  1906  	}
  1907  	replaced := replace(meta.Provides) || replace(meta.Requires) || replace(meta.Peers)
  1908  	c.Assert(replaced, gc.Equals, true, gc.Commentf("charm %q does not implement relation %q", charmPath, oldName))
  1909  
  1910  	newmeta, err := goyaml.Marshal(meta)
  1911  	c.Assert(err, gc.IsNil)
  1912  	ioutil.WriteFile(path, newmeta, 0644)
  1913  
  1914  	f, err = os.Open(path)
  1915  	c.Assert(err, gc.IsNil)
  1916  	defer f.Close()
  1917  	meta, err = charm.ReadMeta(f)
  1918  	c.Assert(err, gc.IsNil)
  1919  }
  1920  
  1921  func createHookLock(c *gc.C, dataDir string) *fslock.Lock {
  1922  	lockDir := filepath.Join(dataDir, "locks")
  1923  	lock, err := fslock.NewLock(lockDir, "uniter-hook-execution")
  1924  	c.Assert(err, gc.IsNil)
  1925  	return lock
  1926  }
  1927  
  1928  type acquireHookSyncLock struct {
  1929  	message string
  1930  }
  1931  
  1932  func (s acquireHookSyncLock) step(c *gc.C, ctx *context) {
  1933  	lock := createHookLock(c, ctx.dataDir)
  1934  	c.Assert(lock.IsLocked(), gc.Equals, false)
  1935  	err := lock.Lock(s.message)
  1936  	c.Assert(err, gc.IsNil)
  1937  }
  1938  
  1939  var releaseHookSyncLock = custom{func(c *gc.C, ctx *context) {
  1940  	lock := createHookLock(c, ctx.dataDir)
  1941  	// Force the release.
  1942  	err := lock.BreakLock()
  1943  	c.Assert(err, gc.IsNil)
  1944  }}
  1945  
  1946  var verifyHookSyncLockUnlocked = custom{func(c *gc.C, ctx *context) {
  1947  	lock := createHookLock(c, ctx.dataDir)
  1948  	c.Assert(lock.IsLocked(), jc.IsFalse)
  1949  }}
  1950  
  1951  var verifyHookSyncLockLocked = custom{func(c *gc.C, ctx *context) {
  1952  	lock := createHookLock(c, ctx.dataDir)
  1953  	c.Assert(lock.IsLocked(), jc.IsTrue)
  1954  }}
  1955  
  1956  type setProxySettings osenv.ProxySettings
  1957  
  1958  func (s setProxySettings) step(c *gc.C, ctx *context) {
  1959  	old, err := ctx.st.EnvironConfig()
  1960  	c.Assert(err, gc.IsNil)
  1961  	cfg, err := old.Apply(map[string]interface{}{
  1962  		"http-proxy":  s.Http,
  1963  		"https-proxy": s.Https,
  1964  		"ftp-proxy":   s.Ftp,
  1965  	})
  1966  	c.Assert(err, gc.IsNil)
  1967  	err = ctx.st.SetEnvironConfig(cfg, old)
  1968  	c.Assert(err, gc.IsNil)
  1969  	// wait for the new values...
  1970  	expected := (osenv.ProxySettings)(s)
  1971  	for attempt := coretesting.LongAttempt.Start(); attempt.Next(); {
  1972  		if ctx.uniter.GetProxyValues() == expected {
  1973  			// Also confirm that the values were specified for the environment.
  1974  			c.Assert(os.Getenv("http_proxy"), gc.Equals, expected.Http)
  1975  			c.Assert(os.Getenv("HTTP_PROXY"), gc.Equals, expected.Http)
  1976  			c.Assert(os.Getenv("https_proxy"), gc.Equals, expected.Https)
  1977  			c.Assert(os.Getenv("HTTPS_PROXY"), gc.Equals, expected.Https)
  1978  			c.Assert(os.Getenv("ftp_proxy"), gc.Equals, expected.Ftp)
  1979  			c.Assert(os.Getenv("FTP_PROXY"), gc.Equals, expected.Ftp)
  1980  			return
  1981  		}
  1982  	}
  1983  	c.Fatal("settings didn't get noticed by the uniter")
  1984  }
  1985  
  1986  type runCommands []string
  1987  
  1988  func (cmds runCommands) step(c *gc.C, ctx *context) {
  1989  	commands := strings.Join(cmds, "\n")
  1990  	result, err := ctx.uniter.RunCommands(commands)
  1991  	c.Assert(err, gc.IsNil)
  1992  	c.Check(result.Code, gc.Equals, 0)
  1993  	c.Check(string(result.Stdout), gc.Equals, "")
  1994  	c.Check(string(result.Stderr), gc.Equals, "")
  1995  }
  1996  
  1997  type asyncRunCommands []string
  1998  
  1999  func (cmds asyncRunCommands) step(c *gc.C, ctx *context) {
  2000  	commands := strings.Join(cmds, "\n")
  2001  	socketPath := filepath.Join(ctx.path, uniter.RunListenerFile)
  2002  
  2003  	go func() {
  2004  		// make sure the socket exists
  2005  		client, err := rpc.Dial("unix", socketPath)
  2006  		c.Assert(err, gc.IsNil)
  2007  		defer client.Close()
  2008  
  2009  		var result utilexec.ExecResponse
  2010  		err = client.Call(uniter.JujuRunEndpoint, commands, &result)
  2011  		c.Assert(err, gc.IsNil)
  2012  		c.Check(result.Code, gc.Equals, 0)
  2013  		c.Check(string(result.Stdout), gc.Equals, "")
  2014  		c.Check(string(result.Stderr), gc.Equals, "")
  2015  	}()
  2016  }
  2017  
  2018  type verifyFile struct {
  2019  	filename string
  2020  	content  string
  2021  }
  2022  
  2023  func (verify verifyFile) fileExists() bool {
  2024  	_, err := os.Stat(verify.filename)
  2025  	return err == nil
  2026  }
  2027  
  2028  func (verify verifyFile) checkContent(c *gc.C) {
  2029  	content, err := ioutil.ReadFile(verify.filename)
  2030  	c.Assert(err, gc.IsNil)
  2031  	c.Assert(string(content), gc.Equals, verify.content)
  2032  }
  2033  
  2034  func (verify verifyFile) step(c *gc.C, ctx *context) {
  2035  	if verify.fileExists() {
  2036  		verify.checkContent(c)
  2037  		return
  2038  	}
  2039  	c.Logf("waiting for file: %s", verify.filename)
  2040  	timeout := time.After(worstCase)
  2041  	for {
  2042  		select {
  2043  		case <-time.After(coretesting.ShortWait):
  2044  			if verify.fileExists() {
  2045  				verify.checkContent(c)
  2046  				return
  2047  			}
  2048  		case <-timeout:
  2049  			c.Fatalf("file does not exist")
  2050  		}
  2051  	}
  2052  }
  2053  
  2054  // verify that the file does not exist
  2055  type verifyNoFile struct {
  2056  	filename string
  2057  }
  2058  
  2059  func (verify verifyNoFile) step(c *gc.C, ctx *context) {
  2060  	c.Assert(verify.filename, jc.DoesNotExist)
  2061  	// Wait a short time and check again.
  2062  	time.Sleep(coretesting.ShortWait)
  2063  	c.Assert(verify.filename, jc.DoesNotExist)
  2064  }