github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/worker/uniter/util_test.go (about)

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