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