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

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package action_test
     5  
     6  import (
     7  	"fmt"
     8  	"testing"
     9  
    10  	"github.com/juju/errors"
    11  	jc "github.com/juju/testing/checkers"
    12  	gc "gopkg.in/check.v1"
    13  	"gopkg.in/juju/names.v2"
    14  
    15  	"github.com/juju/juju/apiserver/action"
    16  	"github.com/juju/juju/apiserver/common"
    17  	commontesting "github.com/juju/juju/apiserver/common/testing"
    18  	"github.com/juju/juju/apiserver/params"
    19  	apiservertesting "github.com/juju/juju/apiserver/testing"
    20  	jujutesting "github.com/juju/juju/juju/testing"
    21  	"github.com/juju/juju/state"
    22  	coretesting "github.com/juju/juju/testing"
    23  	jujuFactory "github.com/juju/juju/testing/factory"
    24  )
    25  
    26  func TestAll(t *testing.T) {
    27  	coretesting.MgoTestPackage(t)
    28  }
    29  
    30  type actionSuite struct {
    31  	jujutesting.JujuConnSuite
    32  	commontesting.BlockHelper
    33  
    34  	action     *action.ActionAPI
    35  	authorizer apiservertesting.FakeAuthorizer
    36  	resources  *common.Resources
    37  
    38  	charm         *state.Charm
    39  	machine0      *state.Machine
    40  	machine1      *state.Machine
    41  	dummy         *state.Application
    42  	wordpress     *state.Application
    43  	mysql         *state.Application
    44  	wordpressUnit *state.Unit
    45  	mysqlUnit     *state.Unit
    46  }
    47  
    48  var _ = gc.Suite(&actionSuite{})
    49  
    50  func (s *actionSuite) SetUpTest(c *gc.C) {
    51  	s.JujuConnSuite.SetUpTest(c)
    52  	s.BlockHelper = commontesting.NewBlockHelper(s.APIState)
    53  	s.AddCleanup(func(*gc.C) { s.BlockHelper.Close() })
    54  
    55  	s.authorizer = apiservertesting.FakeAuthorizer{
    56  		Tag: s.AdminUserTag(c),
    57  	}
    58  	var err error
    59  	s.action, err = action.NewActionAPI(s.State, nil, s.authorizer)
    60  	c.Assert(err, jc.ErrorIsNil)
    61  
    62  	factory := jujuFactory.NewFactory(s.State)
    63  
    64  	s.charm = factory.MakeCharm(c, &jujuFactory.CharmParams{
    65  		Name: "wordpress",
    66  	})
    67  
    68  	s.dummy = factory.MakeApplication(c, &jujuFactory.ApplicationParams{
    69  		Name: "dummy",
    70  		Charm: factory.MakeCharm(c, &jujuFactory.CharmParams{
    71  			Name: "dummy",
    72  		}),
    73  	})
    74  	s.wordpress = factory.MakeApplication(c, &jujuFactory.ApplicationParams{
    75  		Name:  "wordpress",
    76  		Charm: s.charm,
    77  	})
    78  	s.machine0 = factory.MakeMachine(c, &jujuFactory.MachineParams{
    79  		Series: "quantal",
    80  		Jobs:   []state.MachineJob{state.JobHostUnits, state.JobManageModel},
    81  	})
    82  	s.wordpressUnit = factory.MakeUnit(c, &jujuFactory.UnitParams{
    83  		Application: s.wordpress,
    84  		Machine:     s.machine0,
    85  	})
    86  
    87  	mysqlCharm := factory.MakeCharm(c, &jujuFactory.CharmParams{
    88  		Name: "mysql",
    89  	})
    90  	s.mysql = factory.MakeApplication(c, &jujuFactory.ApplicationParams{
    91  		Name:  "mysql",
    92  		Charm: mysqlCharm,
    93  	})
    94  	s.machine1 = factory.MakeMachine(c, &jujuFactory.MachineParams{
    95  		Series: "quantal",
    96  		Jobs:   []state.MachineJob{state.JobHostUnits},
    97  	})
    98  	s.mysqlUnit = factory.MakeUnit(c, &jujuFactory.UnitParams{
    99  		Application: s.mysql,
   100  		Machine:     s.machine1,
   101  	})
   102  	s.resources = common.NewResources()
   103  	s.AddCleanup(func(_ *gc.C) { s.resources.StopAll() })
   104  }
   105  
   106  func (s *actionSuite) AssertBlocked(c *gc.C, err error, msg string) {
   107  	c.Assert(params.IsCodeOperationBlocked(err), jc.IsTrue, gc.Commentf("error: %#v", err))
   108  	c.Assert(errors.Cause(err), gc.DeepEquals, &params.Error{
   109  		Message: msg,
   110  		Code:    "operation is blocked",
   111  	})
   112  }
   113  
   114  func (s *actionSuite) TestBlockEnqueue(c *gc.C) {
   115  	// block all changes
   116  	s.BlockAllChanges(c, "Enqueue")
   117  	_, err := s.action.Enqueue(params.Actions{})
   118  	s.AssertBlocked(c, err, "Enqueue")
   119  }
   120  
   121  func (s *actionSuite) TestBlockCancel(c *gc.C) {
   122  	// block all changes
   123  	s.BlockAllChanges(c, "Cancel")
   124  	_, err := s.action.Cancel(params.Entities{})
   125  	s.AssertBlocked(c, err, "Cancel")
   126  }
   127  
   128  func (s *actionSuite) TestActions(c *gc.C) {
   129  	arg := params.Actions{
   130  		Actions: []params.Action{
   131  			{Receiver: s.wordpressUnit.Tag().String(), Name: "fakeaction", Parameters: map[string]interface{}{}},
   132  			{Receiver: s.mysqlUnit.Tag().String(), Name: "fakeaction", Parameters: map[string]interface{}{}},
   133  			{Receiver: s.wordpressUnit.Tag().String(), Name: "fakeaction", Parameters: map[string]interface{}{"foo": 1, "bar": "please"}},
   134  			{Receiver: s.mysqlUnit.Tag().String(), Name: "fakeaction", Parameters: map[string]interface{}{"baz": true}},
   135  		}}
   136  
   137  	r, err := s.action.Enqueue(arg)
   138  	c.Assert(err, gc.Equals, nil)
   139  	c.Assert(r.Results, gc.HasLen, len(arg.Actions))
   140  
   141  	entities := make([]params.Entity, len(r.Results))
   142  	for i, result := range r.Results {
   143  		entities[i] = params.Entity{Tag: result.Action.Tag}
   144  	}
   145  
   146  	actions, err := s.action.Actions(params.Entities{Entities: entities})
   147  	c.Assert(err, gc.Equals, nil)
   148  
   149  	c.Assert(len(actions.Results), gc.Equals, len(entities))
   150  	for i, got := range actions.Results {
   151  		c.Logf("check index %d (%s: %s)", i, entities[i].Tag, arg.Actions[i].Name)
   152  		c.Assert(got.Error, gc.Equals, (*params.Error)(nil))
   153  		c.Assert(got.Action, gc.Not(gc.Equals), (*params.Action)(nil))
   154  		c.Assert(got.Action.Tag, gc.Equals, entities[i].Tag)
   155  		c.Assert(got.Action.Name, gc.Equals, arg.Actions[i].Name)
   156  		c.Assert(got.Action.Receiver, gc.Equals, arg.Actions[i].Receiver)
   157  		c.Assert(got.Action.Parameters, gc.DeepEquals, arg.Actions[i].Parameters)
   158  		c.Assert(got.Status, gc.Equals, params.ActionPending)
   159  		c.Assert(got.Message, gc.Equals, "")
   160  		c.Assert(got.Output, gc.DeepEquals, map[string]interface{}{})
   161  	}
   162  }
   163  
   164  func (s *actionSuite) TestFindActionTagsByPrefix(c *gc.C) {
   165  	// NOTE: full testing with multiple matches has been moved to state package.
   166  	arg := params.Actions{Actions: []params.Action{{Receiver: s.wordpressUnit.Tag().String(), Name: "fakeaction", Parameters: map[string]interface{}{}}}}
   167  	r, err := s.action.Enqueue(arg)
   168  	c.Assert(err, gc.Equals, nil)
   169  	c.Assert(r.Results, gc.HasLen, len(arg.Actions))
   170  
   171  	actionTag, err := names.ParseActionTag(r.Results[0].Action.Tag)
   172  	c.Assert(err, gc.Equals, nil)
   173  	prefix := actionTag.Id()[:7]
   174  	tags, err := s.action.FindActionTagsByPrefix(params.FindTags{Prefixes: []string{prefix}})
   175  	c.Assert(err, gc.Equals, nil)
   176  
   177  	entities, ok := tags.Matches[prefix]
   178  	c.Assert(ok, gc.Equals, true)
   179  	c.Assert(len(entities), gc.Equals, 1)
   180  	c.Assert(entities[0].Tag, gc.Equals, actionTag.String())
   181  }
   182  
   183  func (s *actionSuite) TestFindActionsByName(c *gc.C) {
   184  	machine := s.JujuConnSuite.Factory.MakeMachine(c, &jujuFactory.MachineParams{
   185  		Series: "quantal",
   186  		Jobs:   []state.MachineJob{state.JobHostUnits},
   187  	})
   188  	dummyUnit := s.JujuConnSuite.Factory.MakeUnit(c, &jujuFactory.UnitParams{
   189  		Application: s.dummy,
   190  		Machine:     machine,
   191  	})
   192  	// NOTE: full testing with multiple matches has been moved to state package.
   193  	arg := params.Actions{Actions: []params.Action{
   194  		{Receiver: s.wordpressUnit.Tag().String(), Name: "fakeaction", Parameters: map[string]interface{}{}},
   195  		{Receiver: dummyUnit.Tag().String(), Name: "snapshot", Parameters: map[string]interface{}{"outfile": "lol"}},
   196  		{Receiver: s.wordpressUnit.Tag().String(), Name: "juju-run", Parameters: map[string]interface{}{"command": "boo", "timeout": 5}},
   197  		{Receiver: s.mysqlUnit.Tag().String(), Name: "juju-run", Parameters: map[string]interface{}{"command": "boo", "timeout": 5}},
   198  	}}
   199  	r, err := s.action.Enqueue(arg)
   200  	c.Assert(err, gc.Equals, nil)
   201  	c.Assert(r.Results, gc.HasLen, len(arg.Actions))
   202  
   203  	actionNames := []string{"snapshot", "juju-run"}
   204  	actions, err := s.action.FindActionsByNames(params.FindActionsByNames{ActionNames: actionNames})
   205  	c.Assert(err, jc.ErrorIsNil)
   206  
   207  	c.Assert(len(actions.Actions), gc.Equals, 2)
   208  	for i, actions := range actions.Actions {
   209  		for _, action := range actions.Actions {
   210  			c.Assert(action.Action.Name, gc.Equals, actionNames[i])
   211  			c.Assert(action.Action.Name, gc.Matches, actions.Name)
   212  		}
   213  	}
   214  }
   215  
   216  func (s *actionSuite) TestEnqueue(c *gc.C) {
   217  	// Make sure no Actions already exist on wordpress Unit.
   218  	actions, err := s.wordpressUnit.Actions()
   219  	c.Assert(err, jc.ErrorIsNil)
   220  	c.Assert(actions, gc.HasLen, 0)
   221  
   222  	// Make sure no Actions already exist on mysql Unit.
   223  	actions, err = s.mysqlUnit.Actions()
   224  	c.Assert(err, jc.ErrorIsNil)
   225  	c.Assert(actions, gc.HasLen, 0)
   226  
   227  	// Add Actions.
   228  	expectedName := "fakeaction"
   229  	expectedParameters := map[string]interface{}{"kan jy nie": "verstaand"}
   230  	arg := params.Actions{
   231  		Actions: []params.Action{
   232  			// No receiver.
   233  			{Name: "fakeaction"},
   234  			// Good.
   235  			{Receiver: s.wordpressUnit.Tag().String(), Name: expectedName, Parameters: expectedParameters},
   236  			// Service tag instead of Unit tag.
   237  			{Receiver: s.wordpress.Tag().String(), Name: "fakeaction"},
   238  			// Missing name.
   239  			{Receiver: s.mysqlUnit.Tag().String(), Parameters: expectedParameters},
   240  		},
   241  	}
   242  	res, err := s.action.Enqueue(arg)
   243  	c.Assert(err, jc.ErrorIsNil)
   244  	c.Assert(res.Results, gc.HasLen, 4)
   245  
   246  	expectedError := &params.Error{Message: "id not found", Code: "not found"}
   247  	emptyActionTag := names.ActionTag{}
   248  	c.Assert(res.Results[0].Error, gc.DeepEquals, expectedError)
   249  	c.Assert(res.Results[0].Action, gc.IsNil)
   250  
   251  	c.Assert(res.Results[1].Error, gc.IsNil)
   252  	c.Assert(res.Results[1].Action, gc.NotNil)
   253  	c.Assert(res.Results[1].Action.Receiver, gc.Equals, s.wordpressUnit.Tag().String())
   254  	c.Assert(res.Results[1].Action.Tag, gc.Not(gc.Equals), emptyActionTag)
   255  
   256  	c.Assert(res.Results[2].Error, gc.DeepEquals, expectedError)
   257  	c.Assert(res.Results[2].Action, gc.IsNil)
   258  
   259  	c.Assert(res.Results[3].Error, gc.ErrorMatches, "no action name given")
   260  	c.Assert(res.Results[3].Action, gc.IsNil)
   261  
   262  	// Make sure an Action was enqueued for the wordpress Unit.
   263  	actions, err = s.wordpressUnit.Actions()
   264  	c.Assert(err, jc.ErrorIsNil)
   265  	c.Assert(actions, gc.HasLen, 1)
   266  	c.Assert(actions[0].Name(), gc.Equals, expectedName)
   267  	c.Assert(actions[0].Parameters(), gc.DeepEquals, expectedParameters)
   268  	c.Assert(actions[0].Receiver(), gc.Equals, s.wordpressUnit.Name())
   269  
   270  	// Make sure an Action was not enqueued for the mysql Unit.
   271  	actions, err = s.mysqlUnit.Actions()
   272  	c.Assert(err, jc.ErrorIsNil)
   273  	c.Assert(actions, gc.HasLen, 0)
   274  }
   275  
   276  type testCaseAction struct {
   277  	Name       string
   278  	Parameters map[string]interface{}
   279  	Execute    bool
   280  }
   281  
   282  type receiverGroup struct {
   283  	ExpectedError *params.Error
   284  	Receiver      names.Tag
   285  	Actions       []testCaseAction
   286  }
   287  
   288  type testCase struct {
   289  	Groups []receiverGroup
   290  }
   291  
   292  var testCases = []testCase{{
   293  	Groups: []receiverGroup{
   294  		{
   295  			ExpectedError: &params.Error{Message: "id not found", Code: "not found"},
   296  			Receiver:      names.NewApplicationTag("wordpress"),
   297  			Actions:       []testCaseAction{},
   298  		}, {
   299  			Receiver: names.NewUnitTag("wordpress/0"),
   300  			Actions: []testCaseAction{
   301  				{"fakeaction", map[string]interface{}{}, false},
   302  				{"fakeaction", map[string]interface{}{"asdf": 3}, true},
   303  				{"fakeaction", map[string]interface{}{"qwer": "ty"}, false},
   304  			},
   305  		}, {
   306  			Receiver: names.NewUnitTag("mysql/0"),
   307  			Actions: []testCaseAction{
   308  				{"fakeaction", map[string]interface{}{"zxcv": false}, false},
   309  				{"fakeaction", map[string]interface{}{}, true},
   310  			},
   311  		},
   312  	},
   313  }}
   314  
   315  func (s *actionSuite) TestListAll(c *gc.C) {
   316  	for _, testCase := range testCases {
   317  		// set up query args
   318  		arg := params.Entities{Entities: make([]params.Entity, len(testCase.Groups))}
   319  
   320  		// prepare state, and set up expectations.
   321  		expected := params.ActionsByReceivers{Actions: make([]params.ActionsByReceiver, len(testCase.Groups))}
   322  		for i, group := range testCase.Groups {
   323  			arg.Entities[i] = params.Entity{Tag: group.Receiver.String()}
   324  
   325  			cur := &expected.Actions[i]
   326  			cur.Error = group.ExpectedError
   327  
   328  			// short circuit and bail if the ActionReceiver isn't a Unit.
   329  			if _, ok := group.Receiver.(names.UnitTag); !ok {
   330  				continue
   331  			}
   332  
   333  			cur.Receiver = group.Receiver.String()
   334  			cur.Actions = make([]params.ActionResult, len(group.Actions))
   335  
   336  			// get Unit (ActionReceiver) for this Pair in the test case.
   337  			unit, err := s.State.Unit(group.Receiver.Id())
   338  			c.Assert(err, jc.ErrorIsNil)
   339  			assertReadyToTest(c, unit)
   340  
   341  			// add each action from the test case.
   342  			for j, action := range group.Actions {
   343  				// add action.
   344  				added, err := unit.AddAction(action.Name, action.Parameters)
   345  				c.Assert(err, jc.ErrorIsNil)
   346  
   347  				// make expectation
   348  				exp := &cur.Actions[j]
   349  				exp.Action = &params.Action{
   350  					Tag:        added.ActionTag().String(),
   351  					Name:       action.Name,
   352  					Parameters: action.Parameters,
   353  				}
   354  				exp.Status = params.ActionPending
   355  				exp.Output = map[string]interface{}{}
   356  
   357  				if action.Execute {
   358  					status := state.ActionCompleted
   359  					output := map[string]interface{}{"output": "blah, blah, blah"}
   360  					message := "success"
   361  
   362  					fa, err := added.Finish(state.ActionResults{Status: status, Results: output, Message: message})
   363  					c.Assert(err, jc.ErrorIsNil)
   364  					c.Assert(fa.Status(), gc.Equals, state.ActionCompleted)
   365  
   366  					exp.Status = string(status)
   367  					exp.Message = message
   368  					exp.Output = output
   369  				}
   370  			}
   371  		}
   372  
   373  		// validate assumptions.
   374  		actionList, err := s.action.ListAll(arg)
   375  		c.Assert(err, jc.ErrorIsNil)
   376  		assertSame(c, actionList, expected)
   377  	}
   378  }
   379  
   380  func (s *actionSuite) TestListPending(c *gc.C) {
   381  	for _, testCase := range testCases {
   382  		// set up query args
   383  		arg := params.Entities{Entities: make([]params.Entity, len(testCase.Groups))}
   384  
   385  		// prepare state, and set up expectations.
   386  		expected := params.ActionsByReceivers{Actions: make([]params.ActionsByReceiver, len(testCase.Groups))}
   387  		for i, group := range testCase.Groups {
   388  			arg.Entities[i] = params.Entity{Tag: group.Receiver.String()}
   389  
   390  			cur := &expected.Actions[i]
   391  			cur.Error = group.ExpectedError
   392  
   393  			// short circuit and bail if the ActionReceiver isn't a Unit.
   394  			if _, ok := group.Receiver.(names.UnitTag); !ok {
   395  				continue
   396  			}
   397  
   398  			cur.Receiver = group.Receiver.String()
   399  			cur.Actions = []params.ActionResult{}
   400  
   401  			// get Unit (ActionReceiver) for this Pair in the test case.
   402  			unit, err := s.State.Unit(group.Receiver.Id())
   403  			c.Assert(err, jc.ErrorIsNil)
   404  			assertReadyToTest(c, unit)
   405  
   406  			// add each action from the test case.
   407  			for _, action := range group.Actions {
   408  				// add action.
   409  				added, err := unit.AddAction(action.Name, action.Parameters)
   410  				c.Assert(err, jc.ErrorIsNil)
   411  
   412  				if action.Execute {
   413  					status := state.ActionCompleted
   414  					output := map[string]interface{}{"output": "blah, blah, blah"}
   415  					message := "success"
   416  
   417  					fa, err := added.Finish(state.ActionResults{Status: status, Results: output, Message: message})
   418  					c.Assert(err, jc.ErrorIsNil)
   419  					c.Assert(fa.Status(), gc.Equals, state.ActionCompleted)
   420  				} else {
   421  					// add expectation
   422  					exp := params.ActionResult{
   423  						Action: &params.Action{
   424  							Tag:        added.ActionTag().String(),
   425  							Name:       action.Name,
   426  							Parameters: action.Parameters,
   427  						},
   428  						Status: params.ActionPending,
   429  						Output: map[string]interface{}{},
   430  					}
   431  					cur.Actions = append(cur.Actions, exp)
   432  				}
   433  			}
   434  		}
   435  
   436  		// validate assumptions.
   437  		actionList, err := s.action.ListPending(arg)
   438  		c.Assert(err, jc.ErrorIsNil)
   439  		assertSame(c, actionList, expected)
   440  	}
   441  }
   442  
   443  func (s *actionSuite) TestListRunning(c *gc.C) {
   444  	for _, testCase := range testCases {
   445  		// set up query args
   446  		arg := params.Entities{Entities: make([]params.Entity, len(testCase.Groups))}
   447  
   448  		// prepare state, and set up expectations.
   449  		expected := params.ActionsByReceivers{Actions: make([]params.ActionsByReceiver, len(testCase.Groups))}
   450  		for i, group := range testCase.Groups {
   451  			arg.Entities[i] = params.Entity{Tag: group.Receiver.String()}
   452  
   453  			cur := &expected.Actions[i]
   454  			cur.Error = group.ExpectedError
   455  
   456  			// short circuit and bail if the ActionReceiver isn't a Unit.
   457  			if _, ok := group.Receiver.(names.UnitTag); !ok {
   458  				continue
   459  			}
   460  
   461  			cur.Receiver = group.Receiver.String()
   462  			cur.Actions = []params.ActionResult{}
   463  
   464  			// get Unit (ActionReceiver) for this Pair in the test case.
   465  			unit, err := s.State.Unit(group.Receiver.Id())
   466  			c.Assert(err, jc.ErrorIsNil)
   467  			assertReadyToTest(c, unit)
   468  
   469  			// add each action from the test case.
   470  			for _, action := range group.Actions {
   471  				// add action.
   472  				added, err := unit.AddAction(action.Name, action.Parameters)
   473  				c.Assert(err, jc.ErrorIsNil)
   474  
   475  				if action.Execute {
   476  					started, err := added.Begin()
   477  					c.Assert(err, jc.ErrorIsNil)
   478  					c.Assert(started.Status(), gc.Equals, state.ActionRunning)
   479  
   480  					// add expectation
   481  					exp := params.ActionResult{
   482  						Action: &params.Action{
   483  							Tag:        added.ActionTag().String(),
   484  							Name:       action.Name,
   485  							Parameters: action.Parameters,
   486  						},
   487  						Status: params.ActionRunning,
   488  						Output: map[string]interface{}{},
   489  					}
   490  					cur.Actions = append(cur.Actions, exp)
   491  				}
   492  			}
   493  		}
   494  
   495  		// validate assumptions.
   496  		actionList, err := s.action.ListRunning(arg)
   497  		c.Assert(err, jc.ErrorIsNil)
   498  		assertSame(c, actionList, expected)
   499  	}
   500  }
   501  
   502  func (s *actionSuite) TestListCompleted(c *gc.C) {
   503  	for _, testCase := range testCases {
   504  		// set up query args
   505  		arg := params.Entities{Entities: make([]params.Entity, len(testCase.Groups))}
   506  
   507  		// prepare state, and set up expectations.
   508  		expected := params.ActionsByReceivers{Actions: make([]params.ActionsByReceiver, len(testCase.Groups))}
   509  		for i, group := range testCase.Groups {
   510  			arg.Entities[i] = params.Entity{Tag: group.Receiver.String()}
   511  
   512  			cur := &expected.Actions[i]
   513  			cur.Error = group.ExpectedError
   514  
   515  			// short circuit and bail if the ActionReceiver isn't a Unit.
   516  			if _, ok := group.Receiver.(names.UnitTag); !ok {
   517  				continue
   518  			}
   519  
   520  			cur.Receiver = group.Receiver.String()
   521  			cur.Actions = []params.ActionResult{}
   522  
   523  			// get Unit (ActionReceiver) for this Pair in the test case.
   524  			unit, err := s.State.Unit(group.Receiver.Id())
   525  			c.Assert(err, jc.ErrorIsNil)
   526  			assertReadyToTest(c, unit)
   527  
   528  			// add each action from the test case.
   529  			for _, action := range group.Actions {
   530  				// add action.
   531  				added, err := unit.AddAction(action.Name, action.Parameters)
   532  				c.Assert(err, jc.ErrorIsNil)
   533  
   534  				if action.Execute {
   535  					status := state.ActionCompleted
   536  					output := map[string]interface{}{"output": "blah, blah, blah"}
   537  					message := "success"
   538  
   539  					_, err = added.Finish(state.ActionResults{Status: status, Results: output, Message: message})
   540  					c.Assert(err, jc.ErrorIsNil)
   541  
   542  					// add expectation
   543  					exp := params.ActionResult{
   544  						Action: &params.Action{
   545  							Tag:        added.ActionTag().String(),
   546  							Name:       action.Name,
   547  							Parameters: action.Parameters,
   548  						},
   549  						Status:  string(status),
   550  						Message: message,
   551  						Output:  output,
   552  					}
   553  					cur.Actions = append(cur.Actions, exp)
   554  				}
   555  			}
   556  		}
   557  
   558  		// validate assumptions.
   559  		actionList, err := s.action.ListCompleted(arg)
   560  		c.Assert(err, jc.ErrorIsNil)
   561  		assertSame(c, actionList, expected)
   562  	}
   563  }
   564  
   565  func (s *actionSuite) TestCancel(c *gc.C) {
   566  	// Make sure no Actions already exist on wordpress Unit.
   567  	actions, err := s.wordpressUnit.Actions()
   568  	c.Assert(err, jc.ErrorIsNil)
   569  	c.Assert(actions, gc.HasLen, 0)
   570  
   571  	// Make sure no Actions already exist on mysql Unit.
   572  	actions, err = s.mysqlUnit.Actions()
   573  	c.Assert(err, jc.ErrorIsNil)
   574  	c.Assert(actions, gc.HasLen, 0)
   575  
   576  	// Add Actions.
   577  	tests := params.Actions{
   578  		Actions: []params.Action{{
   579  			Receiver: s.wordpressUnit.Tag().String(),
   580  			Name:     "fakeaction",
   581  		}, {
   582  			Receiver: s.wordpressUnit.Tag().String(),
   583  			Name:     "fakeaction",
   584  		}, {
   585  			Receiver: s.mysqlUnit.Tag().String(),
   586  			Name:     "fakeaction",
   587  		}, {
   588  			Receiver: s.mysqlUnit.Tag().String(),
   589  			Name:     "fakeaction",
   590  		}},
   591  	}
   592  
   593  	results, err := s.action.Enqueue(tests)
   594  	c.Assert(err, jc.ErrorIsNil)
   595  	c.Assert(results.Results, gc.HasLen, 4)
   596  	for _, res := range results.Results {
   597  		c.Assert(res.Error, gc.IsNil)
   598  	}
   599  
   600  	// Cancel Some.
   601  	arg := params.Entities{
   602  		Entities: []params.Entity{
   603  			// "wp-two"
   604  			{Tag: results.Results[1].Action.Tag},
   605  			// "my-one"
   606  			{Tag: results.Results[2].Action.Tag},
   607  		}}
   608  	results, err = s.action.Cancel(arg)
   609  	c.Assert(err, jc.ErrorIsNil)
   610  	c.Assert(results.Results, gc.HasLen, 2)
   611  
   612  	// Assert the Actions are all in the expected state.
   613  	tags := params.Entities{Entities: []params.Entity{{Tag: s.wordpressUnit.Tag().String()}, {Tag: s.mysqlUnit.Tag().String()}}}
   614  	obtained, err := s.action.ListAll(tags)
   615  	c.Assert(err, jc.ErrorIsNil)
   616  	c.Assert(obtained.Actions, gc.HasLen, 2)
   617  
   618  	wpActions := obtained.Actions[0].Actions
   619  	c.Assert(wpActions, gc.HasLen, 2)
   620  	c.Assert(wpActions[0].Action.Name, gc.Equals, "fakeaction")
   621  	c.Assert(wpActions[0].Status, gc.Equals, params.ActionPending)
   622  	c.Assert(wpActions[1].Action.Name, gc.Equals, "fakeaction")
   623  	c.Assert(wpActions[1].Status, gc.Equals, params.ActionCancelled)
   624  
   625  	myActions := obtained.Actions[1].Actions
   626  	c.Assert(myActions, gc.HasLen, 2)
   627  	c.Assert(myActions[0].Action.Name, gc.Equals, "fakeaction")
   628  	c.Assert(myActions[0].Status, gc.Equals, params.ActionPending)
   629  	c.Assert(myActions[1].Action.Name, gc.Equals, "fakeaction")
   630  	c.Assert(myActions[1].Status, gc.Equals, params.ActionCancelled)
   631  }
   632  
   633  func (s *actionSuite) TestApplicationsCharmsActions(c *gc.C) {
   634  	actionSchemas := map[string]map[string]interface{}{
   635  		"snapshot": {
   636  			"type":        "object",
   637  			"title":       "snapshot",
   638  			"description": "Take a snapshot of the database.",
   639  			"properties": map[string]interface{}{
   640  				"outfile": map[string]interface{}{
   641  					"description": "The file to write out to.",
   642  					"type":        "string",
   643  					"default":     "foo.bz2",
   644  				},
   645  			},
   646  		},
   647  		"fakeaction": {
   648  			"type":        "object",
   649  			"title":       "fakeaction",
   650  			"description": "No description",
   651  			"properties":  map[string]interface{}{},
   652  		},
   653  	}
   654  	tests := []struct {
   655  		serviceNames    []string
   656  		expectedResults params.ApplicationsCharmActionsResults
   657  	}{{
   658  		serviceNames: []string{"dummy"},
   659  		expectedResults: params.ApplicationsCharmActionsResults{
   660  			Results: []params.ApplicationCharmActionsResult{
   661  				{
   662  					ApplicationTag: names.NewApplicationTag("dummy").String(),
   663  					Actions: map[string]params.ActionSpec{
   664  						"snapshot": {
   665  							Description: "Take a snapshot of the database.",
   666  							Params:      actionSchemas["snapshot"],
   667  						},
   668  					},
   669  				},
   670  			},
   671  		},
   672  	}, {
   673  		serviceNames: []string{"wordpress"},
   674  		expectedResults: params.ApplicationsCharmActionsResults{
   675  			Results: []params.ApplicationCharmActionsResult{
   676  				{
   677  					ApplicationTag: names.NewApplicationTag("wordpress").String(),
   678  					Actions: map[string]params.ActionSpec{
   679  						"fakeaction": {
   680  							Description: "No description",
   681  							Params:      actionSchemas["fakeaction"],
   682  						},
   683  					},
   684  				},
   685  			},
   686  		},
   687  	}, {
   688  		serviceNames: []string{"nonsense"},
   689  		expectedResults: params.ApplicationsCharmActionsResults{
   690  			Results: []params.ApplicationCharmActionsResult{
   691  				{
   692  					ApplicationTag: names.NewApplicationTag("nonsense").String(),
   693  					Error: &params.Error{
   694  						Message: `application "nonsense" not found`,
   695  						Code:    "not found",
   696  					},
   697  				},
   698  			},
   699  		},
   700  	}}
   701  
   702  	for i, t := range tests {
   703  		c.Logf("test %d: services: %#v", i, t.serviceNames)
   704  
   705  		svcTags := params.Entities{
   706  			Entities: make([]params.Entity, len(t.serviceNames)),
   707  		}
   708  
   709  		for j, svc := range t.serviceNames {
   710  			svcTag := names.NewApplicationTag(svc)
   711  			svcTags.Entities[j] = params.Entity{Tag: svcTag.String()}
   712  		}
   713  
   714  		results, err := s.action.ApplicationsCharmsActions(svcTags)
   715  		c.Assert(err, jc.ErrorIsNil)
   716  		c.Check(results.Results, jc.DeepEquals, t.expectedResults.Results)
   717  	}
   718  }
   719  
   720  func assertReadyToTest(c *gc.C, receiver state.ActionReceiver) {
   721  	// make sure there are no actions on the receiver already.
   722  	actions, err := receiver.Actions()
   723  	c.Assert(err, jc.ErrorIsNil)
   724  	c.Assert(actions, gc.HasLen, 0)
   725  
   726  	// make sure there are no actions pending already.
   727  	actions, err = receiver.PendingActions()
   728  	c.Assert(err, jc.ErrorIsNil)
   729  	c.Assert(actions, gc.HasLen, 0)
   730  
   731  	// make sure there are no actions running already.
   732  	actions, err = receiver.RunningActions()
   733  	c.Assert(err, jc.ErrorIsNil)
   734  	c.Assert(actions, gc.HasLen, 0)
   735  
   736  	// make sure there are no actions completed already.
   737  	actions, err = receiver.CompletedActions()
   738  	c.Assert(err, jc.ErrorIsNil)
   739  	c.Assert(actions, gc.HasLen, 0)
   740  }
   741  
   742  func assertSame(c *gc.C, got, expected params.ActionsByReceivers) {
   743  	c.Assert(got.Actions, gc.HasLen, len(expected.Actions))
   744  	for i, g1 := range got.Actions {
   745  		e1 := expected.Actions[i]
   746  		c.Assert(g1.Error, gc.DeepEquals, e1.Error)
   747  		c.Assert(g1.Receiver, gc.DeepEquals, e1.Receiver)
   748  		c.Assert(toStrings(g1.Actions), jc.SameContents, toStrings(e1.Actions))
   749  	}
   750  }
   751  
   752  func toStrings(items []params.ActionResult) []string {
   753  	ret := make([]string, len(items))
   754  	for i, a := range items {
   755  		ret[i] = stringify(a)
   756  	}
   757  	return ret
   758  }
   759  
   760  func stringify(r params.ActionResult) string {
   761  	a := r.Action
   762  	if a == nil {
   763  		a = &params.Action{}
   764  	}
   765  	return fmt.Sprintf("%s-%s-%#v-%s-%s-%#v", a.Tag, a.Name, a.Parameters, r.Status, r.Message, r.Output)
   766  }