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