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