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