github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/apiserver/common/action_test.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_test 6 7 import ( 8 "github.com/juju/errors" 9 "github.com/juju/names" 10 jc "github.com/juju/testing/checkers" 11 "github.com/juju/utils" 12 gc "gopkg.in/check.v1" 13 14 "github.com/juju/juju/apiserver/common" 15 "github.com/juju/juju/apiserver/params" 16 "github.com/juju/juju/state" 17 "github.com/juju/juju/testing" 18 ) 19 20 type actionsSuite struct { 21 testing.BaseSuite 22 } 23 24 var _ = gc.Suite(&actionsSuite{}) 25 26 func (s *actionsSuite) TestTagToActionReceiverFn(c *gc.C) { 27 stubActionReceiver := fakeActionReceiver{} 28 stubEntity := fakeEntity{} 29 tagToEntity := map[string]state.Entity{ 30 "unit-valid-0": stubActionReceiver, 31 "unit-invalid-0": stubEntity, 32 } 33 tagFn := common.TagToActionReceiverFn(makeFindEntity(tagToEntity)) 34 35 for i, test := range []struct { 36 tag string 37 err error 38 result state.ActionReceiver 39 }{{ 40 tag: "unit-valid-0", 41 result: stubActionReceiver, 42 }, { 43 tag: "unit-invalid-0", 44 err: common.ErrBadId, 45 }, { 46 tag: "unit-flustered-0", 47 err: common.ErrBadId, 48 }, { 49 tag: "notatag", 50 err: common.ErrBadId, 51 }} { 52 c.Logf("test %d", i) 53 receiver, err := tagFn(test.tag) 54 if test.err != nil { 55 c.Check(err, gc.Equals, test.err) 56 c.Check(receiver, gc.IsNil) 57 } else { 58 c.Assert(err, jc.ErrorIsNil) 59 c.Assert(receiver, gc.Equals, test.result) 60 } 61 } 62 } 63 64 func (s *actionsSuite) TestAuthAndActionFromTagFn(c *gc.C) { 65 notFoundActionTag := names.NewActionTag(utils.MustNewUUID().String()) 66 67 authorizedActionTag := names.NewActionTag(utils.MustNewUUID().String()) 68 authorizedMachineTag := names.NewMachineTag("1") 69 authorizedAction := fakeAction{name: "action1", receiver: authorizedMachineTag.Id()} 70 71 unauthorizedActionTag := names.NewActionTag(utils.MustNewUUID().String()) 72 unauthorizedMachineTag := names.NewMachineTag("10") 73 unauthorizedAction := fakeAction{name: "action2", receiver: unauthorizedMachineTag.Id()} 74 75 invalidReceiverActionTag := names.NewActionTag(utils.MustNewUUID().String()) 76 invalidReceiverAction := fakeAction{name: "action2", receiver: "masterexploder"} 77 78 canAccess := makeCanAccess(map[names.Tag]bool{ 79 authorizedMachineTag: true, 80 }) 81 getActionByTag := makeGetActionByTag(map[names.ActionTag]state.Action{ 82 authorizedActionTag: authorizedAction, 83 unauthorizedActionTag: unauthorizedAction, 84 invalidReceiverActionTag: invalidReceiverAction, 85 }) 86 tagFn := common.AuthAndActionFromTagFn(canAccess, getActionByTag) 87 88 for i, test := range []struct { 89 tag string 90 errString string 91 err error 92 expectedAction state.Action 93 }{{ 94 tag: "invalid-action-tag", 95 errString: `"invalid-action-tag" is not a valid tag`, 96 }, { 97 tag: notFoundActionTag.String(), 98 errString: "action not found", 99 }, { 100 tag: invalidReceiverActionTag.String(), 101 errString: `invalid actionreceiver name "masterexploder"`, 102 }, { 103 tag: unauthorizedActionTag.String(), 104 err: common.ErrPerm, 105 }, { 106 tag: authorizedActionTag.String(), 107 expectedAction: authorizedAction, 108 }} { 109 c.Logf("test %d", i) 110 action, err := tagFn(test.tag) 111 if test.errString != "" { 112 c.Check(err, gc.ErrorMatches, test.errString) 113 c.Check(action, gc.IsNil) 114 } else if test.err != nil { 115 c.Check(err, gc.Equals, test.err) 116 c.Check(action, gc.IsNil) 117 } else { 118 c.Check(err, jc.ErrorIsNil) 119 c.Check(action, gc.Equals, action) 120 } 121 } 122 } 123 124 func (s *actionsSuite) TestBeginActions(c *gc.C) { 125 args := entities("success", "fail", "invalid") 126 expectErr := errors.New("explosivo") 127 actionFn := makeGetActionByTagString(map[string]state.Action{ 128 "success": fakeAction{}, 129 "fail": fakeAction{beginErr: expectErr}, 130 }) 131 132 results := common.BeginActions(args, actionFn) 133 134 c.Assert(results, jc.DeepEquals, params.ErrorResults{ 135 []params.ErrorResult{ 136 {}, 137 {common.ServerError(expectErr)}, 138 {common.ServerError(actionNotFoundErr)}, 139 }, 140 }) 141 } 142 143 func (s *actionsSuite) TestGetActions(c *gc.C) { 144 args := entities("success", "fail", "notPending") 145 actionFn := makeGetActionByTagString(map[string]state.Action{ 146 "success": fakeAction{name: "floosh", status: state.ActionPending}, 147 "notPending": fakeAction{status: state.ActionCancelled}, 148 }) 149 150 results := common.Actions(args, actionFn) 151 152 c.Assert(results, jc.DeepEquals, params.ActionResults{ 153 []params.ActionResult{ 154 {Action: ¶ms.Action{Name: "floosh"}}, 155 {Error: common.ServerError(actionNotFoundErr)}, 156 {Error: common.ServerError(common.ErrActionNotAvailable)}, 157 }, 158 }) 159 } 160 161 func (s *actionsSuite) TestFinishActions(c *gc.C) { 162 args := params.ActionExecutionResults{ 163 []params.ActionExecutionResult{ 164 {ActionTag: "success", Status: string(state.ActionCompleted)}, 165 {ActionTag: "notfound"}, 166 {ActionTag: "convertFail", Status: "failStatus"}, 167 {ActionTag: "finishFail", Status: string(state.ActionCancelled)}, 168 }, 169 } 170 expectErr := errors.New("explosivo") 171 actionFn := makeGetActionByTagString(map[string]state.Action{ 172 "success": fakeAction{}, 173 "convertFail": fakeAction{}, 174 "finishFail": fakeAction{finishErr: expectErr}, 175 }) 176 results := common.FinishActions(args, actionFn) 177 c.Assert(results, jc.DeepEquals, params.ErrorResults{ 178 []params.ErrorResult{ 179 {}, 180 {common.ServerError(actionNotFoundErr)}, 181 {common.ServerError(errors.New("unrecognized action status 'failStatus'"))}, 182 {common.ServerError(expectErr)}, 183 }, 184 }) 185 } 186 187 func (s *actionsSuite) TestWatchActionNotifications(c *gc.C) { 188 args := entities("invalid-actionreceiver", "machine-1", "machine-2", "machine-3") 189 canAccess := makeCanAccess(map[names.Tag]bool{ 190 names.NewMachineTag("2"): true, 191 names.NewMachineTag("3"): true, 192 }) 193 expectedStringsWatchResult := params.StringsWatchResult{ 194 StringsWatcherId: "orosu", 195 } 196 watchOne := makeWatchOne(map[names.Tag]params.StringsWatchResult{ 197 names.NewMachineTag("3"): expectedStringsWatchResult, 198 }) 199 200 results := common.WatchActionNotifications(args, canAccess, watchOne) 201 202 c.Assert(results, jc.DeepEquals, params.StringsWatchResults{ 203 []params.StringsWatchResult{ 204 {Error: common.ServerError(errors.New(`invalid actionreceiver tag "invalid-actionreceiver"`))}, 205 {Error: common.ServerError(common.ErrPerm)}, 206 {Error: common.ServerError(errors.New("pax"))}, 207 {StringsWatcherId: "orosu"}, 208 }, 209 }) 210 } 211 212 func (s *actionsSuite) TestWatchOneActionReceiverNotifications(c *gc.C) { 213 expectErr := errors.New("zwoosh") 214 registerFunc := func(common.Resource) string { return "bambalam" } 215 tagToActionReceiver := common.TagToActionReceiverFn(makeFindEntity(map[string]state.Entity{ 216 "machine-1": &fakeActionReceiver{watcher: &fakeWatcher{}}, 217 "machine-2": &fakeActionReceiver{watcher: &fakeWatcher{err: expectErr}}, 218 })) 219 220 watchOneFn := common.WatchOneActionReceiverNotifications(tagToActionReceiver, registerFunc) 221 222 for i, test := range []struct { 223 tag names.Tag 224 err string 225 watcherId string 226 }{{ 227 tag: names.NewMachineTag("0"), 228 err: "id not found", 229 }, { 230 tag: names.NewMachineTag("1"), 231 watcherId: "bambalam", 232 }, { 233 tag: names.NewMachineTag("2"), 234 err: "zwoosh", 235 }} { 236 c.Logf("test %d", i) 237 c.Logf(test.tag.String()) 238 result, err := watchOneFn(test.tag) 239 if test.err != "" { 240 c.Check(err, gc.ErrorMatches, test.err) 241 c.Check(result, jc.DeepEquals, params.StringsWatchResult{}) 242 } else { 243 c.Check(err, jc.ErrorIsNil) 244 c.Check(result.StringsWatcherId, gc.Equals, test.watcherId) 245 } 246 } 247 } 248 249 func makeWatchOne(mapping map[names.Tag]params.StringsWatchResult) func(names.Tag) (params.StringsWatchResult, error) { 250 return func(tag names.Tag) (params.StringsWatchResult, error) { 251 result, ok := mapping[tag] 252 if !ok { 253 return params.StringsWatchResult{}, errors.New("pax") 254 } 255 return result, nil 256 } 257 } 258 259 func makeFindEntity(tagToEntity map[string]state.Entity) func(tag names.Tag) (state.Entity, error) { 260 return func(tag names.Tag) (state.Entity, error) { 261 receiver, ok := tagToEntity[tag.String()] 262 if !ok { 263 return nil, errors.New("splat") 264 } 265 return receiver, nil 266 } 267 } 268 269 func makeCanAccess(allowed map[names.Tag]bool) common.AuthFunc { 270 return func(tag names.Tag) bool { 271 _, ok := allowed[tag] 272 return ok 273 } 274 } 275 276 var actionNotFoundErr = errors.New("action not found") 277 278 func makeGetActionByTag(tagToAction map[names.ActionTag]state.Action) func(names.ActionTag) (state.Action, error) { 279 return func(tag names.ActionTag) (state.Action, error) { 280 action, ok := tagToAction[tag] 281 if !ok { 282 return nil, actionNotFoundErr 283 } 284 return action, nil 285 } 286 } 287 288 func makeGetActionByTagString(tagToAction map[string]state.Action) func(string) (state.Action, error) { 289 return func(tag string) (state.Action, error) { 290 action, ok := tagToAction[tag] 291 if !ok { 292 return nil, errors.New("action not found") 293 } 294 return action, nil 295 } 296 } 297 298 type fakeActionReceiver struct { 299 state.ActionReceiver 300 watcher state.StringsWatcher 301 } 302 303 func (mock fakeActionReceiver) WatchActionNotifications() state.StringsWatcher { 304 return mock.watcher 305 } 306 307 type fakeWatcher struct { 308 state.StringsWatcher 309 err error 310 } 311 312 func (mock fakeWatcher) Changes() <-chan []string { 313 ch := make(chan []string, 1) 314 if mock.err != nil { 315 close(ch) 316 } else { 317 ch <- []string{"pew", "pew", "pew"} 318 } 319 return ch 320 } 321 322 func (mock fakeWatcher) Err() error { 323 return mock.err 324 } 325 326 type fakeEntity struct { 327 state.Entity 328 } 329 330 type fakeAction struct { 331 state.Action 332 receiver string 333 name string 334 beginErr error 335 finishErr error 336 status state.ActionStatus 337 } 338 339 func (mock fakeAction) Status() state.ActionStatus { 340 return mock.status 341 } 342 343 func (mock fakeAction) Begin() (state.Action, error) { 344 return nil, mock.beginErr 345 } 346 347 func (mock fakeAction) Receiver() string { 348 return mock.receiver 349 } 350 351 func (mock fakeAction) Name() string { 352 return mock.name 353 } 354 355 func (mock fakeAction) Parameters() map[string]interface{} { 356 return nil 357 } 358 359 func (mock fakeAction) Finish(state.ActionResults) (state.Action, error) { 360 return nil, mock.finishErr 361 } 362 363 // entities is a convenience constructor for params.Entities. 364 func entities(tags ...string) params.Entities { 365 entities := params.Entities{ 366 Entities: make([]params.Entity, len(tags)), 367 } 368 for i, tag := range tags { 369 entities.Entities[i].Tag = tag 370 } 371 return entities 372 }