github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/action_test.go (about)

     1  // Copyright 2014-2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state_test
     5  
     6  import (
     7  	"encoding/hex"
     8  	"fmt"
     9  	"strings"
    10  
    11  	"github.com/juju/errors"
    12  	jc "github.com/juju/testing/checkers"
    13  	"github.com/juju/txn"
    14  	"github.com/juju/utils"
    15  	gc "gopkg.in/check.v1"
    16  	"gopkg.in/juju/names.v2"
    17  
    18  	"github.com/juju/juju/state"
    19  	statetesting "github.com/juju/juju/state/testing"
    20  	"github.com/juju/juju/testing"
    21  )
    22  
    23  type ActionSuite struct {
    24  	ConnSuite
    25  	charm             *state.Charm
    26  	actionlessCharm   *state.Charm
    27  	service           *state.Application
    28  	actionlessService *state.Application
    29  	unit              *state.Unit
    30  	unit2             *state.Unit
    31  	charmlessUnit     *state.Unit
    32  	actionlessUnit    *state.Unit
    33  }
    34  
    35  var _ = gc.Suite(&ActionSuite{})
    36  
    37  func (s *ActionSuite) SetUpTest(c *gc.C) {
    38  	var err error
    39  
    40  	s.ConnSuite.SetUpTest(c)
    41  
    42  	s.charm = s.AddTestingCharm(c, "dummy")
    43  	s.actionlessCharm = s.AddTestingCharm(c, "actionless")
    44  
    45  	s.service = s.AddTestingService(c, "dummy", s.charm)
    46  	c.Assert(err, jc.ErrorIsNil)
    47  	s.actionlessService = s.AddTestingService(c, "actionless", s.actionlessCharm)
    48  	c.Assert(err, jc.ErrorIsNil)
    49  
    50  	sURL, _ := s.service.CharmURL()
    51  	c.Assert(sURL, gc.NotNil)
    52  	actionlessSURL, _ := s.actionlessService.CharmURL()
    53  	c.Assert(actionlessSURL, gc.NotNil)
    54  
    55  	s.unit, err = s.service.AddUnit()
    56  	c.Assert(err, jc.ErrorIsNil)
    57  	c.Assert(s.unit.Series(), gc.Equals, "quantal")
    58  
    59  	err = s.unit.SetCharmURL(sURL)
    60  	c.Assert(err, jc.ErrorIsNil)
    61  
    62  	s.unit2, err = s.service.AddUnit()
    63  	c.Assert(err, jc.ErrorIsNil)
    64  	c.Assert(s.unit2.Series(), gc.Equals, "quantal")
    65  
    66  	err = s.unit2.SetCharmURL(sURL)
    67  	c.Assert(err, jc.ErrorIsNil)
    68  
    69  	s.charmlessUnit, err = s.service.AddUnit()
    70  	c.Assert(err, jc.ErrorIsNil)
    71  	c.Assert(s.charmlessUnit.Series(), gc.Equals, "quantal")
    72  
    73  	s.actionlessUnit, err = s.actionlessService.AddUnit()
    74  	c.Assert(err, jc.ErrorIsNil)
    75  	c.Assert(s.actionlessUnit.Series(), gc.Equals, "quantal")
    76  
    77  	err = s.actionlessUnit.SetCharmURL(actionlessSURL)
    78  	c.Assert(err, jc.ErrorIsNil)
    79  }
    80  
    81  func (s *ActionSuite) TestActionTag(c *gc.C) {
    82  	action, err := s.unit.AddAction("snapshot", nil)
    83  	c.Assert(err, jc.ErrorIsNil)
    84  
    85  	tag := action.Tag()
    86  	c.Assert(tag.String(), gc.Equals, "action-"+action.Id())
    87  
    88  	result, err := action.Finish(state.ActionResults{Status: state.ActionCompleted})
    89  	c.Assert(err, jc.ErrorIsNil)
    90  
    91  	actions, err := s.unit.CompletedActions()
    92  	c.Assert(err, jc.ErrorIsNil)
    93  	c.Assert(len(actions), gc.Equals, 1)
    94  
    95  	actionResult := actions[0]
    96  	c.Assert(actionResult, gc.DeepEquals, result)
    97  
    98  	tag = actionResult.Tag()
    99  	c.Assert(tag.String(), gc.Equals, "action-"+actionResult.Id())
   100  }
   101  
   102  func (s *ActionSuite) TestAddAction(c *gc.C) {
   103  	for i, t := range []struct {
   104  		should      string
   105  		name        string
   106  		params      map[string]interface{}
   107  		whichUnit   *state.Unit
   108  		expectedErr string
   109  	}{{
   110  		should:    "enqueue normally",
   111  		name:      "snapshot",
   112  		whichUnit: s.unit,
   113  		//params:    map[string]interface{}{"outfile": "outfile.tar.bz2"},
   114  	}, {
   115  		should:      "fail on actionless charms",
   116  		name:        "something",
   117  		whichUnit:   s.actionlessUnit,
   118  		expectedErr: "no actions defined on charm \"local:quantal/quantal-actionless-1\"",
   119  	}, {
   120  		should:      "fail on action not defined in schema",
   121  		whichUnit:   s.unit,
   122  		name:        "something-nonexistent",
   123  		expectedErr: "action \"something-nonexistent\" not defined on unit \"dummy/0\"",
   124  	}, {
   125  		should:    "invalidate with bad params",
   126  		whichUnit: s.unit,
   127  		name:      "snapshot",
   128  		params: map[string]interface{}{
   129  			"outfile": 5.0,
   130  		},
   131  		expectedErr: "validation failed: \\(root\\)\\.outfile : must be of type string, given 5",
   132  	}} {
   133  		c.Logf("Test %d: should %s", i, t.should)
   134  		before := s.State.NowToTheSecond()
   135  		later := before.Add(testing.LongWait)
   136  
   137  		// Copy params over into empty premade map for comparison later
   138  		params := make(map[string]interface{})
   139  		for k, v := range t.params {
   140  			params[k] = v
   141  		}
   142  
   143  		// Verify we can add an Action
   144  		a, err := t.whichUnit.AddAction(t.name, params)
   145  
   146  		if t.expectedErr == "" {
   147  			c.Assert(err, jc.ErrorIsNil)
   148  			curl, _ := t.whichUnit.CharmURL()
   149  			ch, _ := s.State.Charm(curl)
   150  			schema := ch.Actions()
   151  			c.Logf("Schema for unit %q:\n%#v", t.whichUnit.Name(), schema)
   152  			// verify we can get it back out by Id
   153  			action, err := s.State.Action(a.Id())
   154  			c.Assert(err, jc.ErrorIsNil)
   155  			c.Assert(action, gc.NotNil)
   156  			c.Check(action.Id(), gc.Equals, a.Id())
   157  
   158  			// verify we get out what we put in
   159  			c.Check(action.Name(), gc.Equals, t.name)
   160  			c.Check(action.Parameters(), jc.DeepEquals, params)
   161  
   162  			// Enqueued time should be within a reasonable time of the beginning
   163  			// of the test
   164  			now := s.State.NowToTheSecond()
   165  			c.Check(action.Enqueued(), jc.TimeBetween(before, now))
   166  			c.Check(action.Enqueued(), jc.TimeBetween(before, later))
   167  			continue
   168  		}
   169  
   170  		c.Check(err, gc.ErrorMatches, t.expectedErr)
   171  	}
   172  }
   173  
   174  func (s *ActionSuite) TestAddActionInsertsDefaults(c *gc.C) {
   175  	units := make(map[string]*state.Unit)
   176  	schemas := map[string]string{
   177  		"simple": `
   178  act:
   179    params:
   180      val:
   181        type: string
   182        default: somestr
   183  `[1:],
   184  		"complicated": `
   185  act:
   186    params:
   187      val:
   188        type: object
   189        properties:
   190          foo:
   191            type: string
   192          bar:
   193            type: object
   194            properties:
   195              baz:
   196                type: string
   197                default: woz
   198  `[1:],
   199  		"none": `
   200  act:
   201    params:
   202      val:
   203        type: string
   204  `[1:]}
   205  
   206  	// Prepare the units for this test
   207  	makeUnits(c, s, units, schemas)
   208  
   209  	for i, t := range []struct {
   210  		should         string
   211  		params         map[string]interface{}
   212  		schema         string
   213  		expectedParams map[string]interface{}
   214  	}{{
   215  		should:         "do nothing with no defaults",
   216  		params:         map[string]interface{}{},
   217  		schema:         "none",
   218  		expectedParams: map[string]interface{}{},
   219  	}, {
   220  		should: "insert a simple default value",
   221  		params: map[string]interface{}{"foo": "bar"},
   222  		schema: "simple",
   223  		expectedParams: map[string]interface{}{
   224  			"foo": "bar",
   225  			"val": "somestr",
   226  		},
   227  	}, {
   228  		should: "insert a default value when an empty map is passed",
   229  		params: map[string]interface{}{},
   230  		schema: "simple",
   231  		expectedParams: map[string]interface{}{
   232  			"val": "somestr",
   233  		},
   234  	}, {
   235  		should: "insert a default value when a nil map is passed",
   236  		params: nil,
   237  		schema: "simple",
   238  		expectedParams: map[string]interface{}{
   239  			"val": "somestr",
   240  		},
   241  	}, {
   242  		should: "insert a nested default value",
   243  		params: map[string]interface{}{"foo": "bar"},
   244  		schema: "complicated",
   245  		expectedParams: map[string]interface{}{
   246  			"foo": "bar",
   247  			"val": map[string]interface{}{
   248  				"bar": map[string]interface{}{
   249  					"baz": "woz",
   250  				}}},
   251  	}} {
   252  		c.Logf("test %d: should %s", i, t.should)
   253  		u := units[t.schema]
   254  		// Note that AddAction will only result in errors in the case
   255  		// of malformed schemas, and schema objects can only be
   256  		// created from valid schemas.  The error handling for this
   257  		// is tested in the gojsonschema package.
   258  		action, err := u.AddAction("act", t.params)
   259  		c.Assert(err, jc.ErrorIsNil)
   260  		c.Check(action.Parameters(), jc.DeepEquals, t.expectedParams)
   261  	}
   262  }
   263  
   264  // makeUnits prepares units with given Action schemas
   265  func makeUnits(c *gc.C, s *ActionSuite, units map[string]*state.Unit, schemas map[string]string) {
   266  	// A few dummy charms that haven't been used yet
   267  	freeCharms := map[string]string{
   268  		"simple":      "mysql",
   269  		"complicated": "mysql-alternative",
   270  		"none":        "wordpress",
   271  	}
   272  
   273  	for name, schema := range schemas {
   274  		svcName := name + "-defaults-service"
   275  
   276  		// Add a testing service
   277  		ch := s.AddActionsCharm(c, freeCharms[name], schema, 1)
   278  		svc := s.AddTestingService(c, svcName, ch)
   279  
   280  		// Get its charm URL
   281  		sURL, _ := svc.CharmURL()
   282  		c.Assert(sURL, gc.NotNil)
   283  
   284  		// Add a unit
   285  		var err error
   286  		u, err := svc.AddUnit()
   287  		c.Assert(err, jc.ErrorIsNil)
   288  		c.Assert(u.Series(), gc.Equals, "quantal")
   289  		err = u.SetCharmURL(sURL)
   290  		c.Assert(err, jc.ErrorIsNil)
   291  
   292  		units[name] = u
   293  	}
   294  }
   295  
   296  func (s *ActionSuite) TestEnqueueActionRequiresName(c *gc.C) {
   297  	name := ""
   298  
   299  	// verify can not enqueue an Action without a name
   300  	_, err := s.State.EnqueueAction(s.unit.Tag(), name, nil)
   301  	c.Assert(err, gc.ErrorMatches, "action name required")
   302  }
   303  
   304  func (s *ActionSuite) TestAddActionAcceptsDuplicateNames(c *gc.C) {
   305  	name := "snapshot"
   306  	params1 := map[string]interface{}{"outfile": "outfile.tar.bz2"}
   307  	params2 := map[string]interface{}{"infile": "infile.zip"}
   308  
   309  	// verify can add two actions with same name
   310  	a1, err := s.unit.AddAction(name, params1)
   311  	c.Assert(err, jc.ErrorIsNil)
   312  
   313  	a2, err := s.unit.AddAction(name, params2)
   314  	c.Assert(err, jc.ErrorIsNil)
   315  
   316  	c.Assert(a1.Id(), gc.Not(gc.Equals), a2.Id())
   317  
   318  	// verify both actually got added
   319  	actions, err := s.unit.PendingActions()
   320  	c.Assert(err, jc.ErrorIsNil)
   321  	c.Assert(len(actions), gc.Equals, 2)
   322  
   323  	// verify we can Fail one, retrieve the other, and they're not mixed up
   324  	action1, err := s.State.Action(a1.Id())
   325  	c.Assert(err, jc.ErrorIsNil)
   326  	_, err = action1.Finish(state.ActionResults{Status: state.ActionFailed})
   327  	c.Assert(err, jc.ErrorIsNil)
   328  
   329  	action2, err := s.State.Action(a2.Id())
   330  	c.Assert(err, jc.ErrorIsNil)
   331  	c.Assert(action2.Parameters(), jc.DeepEquals, params2)
   332  
   333  	// verify only one left, and it's the expected one
   334  	actions, err = s.unit.PendingActions()
   335  	c.Assert(err, jc.ErrorIsNil)
   336  	c.Assert(len(actions), gc.Equals, 1)
   337  	c.Assert(actions[0].Id(), gc.Equals, a2.Id())
   338  }
   339  
   340  func (s *ActionSuite) TestAddActionLifecycle(c *gc.C) {
   341  	unit, err := s.State.Unit(s.unit.Name())
   342  	c.Assert(err, jc.ErrorIsNil)
   343  	preventUnitDestroyRemove(c, unit)
   344  
   345  	// make unit state Dying
   346  	err = unit.Destroy()
   347  	c.Assert(err, jc.ErrorIsNil)
   348  
   349  	// can add action to a dying unit
   350  	_, err = unit.AddAction("snapshot", map[string]interface{}{})
   351  	c.Assert(err, jc.ErrorIsNil)
   352  
   353  	// make sure unit is dead
   354  	err = unit.EnsureDead()
   355  	c.Assert(err, jc.ErrorIsNil)
   356  
   357  	// cannot add action to a dead unit
   358  	_, err = unit.AddAction("snapshot", map[string]interface{}{})
   359  	c.Assert(err, gc.Equals, state.ErrDead)
   360  }
   361  
   362  func (s *ActionSuite) TestAddActionFailsOnDeadUnitInTransaction(c *gc.C) {
   363  	unit, err := s.State.Unit(s.unit.Name())
   364  	c.Assert(err, jc.ErrorIsNil)
   365  	preventUnitDestroyRemove(c, unit)
   366  
   367  	killUnit := txn.TestHook{
   368  		Before: func() {
   369  			c.Assert(unit.Destroy(), gc.IsNil)
   370  			c.Assert(unit.EnsureDead(), gc.IsNil)
   371  		},
   372  	}
   373  	defer state.SetTestHooks(c, s.State, killUnit).Check()
   374  
   375  	_, err = unit.AddAction("snapshot", map[string]interface{}{})
   376  	c.Assert(err, gc.Equals, state.ErrDead)
   377  }
   378  
   379  func (s *ActionSuite) TestFail(c *gc.C) {
   380  	// get unit, add an action, retrieve that action
   381  	unit, err := s.State.Unit(s.unit.Name())
   382  	c.Assert(err, jc.ErrorIsNil)
   383  	preventUnitDestroyRemove(c, unit)
   384  
   385  	a, err := unit.AddAction("snapshot", nil)
   386  	c.Assert(err, jc.ErrorIsNil)
   387  
   388  	action, err := s.State.Action(a.Id())
   389  	c.Assert(err, jc.ErrorIsNil)
   390  
   391  	// ensure no action results for this action
   392  	results, err := unit.CompletedActions()
   393  	c.Assert(err, jc.ErrorIsNil)
   394  	c.Assert(len(results), gc.Equals, 0)
   395  
   396  	// fail the action, and verify that it succeeds
   397  	reason := "test fail reason"
   398  	result, err := action.Finish(state.ActionResults{Status: state.ActionFailed, Message: reason})
   399  	c.Assert(err, jc.ErrorIsNil)
   400  
   401  	// ensure we now have a result for this action
   402  	results, err = unit.CompletedActions()
   403  	c.Assert(err, jc.ErrorIsNil)
   404  	c.Assert(len(results), gc.Equals, 1)
   405  	c.Assert(results[0], gc.DeepEquals, result)
   406  
   407  	c.Assert(results[0].Name(), gc.Equals, action.Name())
   408  	c.Assert(results[0].Status(), gc.Equals, state.ActionFailed)
   409  
   410  	// Verify the Action Completed time was within a reasonable
   411  	// time of the Enqueued time.
   412  	diff := results[0].Completed().Sub(action.Enqueued())
   413  	c.Assert(diff >= 0, jc.IsTrue)
   414  	c.Assert(diff < testing.LongWait, jc.IsTrue)
   415  
   416  	res, errstr := results[0].Results()
   417  	c.Assert(errstr, gc.Equals, reason)
   418  	c.Assert(res, gc.DeepEquals, map[string]interface{}{})
   419  
   420  	// validate that a pending action is no longer returned by UnitActions.
   421  	actions, err := unit.PendingActions()
   422  	c.Assert(err, jc.ErrorIsNil)
   423  	c.Assert(len(actions), gc.Equals, 0)
   424  }
   425  
   426  func (s *ActionSuite) TestComplete(c *gc.C) {
   427  	// get unit, add an action, retrieve that action
   428  	unit, err := s.State.Unit(s.unit.Name())
   429  	c.Assert(err, jc.ErrorIsNil)
   430  	preventUnitDestroyRemove(c, unit)
   431  
   432  	a, err := unit.AddAction("snapshot", nil)
   433  	c.Assert(err, jc.ErrorIsNil)
   434  
   435  	action, err := s.State.Action(a.Id())
   436  	c.Assert(err, jc.ErrorIsNil)
   437  
   438  	// ensure no action results for this action
   439  	results, err := unit.CompletedActions()
   440  	c.Assert(err, jc.ErrorIsNil)
   441  	c.Assert(len(results), gc.Equals, 0)
   442  
   443  	// complete the action, and verify that it succeeds
   444  	output := map[string]interface{}{"output": "action ran successfully"}
   445  	result, err := action.Finish(state.ActionResults{Status: state.ActionCompleted, Results: output})
   446  	c.Assert(err, jc.ErrorIsNil)
   447  
   448  	// ensure we now have a result for this action
   449  	results, err = unit.CompletedActions()
   450  	c.Assert(err, jc.ErrorIsNil)
   451  	c.Assert(len(results), gc.Equals, 1)
   452  	c.Assert(results[0], gc.DeepEquals, result)
   453  
   454  	c.Assert(results[0].Name(), gc.Equals, action.Name())
   455  	c.Assert(results[0].Status(), gc.Equals, state.ActionCompleted)
   456  	res, errstr := results[0].Results()
   457  	c.Assert(errstr, gc.Equals, "")
   458  	c.Assert(res, gc.DeepEquals, output)
   459  
   460  	// validate that a pending action is no longer returned by UnitActions.
   461  	actions, err := unit.PendingActions()
   462  	c.Assert(err, jc.ErrorIsNil)
   463  	c.Assert(len(actions), gc.Equals, 0)
   464  }
   465  
   466  func (s *ActionSuite) TestFindActionTagsByPrefix(c *gc.C) {
   467  	prefix := "feedbeef"
   468  	uuidMock := uuidMockHelper{}
   469  	uuidMock.SetPrefixMask(prefix)
   470  	s.PatchValue(&state.NewUUID, uuidMock.NewUUID)
   471  
   472  	actions := []struct {
   473  		Name       string
   474  		Parameters map[string]interface{}
   475  	}{
   476  		{Name: "action-1", Parameters: map[string]interface{}{}},
   477  		{Name: "fake", Parameters: map[string]interface{}{"yeah": true, "take": nil}},
   478  		{Name: "action-9", Parameters: map[string]interface{}{"district": 9}},
   479  		{Name: "blarney", Parameters: map[string]interface{}{"conversation": []string{"what", "now"}}},
   480  	}
   481  
   482  	for _, action := range actions {
   483  		_, err := s.State.EnqueueAction(s.unit.Tag(), action.Name, action.Parameters)
   484  		c.Assert(err, gc.Equals, nil)
   485  	}
   486  
   487  	tags := s.State.FindActionTagsByPrefix(prefix)
   488  
   489  	c.Assert(len(tags), gc.Equals, len(actions))
   490  	for i, tag := range tags {
   491  		c.Logf("check %q against %d:%q", prefix, i, tag)
   492  		c.Check(tag.Id()[:len(prefix)], gc.Equals, prefix)
   493  	}
   494  }
   495  
   496  func (s *ActionSuite) TestFindActionsByName(c *gc.C) {
   497  	actions := []struct {
   498  		Name       string
   499  		Parameters map[string]interface{}
   500  	}{
   501  		{Name: "action-1", Parameters: map[string]interface{}{}},
   502  		{Name: "fake", Parameters: map[string]interface{}{"yeah": true, "take": nil}},
   503  		{Name: "action-1", Parameters: map[string]interface{}{"yeah": true, "take": nil}},
   504  		{Name: "action-9", Parameters: map[string]interface{}{"district": 9}},
   505  		{Name: "blarney", Parameters: map[string]interface{}{"conversation": []string{"what", "now"}}},
   506  	}
   507  
   508  	for _, action := range actions {
   509  		_, err := s.State.EnqueueAction(s.unit.Tag(), action.Name, action.Parameters)
   510  		c.Assert(err, gc.Equals, nil)
   511  	}
   512  
   513  	results, err := s.State.FindActionsByName("action-1")
   514  	c.Assert(err, jc.ErrorIsNil)
   515  
   516  	c.Assert(len(results), gc.Equals, 2)
   517  	for _, result := range results {
   518  		c.Check(result.Name(), gc.Equals, "action-1")
   519  	}
   520  }
   521  
   522  func (s *ActionSuite) TestActionsWatcherEmitsInitialChanges(c *gc.C) {
   523  	// LP-1391914 :: idPrefixWatcher fails watcher contract to send
   524  	// initial Change event
   525  	//
   526  	// state/idPrefixWatcher does not send an initial event in response
   527  	// to the first time Changes() is called if all of the pending
   528  	// events are removed before the first consumption of Changes().
   529  	// The watcher contract specifies that the first call to Changes()
   530  	// should always return at a minimum an empty change set to notify
   531  	// clients of it's initial state
   532  
   533  	// preamble
   534  	svc := s.AddTestingService(c, "dummy3", s.charm)
   535  	unit, err := svc.AddUnit()
   536  	c.Assert(err, jc.ErrorIsNil)
   537  	u, err := s.State.Unit(unit.Name())
   538  	c.Assert(err, jc.ErrorIsNil)
   539  	preventUnitDestroyRemove(c, u)
   540  
   541  	// queue up actions
   542  	a1, err := u.AddAction("snapshot", nil)
   543  	c.Assert(err, jc.ErrorIsNil)
   544  	a2, err := u.AddAction("snapshot", nil)
   545  	c.Assert(err, jc.ErrorIsNil)
   546  
   547  	// start watcher but don't consume Changes() yet
   548  	w := u.WatchActionNotifications()
   549  	defer statetesting.AssertStop(c, w)
   550  	wc := statetesting.NewStringsWatcherC(c, s.State, w)
   551  
   552  	// remove actions
   553  	reason := "removed"
   554  	_, err = a1.Finish(state.ActionResults{Status: state.ActionFailed, Message: reason})
   555  	c.Assert(err, jc.ErrorIsNil)
   556  	_, err = a2.Finish(state.ActionResults{Status: state.ActionFailed, Message: reason})
   557  	c.Assert(err, jc.ErrorIsNil)
   558  
   559  	// per contract, there should be at minimum an initial empty Change() result
   560  	wc.AssertChangeMaybeIncluding(expectActionIds(a1, a2)...)
   561  	wc.AssertNoChange()
   562  }
   563  
   564  func (s *ActionSuite) TestUnitWatchActionNotifications(c *gc.C) {
   565  	// get units
   566  	unit1, err := s.State.Unit(s.unit.Name())
   567  	c.Assert(err, jc.ErrorIsNil)
   568  	preventUnitDestroyRemove(c, unit1)
   569  
   570  	unit2, err := s.State.Unit(s.unit2.Name())
   571  	c.Assert(err, jc.ErrorIsNil)
   572  	preventUnitDestroyRemove(c, unit2)
   573  
   574  	// queue some actions before starting the watcher
   575  	fa1, err := unit1.AddAction("snapshot", nil)
   576  	c.Assert(err, jc.ErrorIsNil)
   577  	fa2, err := unit1.AddAction("snapshot", nil)
   578  	c.Assert(err, jc.ErrorIsNil)
   579  
   580  	// set up watcher on first unit
   581  	w := unit1.WatchActionNotifications()
   582  	defer statetesting.AssertStop(c, w)
   583  	wc := statetesting.NewStringsWatcherC(c, s.State, w)
   584  	// make sure the previously pending actions are sent on the watcher
   585  	expect := expectActionIds(fa1, fa2)
   586  	wc.AssertChange(expect...)
   587  	wc.AssertNoChange()
   588  
   589  	// add watcher on unit2
   590  	w2 := unit2.WatchActionNotifications()
   591  	defer statetesting.AssertStop(c, w2)
   592  	wc2 := statetesting.NewStringsWatcherC(c, s.State, w2)
   593  	wc2.AssertChange()
   594  	wc2.AssertNoChange()
   595  
   596  	// add action on unit2 and makes sure unit1 watcher doesn't trigger
   597  	// and unit2 watcher does
   598  	fa3, err := unit2.AddAction("snapshot", nil)
   599  	c.Assert(err, jc.ErrorIsNil)
   600  	wc.AssertNoChange()
   601  	expect2 := expectActionIds(fa3)
   602  	wc2.AssertChange(expect2...)
   603  	wc2.AssertNoChange()
   604  
   605  	// add a couple actions on unit1 and make sure watcher sees events
   606  	fa4, err := unit1.AddAction("snapshot", nil)
   607  	c.Assert(err, jc.ErrorIsNil)
   608  	fa5, err := unit1.AddAction("snapshot", nil)
   609  	c.Assert(err, jc.ErrorIsNil)
   610  
   611  	expect = expectActionIds(fa4, fa5)
   612  	wc.AssertChange(expect...)
   613  	wc.AssertNoChange()
   614  }
   615  
   616  func (s *ActionSuite) TestMergeIds(c *gc.C) {
   617  	var tests = []struct {
   618  		changes  string
   619  		adds     string
   620  		removes  string
   621  		expected string
   622  	}{
   623  		{changes: "", adds: "a0,a1", removes: "", expected: "a0,a1"},
   624  		{changes: "a0,a1", adds: "", removes: "a0", expected: "a1"},
   625  		{changes: "a0,a1", adds: "a2", removes: "a0", expected: "a1,a2"},
   626  
   627  		{changes: "", adds: "a0,a1,a2", removes: "a0,a2", expected: "a1"},
   628  		{changes: "", adds: "a0,a1,a2", removes: "a0,a1,a2", expected: ""},
   629  
   630  		{changes: "a0", adds: "a0,a1,a2", removes: "a0,a2", expected: "a1"},
   631  		{changes: "a1", adds: "a0,a1,a2", removes: "a0,a2", expected: "a1"},
   632  		{changes: "a2", adds: "a0,a1,a2", removes: "a0,a2", expected: "a1"},
   633  
   634  		{changes: "a3,a4", adds: "a1,a4,a5", removes: "a1,a3", expected: "a4,a5"},
   635  		{changes: "a0,a1,a2", adds: "a1,a4,a5", removes: "a1,a3", expected: "a0,a2,a4,a5"},
   636  	}
   637  
   638  	prefix := state.DocID(s.State, "")
   639  
   640  	for ix, test := range tests {
   641  		updates := mapify(prefix, test.adds, test.removes)
   642  		changes := sliceify("", test.changes)
   643  		expected := sliceify("", test.expected)
   644  
   645  		c.Log(fmt.Sprintf("test number %d %#v", ix, test))
   646  		err := state.WatcherMergeIds(s.State, &changes, updates, state.ActionNotificationIdToActionId)
   647  		c.Assert(err, jc.ErrorIsNil)
   648  		c.Assert(changes, jc.SameContents, expected)
   649  	}
   650  }
   651  
   652  func (s *ActionSuite) TestMergeIdsErrors(c *gc.C) {
   653  
   654  	var tests = []struct {
   655  		name string
   656  		key  interface{}
   657  	}{
   658  		{name: "bool", key: true},
   659  		{name: "int", key: 0},
   660  		{name: "chan string", key: make(chan string)},
   661  	}
   662  
   663  	for _, test := range tests {
   664  		changes, updates := []string{}, map[interface{}]bool{}
   665  		updates[test.key] = true
   666  		err := state.WatcherMergeIds(s.State, &changes, updates, state.ActionNotificationIdToActionId)
   667  		c.Assert(err, gc.ErrorMatches, "id is not of type string, got "+test.name)
   668  	}
   669  }
   670  
   671  func (s *ActionSuite) TestEnsureSuffix(c *gc.C) {
   672  	marker := "-marker-"
   673  	fn := state.WatcherEnsureSuffixFn(marker)
   674  	c.Assert(fn, gc.Not(gc.IsNil))
   675  
   676  	var tests = []struct {
   677  		given  string
   678  		expect string
   679  	}{
   680  		{given: marker, expect: marker},
   681  		{given: "", expect: "" + marker},
   682  		{given: "asdf", expect: "asdf" + marker},
   683  		{given: "asdf" + marker, expect: "asdf" + marker},
   684  		{given: "asdf" + marker + "qwerty", expect: "asdf" + marker + "qwerty" + marker},
   685  	}
   686  
   687  	for _, test := range tests {
   688  		c.Assert(fn(test.given), gc.Equals, test.expect)
   689  	}
   690  }
   691  
   692  func (s *ActionSuite) TestMakeIdFilter(c *gc.C) {
   693  	marker := "-marker-"
   694  	badmarker := "-bad-"
   695  	fn := state.WatcherMakeIdFilter(s.State, marker)
   696  	c.Assert(fn, gc.IsNil)
   697  
   698  	ar1 := mockAR{id: "mock/1"}
   699  	ar2 := mockAR{id: "mock/2"}
   700  	fn = state.WatcherMakeIdFilter(s.State, marker, ar1, ar2)
   701  	c.Assert(fn, gc.Not(gc.IsNil))
   702  
   703  	var tests = []struct {
   704  		id    string
   705  		match bool
   706  	}{
   707  		{id: "mock/1" + marker + "", match: true},
   708  		{id: "mock/1" + marker + "asdf", match: true},
   709  		{id: "mock/2" + marker + "", match: true},
   710  		{id: "mock/2" + marker + "asdf", match: true},
   711  
   712  		{id: "mock/1" + badmarker + "", match: false},
   713  		{id: "mock/1" + badmarker + "asdf", match: false},
   714  		{id: "mock/2" + badmarker + "", match: false},
   715  		{id: "mock/2" + badmarker + "asdf", match: false},
   716  
   717  		{id: "mock/1" + marker + "0", match: true},
   718  		{id: "mock/10" + marker + "0", match: false},
   719  		{id: "mock/2" + marker + "0", match: true},
   720  		{id: "mock/20" + marker + "0", match: false},
   721  		{id: "mock" + marker + "0", match: false},
   722  
   723  		{id: "" + marker + "0", match: false},
   724  		{id: "mock/1-0", match: false},
   725  		{id: "mock/1-0", match: false},
   726  	}
   727  
   728  	for _, test := range tests {
   729  		c.Assert(fn(state.DocID(s.State, test.id)), gc.Equals, test.match)
   730  	}
   731  }
   732  
   733  func (s *ActionSuite) TestWatchActionNotifications(c *gc.C) {
   734  	svc := s.AddTestingService(c, "dummy2", s.charm)
   735  	u, err := svc.AddUnit()
   736  	c.Assert(err, jc.ErrorIsNil)
   737  
   738  	w := u.WatchActionNotifications()
   739  	defer statetesting.AssertStop(c, w)
   740  	wc := statetesting.NewStringsWatcherC(c, s.State, w)
   741  	wc.AssertChange()
   742  	wc.AssertNoChange()
   743  
   744  	// add 3 actions
   745  	fa1, err := u.AddAction("snapshot", nil)
   746  	c.Assert(err, jc.ErrorIsNil)
   747  	fa2, err := u.AddAction("snapshot", nil)
   748  	c.Assert(err, jc.ErrorIsNil)
   749  	fa3, err := u.AddAction("snapshot", nil)
   750  	c.Assert(err, jc.ErrorIsNil)
   751  
   752  	// fail the middle one
   753  	action, err := s.State.Action(fa2.Id())
   754  	c.Assert(err, jc.ErrorIsNil)
   755  	_, err = action.Finish(state.ActionResults{Status: state.ActionFailed, Message: "die scum"})
   756  	c.Assert(err, jc.ErrorIsNil)
   757  
   758  	// expect the first and last one in the watcher
   759  	expect := expectActionIds(fa1, fa3)
   760  	wc.AssertChange(expect...)
   761  	wc.AssertNoChange()
   762  }
   763  
   764  func (s *ActionSuite) TestActionStatusWatcher(c *gc.C) {
   765  	testCase := []struct {
   766  		receiver state.ActionReceiver
   767  		name     string
   768  		status   state.ActionStatus
   769  	}{
   770  		{s.unit, "snapshot", state.ActionCancelled},
   771  		{s.unit2, "snapshot", state.ActionCancelled},
   772  		{s.unit, "snapshot", state.ActionPending},
   773  		{s.unit2, "snapshot", state.ActionPending},
   774  		{s.unit, "snapshot", state.ActionFailed},
   775  		{s.unit2, "snapshot", state.ActionFailed},
   776  		{s.unit, "snapshot", state.ActionCompleted},
   777  		{s.unit2, "snapshot", state.ActionCompleted},
   778  	}
   779  
   780  	w1 := state.NewActionStatusWatcher(s.State, []state.ActionReceiver{s.unit})
   781  	defer statetesting.AssertStop(c, w1)
   782  
   783  	w2 := state.NewActionStatusWatcher(s.State, []state.ActionReceiver{s.unit}, state.ActionFailed)
   784  	defer statetesting.AssertStop(c, w2)
   785  
   786  	w3 := state.NewActionStatusWatcher(s.State, []state.ActionReceiver{s.unit}, state.ActionCancelled, state.ActionCompleted)
   787  	defer statetesting.AssertStop(c, w3)
   788  
   789  	watchAny := statetesting.NewStringsWatcherC(c, s.State, w1)
   790  	watchAny.AssertChange()
   791  	watchAny.AssertNoChange()
   792  
   793  	watchFailed := statetesting.NewStringsWatcherC(c, s.State, w2)
   794  	watchFailed.AssertChange()
   795  	watchFailed.AssertNoChange()
   796  
   797  	watchCancelledOrCompleted := statetesting.NewStringsWatcherC(c, s.State, w3)
   798  	watchCancelledOrCompleted.AssertChange()
   799  	watchCancelledOrCompleted.AssertNoChange()
   800  
   801  	expect := map[state.ActionStatus][]state.Action{}
   802  	all := []state.Action{}
   803  	for _, tcase := range testCase {
   804  		a, err := tcase.receiver.AddAction(tcase.name, nil)
   805  		c.Assert(err, jc.ErrorIsNil)
   806  
   807  		action, err := s.State.Action(a.Id())
   808  		c.Assert(err, jc.ErrorIsNil)
   809  
   810  		_, err = action.Finish(state.ActionResults{Status: tcase.status})
   811  		c.Assert(err, jc.ErrorIsNil)
   812  
   813  		if tcase.receiver == s.unit {
   814  			expect[tcase.status] = append(expect[tcase.status], action)
   815  			all = append(all, action)
   816  		}
   817  	}
   818  
   819  	watchAny.AssertChange(expectActionIds(all...)...)
   820  	watchAny.AssertNoChange()
   821  
   822  	watchFailed.AssertChange(expectActionIds(expect[state.ActionFailed]...)...)
   823  	watchFailed.AssertNoChange()
   824  
   825  	cancelledAndCompleted := expectActionIds(append(expect[state.ActionCancelled], expect[state.ActionCompleted]...)...)
   826  	watchCancelledOrCompleted.AssertChange(cancelledAndCompleted...)
   827  	watchCancelledOrCompleted.AssertNoChange()
   828  }
   829  
   830  func expectActionIds(actions ...state.Action) []string {
   831  	ids := make([]string, len(actions))
   832  	for i, action := range actions {
   833  		ids[i] = action.Id()
   834  	}
   835  	return ids
   836  }
   837  
   838  // mapify is a convenience method, also to make reading the tests
   839  // easier. It combines two comma delimited strings representing
   840  // additions and removals and turns it into the map[interface{}]bool
   841  // format needed
   842  func mapify(prefix, adds, removes string) map[interface{}]bool {
   843  	m := map[interface{}]bool{}
   844  	for _, v := range sliceify(prefix, adds) {
   845  		m[v] = true
   846  	}
   847  	for _, v := range sliceify(prefix, removes) {
   848  		m[v] = false
   849  	}
   850  	return m
   851  }
   852  
   853  // sliceify turns a comma separated list of strings into a slice
   854  // trimming white space and excluding empty strings.
   855  func sliceify(prefix, csvlist string) []string {
   856  	slice := []string{}
   857  	if csvlist == "" {
   858  		return slice
   859  	}
   860  	for _, entry := range strings.Split(csvlist, ",") {
   861  		clean := strings.TrimSpace(entry)
   862  		if clean != "" {
   863  			slice = append(slice, prefix+clean)
   864  		}
   865  	}
   866  	return slice
   867  }
   868  
   869  // mockAR is an implementation of ActionReceiver that can be used for
   870  // testing that requires the ActionReceiver.Tag() call to return a
   871  // names.Tag
   872  type mockAR struct {
   873  	id string
   874  }
   875  
   876  var _ state.ActionReceiver = (*mockAR)(nil)
   877  
   878  func (r mockAR) AddAction(name string, payload map[string]interface{}) (state.Action, error) {
   879  	return nil, nil
   880  }
   881  func (r mockAR) CancelAction(state.Action) (state.Action, error) { return nil, nil }
   882  func (r mockAR) WatchActionNotifications() state.StringsWatcher  { return nil }
   883  func (r mockAR) Actions() ([]state.Action, error)                { return nil, nil }
   884  func (r mockAR) CompletedActions() ([]state.Action, error)       { return nil, nil }
   885  func (r mockAR) PendingActions() ([]state.Action, error)         { return nil, nil }
   886  func (r mockAR) RunningActions() ([]state.Action, error)         { return nil, nil }
   887  func (r mockAR) Tag() names.Tag                                  { return names.NewUnitTag(r.id) }
   888  
   889  // TestMock verifies the mock UUID generator works as expected.
   890  func (s *ActionSuite) TestMock(c *gc.C) {
   891  	prefix := "abbadead"
   892  	uuidMock := uuidMockHelper{}
   893  	uuidMock.SetPrefixMask(prefix)
   894  	s.PatchValue(&state.NewUUID, uuidMock.NewUUID)
   895  	for i := 0; i < 10; i++ {
   896  		uuid, err := state.NewUUID()
   897  		c.Check(err, jc.ErrorIsNil)
   898  		c.Check(uuid.String()[:len(prefix)], gc.Equals, prefix)
   899  	}
   900  }
   901  
   902  type uuidGenFn func() (utils.UUID, error)
   903  type uuidMockHelper struct {
   904  	original   uuidGenFn
   905  	prefixMask []byte
   906  }
   907  
   908  func (h *uuidMockHelper) SetPrefixMask(prefix string) error {
   909  	prefix = strings.Replace(prefix, "-", "", 4)
   910  	mask, err := hex.DecodeString(prefix)
   911  	if err != nil {
   912  		return err
   913  	}
   914  	if len(mask) > 16 {
   915  		return errors.Errorf("prefix mask longer than uuid %q", prefix)
   916  	}
   917  	h.prefixMask = mask
   918  	return nil
   919  }
   920  
   921  func (h *uuidMockHelper) NewUUID() (utils.UUID, error) {
   922  	uuidGenFn := h.original
   923  	if uuidGenFn == nil {
   924  		uuidGenFn = utils.NewUUID
   925  	}
   926  	uuid, err := uuidGenFn()
   927  	if err != nil {
   928  		return uuid, errors.Trace(err)
   929  	}
   930  	return h.mask(uuid), nil
   931  }
   932  
   933  func (h *uuidMockHelper) mask(uuid utils.UUID) utils.UUID {
   934  	if len(h.prefixMask) > 0 {
   935  		for i, b := range h.prefixMask {
   936  			uuid[i] = b
   937  		}
   938  	}
   939  	return uuid
   940  }