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