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

     1  // Copyright 2016 Canonical Ltd.
     2  // Copyright 2016 Cloudbase Solutions
     3  // Licensed under the AGPLv3, see LICENCE file for details.
     4  
     5  package common
     6  
     7  import (
     8  	"github.com/juju/errors"
     9  	"gopkg.in/juju/names.v2"
    10  
    11  	"github.com/juju/juju/apiserver/facade"
    12  	"github.com/juju/juju/apiserver/params"
    13  	"github.com/juju/juju/state"
    14  	"github.com/juju/juju/state/watcher"
    15  )
    16  
    17  // ParamsActionExecutionResultsToStateActionResults does exactly what
    18  // the name implies.
    19  func ParamsActionExecutionResultsToStateActionResults(arg params.ActionExecutionResult) (state.ActionResults, error) {
    20  	var status state.ActionStatus
    21  	switch arg.Status {
    22  	case params.ActionCancelled:
    23  		status = state.ActionCancelled
    24  	case params.ActionCompleted:
    25  		status = state.ActionCompleted
    26  	case params.ActionFailed:
    27  		status = state.ActionFailed
    28  	case params.ActionPending:
    29  		status = state.ActionPending
    30  	default:
    31  		return state.ActionResults{}, errors.Errorf("unrecognized action status '%s'", arg.Status)
    32  	}
    33  	return state.ActionResults{
    34  		Status:  status,
    35  		Results: arg.Results,
    36  		Message: arg.Message,
    37  	}, nil
    38  }
    39  
    40  // TagToActionReceiver takes a tag string and tries to convert it to an
    41  // ActionReceiver. It needs a findEntity function passed in that can search for the tags in state.
    42  func TagToActionReceiverFn(findEntity func(names.Tag) (state.Entity, error)) func(tag string) (state.ActionReceiver, error) {
    43  	return func(tag string) (state.ActionReceiver, error) {
    44  		receiverTag, err := names.ParseTag(tag)
    45  		if err != nil {
    46  			return nil, ErrBadId
    47  		}
    48  		entity, err := findEntity(receiverTag)
    49  		if err != nil {
    50  			return nil, ErrBadId
    51  		}
    52  		receiver, ok := entity.(state.ActionReceiver)
    53  		if !ok {
    54  			return nil, ErrBadId
    55  		}
    56  		return receiver, nil
    57  	}
    58  }
    59  
    60  // AuthAndActionFromTagFn takes in an authorizer function and a function that can fetch action by tags from state
    61  // and returns a function that can fetch an action from state by id and check the authorization.
    62  func AuthAndActionFromTagFn(canAccess AuthFunc, getActionByTag func(names.ActionTag) (state.Action, error)) func(string) (state.Action, error) {
    63  	return func(tag string) (state.Action, error) {
    64  		actionTag, err := names.ParseActionTag(tag)
    65  		if err != nil {
    66  			return nil, errors.Trace(err)
    67  		}
    68  		action, err := getActionByTag(actionTag)
    69  		if err != nil {
    70  			return nil, errors.Trace(err)
    71  		}
    72  		receiverTag, err := names.ActionReceiverTag(action.Receiver())
    73  		if err != nil {
    74  			return nil, errors.Trace(err)
    75  		}
    76  		if !canAccess(receiverTag) {
    77  			return nil, ErrPerm
    78  		}
    79  		return action, nil
    80  	}
    81  }
    82  
    83  // BeginActions calls begin on every action passed in through args.
    84  // It's a helper function currently used by the uniter and by machineactions
    85  // It needs an actionFn that can fetch an action from state using it's id, that's usually created by AuthAndActionFromTagFn
    86  func BeginActions(args params.Entities, actionFn func(string) (state.Action, error)) params.ErrorResults {
    87  	results := params.ErrorResults{Results: make([]params.ErrorResult, len(args.Entities))}
    88  
    89  	for i, arg := range args.Entities {
    90  		action, err := actionFn(arg.Tag)
    91  		if err != nil {
    92  			results.Results[i].Error = ServerError(err)
    93  			continue
    94  		}
    95  
    96  		_, err = action.Begin()
    97  		if err != nil {
    98  			results.Results[i].Error = ServerError(err)
    99  			continue
   100  		}
   101  	}
   102  
   103  	return results
   104  }
   105  
   106  // FinishActions saves the result of a completed Action.
   107  // It's a helper function currently used by the uniter and by machineactions
   108  // It needs an actionFn that can fetch an action from state using it's id that's usually created by AuthAndActionFromTagFn
   109  func FinishActions(args params.ActionExecutionResults, actionFn func(string) (state.Action, error)) params.ErrorResults {
   110  	results := params.ErrorResults{Results: make([]params.ErrorResult, len(args.Results))}
   111  
   112  	for i, arg := range args.Results {
   113  		action, err := actionFn(arg.ActionTag)
   114  		if err != nil {
   115  			results.Results[i].Error = ServerError(err)
   116  			continue
   117  		}
   118  		actionResults, err := ParamsActionExecutionResultsToStateActionResults(arg)
   119  		if err != nil {
   120  			results.Results[i].Error = ServerError(err)
   121  			continue
   122  		}
   123  
   124  		_, err = action.Finish(actionResults)
   125  		if err != nil {
   126  			results.Results[i].Error = ServerError(err)
   127  			continue
   128  		}
   129  	}
   130  
   131  	return results
   132  }
   133  
   134  // Actions returns the Actions by Tags passed in and ensures that the receiver asking for
   135  // them is the same one that has the action.
   136  // It's a helper function currently used by the uniter and by machineactions.
   137  // It needs an actionFn that can fetch an action from state using it's id that's usually created by AuthAndActionFromTagFn
   138  func Actions(args params.Entities, actionFn func(string) (state.Action, error)) params.ActionResults {
   139  	results := params.ActionResults{
   140  		Results: make([]params.ActionResult, len(args.Entities)),
   141  	}
   142  
   143  	for i, arg := range args.Entities {
   144  		action, err := actionFn(arg.Tag)
   145  		if err != nil {
   146  			results.Results[i].Error = ServerError(err)
   147  			continue
   148  		}
   149  		if action.Status() != state.ActionPending {
   150  			results.Results[i].Error = ServerError(ErrActionNotAvailable)
   151  			continue
   152  		}
   153  		results.Results[i].Action = &params.Action{
   154  			Name:       action.Name(),
   155  			Parameters: action.Parameters(),
   156  		}
   157  	}
   158  
   159  	return results
   160  }
   161  
   162  // WatchOneActionReceiverNotifications to create a watcher for one receiver.
   163  // It needs a tagToActionReceiver function and a registerFunc to register
   164  // resources.
   165  // It's a helper function currently used by the uniter and by machineactions
   166  func WatchOneActionReceiverNotifications(tagToActionReceiver func(tag string) (state.ActionReceiver, error), registerFunc func(r facade.Resource) string) func(names.Tag) (params.StringsWatchResult, error) {
   167  	return func(tag names.Tag) (params.StringsWatchResult, error) {
   168  		nothing := params.StringsWatchResult{}
   169  		receiver, err := tagToActionReceiver(tag.String())
   170  		if err != nil {
   171  			return nothing, err
   172  		}
   173  		watch := receiver.WatchActionNotifications()
   174  
   175  		if changes, ok := <-watch.Changes(); ok {
   176  			return params.StringsWatchResult{
   177  				StringsWatcherId: registerFunc(watch),
   178  				Changes:          changes,
   179  			}, nil
   180  		}
   181  		return nothing, watcher.EnsureErr(watch)
   182  	}
   183  }
   184  
   185  // WatchActionNotifications returns a StringsWatcher for observing incoming actions towards an actionreceiver.
   186  // It's a helper function currently used by the uniter and by machineactions
   187  // canAccess is passed in by the respective caller to provide authorization.
   188  // watchOne is usually a function created by WatchOneActionReceiverNotifications
   189  func WatchActionNotifications(args params.Entities, canAccess AuthFunc, watchOne func(names.Tag) (params.StringsWatchResult, error)) params.StringsWatchResults {
   190  	result := params.StringsWatchResults{
   191  		Results: make([]params.StringsWatchResult, len(args.Entities)),
   192  	}
   193  
   194  	for i, entity := range args.Entities {
   195  		tag, err := names.ActionReceiverFromTag(entity.Tag)
   196  		if err != nil {
   197  			result.Results[i].Error = ServerError(err)
   198  			continue
   199  		}
   200  		err = ErrPerm
   201  		if canAccess(tag) {
   202  			result.Results[i], err = watchOne(tag)
   203  		}
   204  		result.Results[i].Error = ServerError(err)
   205  	}
   206  
   207  	return result
   208  }
   209  
   210  // GetActionsFn declares the function type that returns a slice of
   211  // state.Action and error, used to curry specific list functions.
   212  type GetActionsFn func() ([]state.Action, error)
   213  
   214  // ConvertActions takes a generic getActionsFn to obtain a slice
   215  // of state.Action and then converts them to the API slice of
   216  // params.ActionResult.
   217  func ConvertActions(ar state.ActionReceiver, fn GetActionsFn) ([]params.ActionResult, error) {
   218  	items := []params.ActionResult{}
   219  	actions, err := fn()
   220  	if err != nil {
   221  		return items, err
   222  	}
   223  	for _, action := range actions {
   224  		if action == nil {
   225  			continue
   226  		}
   227  		items = append(items, MakeActionResult(ar.Tag(), action))
   228  	}
   229  	return items, nil
   230  }
   231  
   232  // MakeActionResult does the actual type conversion from state.Action
   233  // to params.ActionResult.
   234  func MakeActionResult(actionReceiverTag names.Tag, action state.Action) params.ActionResult {
   235  	output, message := action.Results()
   236  	return params.ActionResult{
   237  		Action: &params.Action{
   238  			Receiver:   actionReceiverTag.String(),
   239  			Tag:        action.ActionTag().String(),
   240  			Name:       action.Name(),
   241  			Parameters: action.Parameters(),
   242  		},
   243  		Status:    string(action.Status()),
   244  		Message:   message,
   245  		Output:    output,
   246  		Enqueued:  action.Enqueued(),
   247  		Started:   action.Started(),
   248  		Completed: action.Completed(),
   249  	}
   250  }