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