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 = ¶ms.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: ¶ms.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 }