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