Skip to content

Commit

Permalink
Enhance run action CLI to deal with new operations
Browse files Browse the repository at this point in the history
  • Loading branch information
wallyworld committed Feb 6, 2020
1 parent 6b3d67b commit 496bc0c
Show file tree
Hide file tree
Showing 14 changed files with 370 additions and 77 deletions.
12 changes: 12 additions & 0 deletions api/action/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,18 @@ func (c *Client) Enqueue(arg params.Actions) (params.ActionResults, error) {
return results, err
}

// EnqueueV2 takes a list of Actions and queues them up to be executed by
// the designated ActionReceiver, returning the ids of the overall operation
// and each individual task.
func (c *Client) EnqueueV2(arg params.Actions) (params.EnqueuedActions, error) {
results := params.EnqueuedActions{}
if v := c.BestAPIVersion(); v < 6 {
return results, errors.Errorf("EnqueueV2 not supported by this version (%d) of Juju", v)
}
err := c.facade.FacadeCall("EnqueueV2", arg, &results)
return results, err
}

// FindActionsByNames takes a list of action names and returns actions for
// every name.
func (c *Client) FindActionsByNames(arg params.FindActionsByNames) (params.ActionsByNames, error) {
Expand Down
60 changes: 60 additions & 0 deletions api/action/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,3 +262,63 @@ func (s *actionSuite) TestOperationsNotSupported(c *gc.C) {
_, err := client.Operations(params.OperationQueryArgs{})
c.Assert(err, gc.ErrorMatches, "Operations not supported by this version \\(4\\) of Juju")
}

func (s *actionSuite) TestEnqueueV2(c *gc.C) {
args := params.Actions{
Actions: []params.Action{{
Receiver: "unit/0",
Name: "test",
Parameters: map[string]interface{}{
"foo": "bar",
},
}},
}
apiCaller := basetesting.BestVersionCaller{
APICallerFunc: basetesting.APICallerFunc(
func(objType string,
version int,
id, request string,
a, result interface{},
) error {
c.Assert(request, gc.Equals, "EnqueueV2")
c.Assert(a, jc.DeepEquals, args)
c.Assert(result, gc.FitsTypeOf, &params.EnqueuedActions{})
*(result.(*params.EnqueuedActions)) = params.EnqueuedActions{
OperationTag: "operation-1",
Actions: []params.StringResult{{
Error: &params.Error{Message: "FAIL"},
}},
}
return nil
},
),
BestVersion: 6,
}
client := action.NewClient(apiCaller)
result, err := client.EnqueueV2(args)
c.Assert(err, jc.ErrorIsNil)
c.Assert(result, jc.DeepEquals, params.EnqueuedActions{
Actions: []params.StringResult{{
Error: &params.Error{Message: "FAIL"},
}},
OperationTag: "operation-1",
})
}

func (s *actionSuite) TestEnqueueV2NotSupported(c *gc.C) {
apiCaller := basetesting.BestVersionCaller{
APICallerFunc: basetesting.APICallerFunc(
func(objType string,
version int,
id, request string,
a, result interface{},
) error {
return nil
},
),
BestVersion: 5,
}
client := action.NewClient(apiCaller)
_, err := client.EnqueueV2(params.Actions{})
c.Assert(err, gc.ErrorMatches, "EnqueueV2 not supported by this version \\(5\\) of Juju")
}
2 changes: 1 addition & 1 deletion api/facadeversions.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ package api
// New facades should start at 1.
// Facades that existed before versioning start at 0.
var facadeVersions = map[string]int{
"Action": 5,
"Action": 6,
"ActionPruner": 1,
"Agent": 2,
"AgentTools": 1,
Expand Down
1 change: 1 addition & 0 deletions apiserver/allfacades.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ func AllFacades() *facade.Registry {
reg("Action", 3, action.NewActionAPIV3)
reg("Action", 4, action.NewActionAPIV4)
reg("Action", 5, action.NewActionAPIV5)
reg("Action", 6, action.NewActionAPIV6)
reg("ActionPruner", 1, actionpruner.NewAPI)
reg("Agent", 2, agent.NewAgentAPIV2)
reg("AgentTools", 1, agenttools.NewFacade)
Expand Down
53 changes: 48 additions & 5 deletions apiserver/facades/client/action/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ type APIv4 struct {

// APIv5 provides the Action API facade for version 5.
type APIv5 struct {
*APIv6
}

// APIv6 provides the Action API facade for version 6.
type APIv6 struct {
*ActionAPI
}

Expand Down Expand Up @@ -75,15 +80,24 @@ func NewActionAPIV4(ctx facade.Context) (*APIv4, error) {
return &APIv4{api}, nil
}

// NewActionAPIV5 returns an initialized ActionAPI for version 4.
// NewActionAPIV5 returns an initialized ActionAPI for version 5.
func NewActionAPIV5(ctx facade.Context) (*APIv5, error) {
api, err := newActionAPI(ctx.State(), ctx.Resources(), ctx.Auth())
api, err := NewActionAPIV6(ctx)
if err != nil {
return nil, errors.Trace(err)
}
return &APIv5{api}, nil
}

// NewActionAPIV6 returns an initialized ActionAPI for version 6.
func NewActionAPIV6(ctx facade.Context) (*APIv6, error) {
api, err := newActionAPI(ctx.State(), ctx.Resources(), ctx.Auth())
if err != nil {
return nil, errors.Trace(err)
}
return &APIv6{api}, nil
}

func newActionAPI(st *state.State, resources facade.Resources, authorizer facade.Authorizer) (*ActionAPI, error) {
if !authorizer.AuthClient() {
return nil, common.ErrPerm
Expand Down Expand Up @@ -247,13 +261,42 @@ func (a *ActionAPI) FindActionsByNames(arg params.FindActionsByNames) (params.Ac
return response, nil
}

// EnqueueV2 isn't on the V5 API.
func (*APIv5) EnqueueV2(_, _ struct{}) {}

// EnqueueV2 takes a list of Actions and queues them up to be executed by
// the designated ActionReceiver, returning the ids of the overall operation
// and each individual task.
func (a *ActionAPI) EnqueueV2(arg params.Actions) (params.EnqueuedActions, error) {
operationId, actionResults, err := a.enqueue(arg)
if err != nil {
return params.EnqueuedActions{}, err
}
results := params.EnqueuedActions{
OperationTag: names.NewOperationTag(operationId).String(),
Actions: make([]params.StringResult, len(actionResults.Results)),
}
for i, action := range actionResults.Results {
results.Actions[i].Error = action.Error
if action.Action != nil {
results.Actions[i].Result = action.Action.Tag
}
}
return results, nil
}

// Enqueue takes a list of Actions and queues them up to be executed by
// the designated ActionReceiver, returning the params.Action for each
// enqueued Action, or an error if there was a problem enqueueing the
// Action.
func (a *ActionAPI) Enqueue(arg params.Actions) (params.ActionResults, error) {
_, results, err := a.enqueue(arg)
return results, err
}

func (a *ActionAPI) enqueue(arg params.Actions) (string, params.ActionResults, error) {
if err := a.checkCanWrite(); err != nil {
return params.ActionResults{}, errors.Trace(err)
return "", params.ActionResults{}, errors.Trace(err)
}

var leaders map[string]string
Expand Down Expand Up @@ -288,7 +331,7 @@ func (a *ActionAPI) Enqueue(arg params.Actions) (params.ActionResults, error) {
summary := fmt.Sprintf("%v run on %v", operationName, strings.Join(receivers, ","))
operationID, err := a.model.EnqueueOperation(summary)
if err != nil {
return params.ActionResults{}, errors.Annotate(err, "creating operation for actions")
return "", params.ActionResults{}, errors.Annotate(err, "creating operation for actions")
}

tagToActionReceiver := common.TagToActionReceiverFn(a.state.FindEntity)
Expand Down Expand Up @@ -318,7 +361,7 @@ func (a *ActionAPI) Enqueue(arg params.Actions) (params.ActionResults, error) {

response.Results[i] = common.MakeActionResult(receiver.Tag(), enqueued, false)
}
return response, nil
return operationID, response, nil
}

// ListAll takes a list of Entities representing ActionReceivers and
Expand Down
81 changes: 81 additions & 0 deletions apiserver/facades/client/action/action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,87 @@ func (s *actionSuite) TestEnqueue(c *gc.C) {
c.Assert(operations[0].Summary(), gc.Equals, "multiple actions run on unit-wordpress-0,application-wordpress,unit-mysql-0,wordpress/leader")
}

func (s *actionSuite) TestEnqueueV2(c *gc.C) {
// Ensure wordpress unit is the leader.
claimer, err := s.LeaseManager.Claimer("application-leadership", s.State.ModelUUID())
c.Assert(err, jc.ErrorIsNil)
err = claimer.Claim("wordpress", "wordpress/0", time.Minute)
c.Assert(err, jc.ErrorIsNil)

// Make sure no Actions already exist on wordpress Unit.
actions, err := s.wordpressUnit.Actions()
c.Assert(err, jc.ErrorIsNil)
c.Assert(actions, gc.HasLen, 0)

// Make sure no Actions already exist on mysql Unit.
actions, err = s.mysqlUnit.Actions()
c.Assert(err, jc.ErrorIsNil)
c.Assert(actions, gc.HasLen, 0)

// Add Actions.
expectedName := "fakeaction"
expectedParameters := map[string]interface{}{"kan jy nie": "verstaand"}
arg := params.Actions{
Actions: []params.Action{
// No receiver.
{Name: "fakeaction"},
// Good.
{Receiver: s.wordpressUnit.Tag().String(), Name: expectedName, Parameters: expectedParameters},
// Application tag instead of Unit tag.
{Receiver: s.wordpress.Tag().String(), Name: "fakeaction"},
// Missing name.
{Receiver: s.mysqlUnit.Tag().String(), Parameters: expectedParameters},
// Good (leader syntax).
{Receiver: "wordpress/leader", Name: expectedName, Parameters: expectedParameters},
},
}

// blocking changes should have no effect
s.BlockAllChanges(c, "Enqueue")

res, err := s.action.EnqueueV2(arg)
c.Assert(err, jc.ErrorIsNil)
c.Assert(res.Actions, gc.HasLen, 5)

emptyActionTag := names.ActionTag{}
c.Assert(res.Actions[0].Error, gc.DeepEquals,
&params.Error{Message: fmt.Sprintf("%s not valid", arg.Actions[0].Receiver), Code: ""})
c.Assert(res.Actions[0].Result, gc.Equals, "")

c.Assert(res.Actions[1].Error, gc.IsNil)
c.Assert(res.Actions[1].Result, gc.Not(gc.Equals), emptyActionTag)

errorString := fmt.Sprintf("action receiver interface on entity %s not implemented", arg.Actions[2].Receiver)
c.Assert(res.Actions[2].Error, gc.DeepEquals, &params.Error{Message: errorString, Code: "not implemented"})
c.Assert(res.Actions[2].Result, gc.Equals, "")

c.Assert(res.Actions[3].Error, gc.ErrorMatches, "no action name given")
c.Assert(res.Actions[3].Result, gc.Equals, "")

c.Assert(res.Actions[4].Error, gc.IsNil)
c.Assert(res.Actions[4].Result, gc.Not(gc.Equals), emptyActionTag)

// Make sure that 2 actions were enqueued for the wordpress Unit.
actions, err = s.wordpressUnit.Actions()
c.Assert(err, jc.ErrorIsNil)
c.Assert(actions, gc.HasLen, 2)
for _, act := range actions {
c.Assert(act.Name(), gc.Equals, expectedName)
c.Assert(act.Parameters(), gc.DeepEquals, expectedParameters)
c.Assert(act.Receiver(), gc.Equals, s.wordpressUnit.Name())
}

// Make sure an Action was not enqueued for the mysql Unit.
actions, err = s.mysqlUnit.Actions()
c.Assert(err, jc.ErrorIsNil)
c.Assert(actions, gc.HasLen, 0)

operations, err := s.Model.AllOperations()
c.Assert(err, jc.ErrorIsNil)
c.Assert(operations, gc.HasLen, 1)
c.Assert(operations[0].Summary(), gc.Equals, "multiple actions run on unit-wordpress-0,application-wordpress,unit-mysql-0,wordpress/leader")
}

type testCaseAction struct {
Name string
Parameters map[string]interface{}
Expand Down
46 changes: 45 additions & 1 deletion apiserver/facades/schema.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[
{
"Name": "Action",
"Version": 5,
"Version": 6,
"Schema": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -49,6 +49,17 @@
}
}
},
"EnqueueV2": {
"type": "object",
"properties": {
"Params": {
"$ref": "#/definitions/Actions"
},
"Result": {
"$ref": "#/definitions/EnqueuedActions"
}
}
},
"FindActionTagsByPrefix": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -390,6 +401,24 @@
},
"additionalProperties": false
},
"EnqueuedActions": {
"type": "object",
"properties": {
"actions": {
"type": "array",
"items": {
"$ref": "#/definitions/StringResult"
}
},
"operation": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"operation"
]
},
"Entities": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -556,6 +585,21 @@
"timeout"
]
},
"StringResult": {
"type": "object",
"properties": {
"error": {
"$ref": "#/definitions/Error"
},
"result": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"result"
]
},
"StringsWatchResult": {
"type": "object",
"properties": {
Expand Down
6 changes: 6 additions & 0 deletions apiserver/params/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ type Action struct {
Parameters map[string]interface{} `json:"parameters,omitempty"`
}

// EnqueuedActions represents the result of enqueuing actions to run.
type EnqueuedActions struct {
OperationTag string `json:"operation"`
Actions []StringResult `json:"actions,omitempty"`
}

// ActionResults is a slice of ActionResult for bulk requests.
type ActionResults struct {
Results []ActionResult `json:"results,omitempty"`
Expand Down
6 changes: 6 additions & 0 deletions cmd/juju/action/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,14 @@ type APIClient interface {
// the designated ActionReceiver, returning the params.Action for each
// queued Action, or an error if there was a problem queueing up the
// Action.
// TODO(juju3) - remove.
Enqueue(params.Actions) (params.ActionResults, error)

// EnqueueV2 takes a list of Actions and queues them up to be executed by
// the designated ActionReceiver, returning the ids of the overall operation
// and each individual task.
EnqueueV2(params.Actions) (params.EnqueuedActions, error)

// ListAll takes a list of Tags representing ActionReceivers and returns
// all of the Actions that have been queued or run by each of those
// Entities.
Expand Down
Loading

0 comments on commit 496bc0c

Please sign in to comment.