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: &params.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  }