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