Skip to content

Commit

Permalink
Merge pull request #141 from binary132/actions-uniter-api-watcher
Browse files Browse the repository at this point in the history
Uniter API now has WatchActions

This commit adds state/api and state/apiserver endpoints for
WatchActions.
  • Loading branch information
jujubot committed Jul 9, 2014
2 parents 1f4956f + 5fef503 commit ade4674
Show file tree
Hide file tree
Showing 7 changed files with 291 additions and 2 deletions.
4 changes: 4 additions & 0 deletions state/api/uniter/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,23 @@

package uniter

// Action represents a single instance of an Action call, by name and params.
type Action struct {
name string
params map[string]interface{}
}

// NewAction makes a new Action with specified name and params map.
func NewAction(name string, params map[string]interface{}) (*Action, error) {
return &Action{name: name, params: params}, nil
}

// Name retrieves the name of the Action.
func (a *Action) Name() string {
return a.name
}

// Params retrieves the params map of the Action.
func (a *Action) Params() map[string]interface{} {
return a.params
}
2 changes: 2 additions & 0 deletions state/api/uniter/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
package uniter

var NewSettings = newSettings

var Call = &call
23 changes: 23 additions & 0 deletions state/api/uniter/unit.go
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,29 @@ func (u *Unit) WatchAddresses() (watcher.NotifyWatcher, error) {
return w, nil
}

// WatchActions returns a StringsWatcher for observing the ids of Actions
// added to the Unit. The initial event will contain the ids of any Actions
// pending at the time the Watcher is made.
func (u *Unit) WatchActions() (watcher.StringsWatcher, error) {
var results params.StringsWatchResults
args := params.Entities{
Entities: []params.Entity{{Tag: u.tag.String()}},
}
err := u.st.call("WatchActions", args, &results)
if err != nil {
return nil, err
}
if len(results.Results) != 1 {
return nil, fmt.Errorf("expected 1 result, got %d", len(results.Results))
}
result := results.Results[0]
if result.Error != nil {
return nil, result.Error
}
w := watcher.NewStringsWatcher(u.st.caller, result)
return w, nil
}

// JoinedRelations returns the tags of the relations the unit has joined.
func (u *Unit) JoinedRelations() ([]string, error) {
var results params.StringsResults
Expand Down
89 changes: 89 additions & 0 deletions state/api/uniter/unit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
package uniter_test

import (
"fmt"
"sort"

"github.com/juju/charm"
"github.com/juju/errors"
"github.com/juju/names"
"github.com/juju/testing"
jc "github.com/juju/testing/checkers"
gc "launchpad.net/gocheck"

Expand Down Expand Up @@ -354,6 +356,93 @@ func (s *unitSuite) TestWatchConfigSettings(c *gc.C) {
wc.AssertClosed()
}

func (s *unitSuite) TestWatchActions(c *gc.C) {
w, err := s.apiUnit.WatchActions()
c.Assert(err, gc.IsNil)

defer statetesting.AssertStop(c, w)
wc := statetesting.NewStringsWatcherC(c, s.BackingState, w)

// Initial event.
wc.AssertChange()

// Add a couple of actions and make sure the changes are detected.
action, err := s.wordpressUnit.AddAction("snapshot", map[string]interface{}{
"outfile": "foo.txt",
})
c.Assert(err, gc.IsNil)
wc.AssertChange(action.Id())

action, err = s.wordpressUnit.AddAction("backup", map[string]interface{}{
"outfile": "foo.bz2",
"compression": map[string]interface{}{
"kind": "bzip",
"quality": float64(5.0),
},
})
c.Assert(err, gc.IsNil)
wc.AssertChange(action.Id())

statetesting.AssertStop(c, w)
wc.AssertClosed()
}

func (s *unitSuite) TestWatchActionsError(c *gc.C) {
restore := testing.PatchValue(uniter.Call, func(st *uniter.State, method string, params, results interface{}) error {
return fmt.Errorf("Test error")
})

defer restore()

_, err := s.apiUnit.WatchActions()
c.Assert(err.Error(), gc.Equals, "Test error")
}

func (s *unitSuite) TestWatchActionsErrorResults(c *gc.C) {
restore := testing.PatchValue(uniter.Call, func(st *uniter.State, method string, args, results interface{}) error {
if results, ok := results.(*params.StringsWatchResults); ok {
results.Results = make([]params.StringsWatchResult, 1)
results.Results[0] = params.StringsWatchResult{
Error: &params.Error{
Message: "An error in the watch result.",
Code: params.CodeNotAssigned,
},
}
}
return nil
})

defer restore()

_, err := s.apiUnit.WatchActions()
c.Assert(err.Error(), gc.Equals, "An error in the watch result.")
}

func (s *unitSuite) TestWatchActionsNoResults(c *gc.C) {
restore := testing.PatchValue(uniter.Call, func(st *uniter.State, method string, params, results interface{}) error {
return nil
})

defer restore()

_, err := s.apiUnit.WatchActions()
c.Assert(err.Error(), gc.Equals, "expected 1 result, got 0")
}

func (s *unitSuite) TestWatchActionsMoreResults(c *gc.C) {
restore := testing.PatchValue(uniter.Call, func(st *uniter.State, method string, args, results interface{}) error {
if results, ok := results.(*params.StringsWatchResults); ok {
results.Results = make([]params.StringsWatchResult, 2)
}
return nil
})

defer restore()

_, err := s.apiUnit.WatchActions()
c.Assert(err.Error(), gc.Equals, "expected 1 result, got 2")
}

func (s *unitSuite) TestServiceNameAndTag(c *gc.C) {
c.Assert(s.apiUnit.ServiceName(), gc.Equals, "wordpress")
c.Assert(s.apiUnit.ServiceTag(), gc.Equals, "service-wordpress")
Expand Down
8 changes: 6 additions & 2 deletions state/api/uniter/uniter.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,12 @@ func NewState(caller base.Caller, authTag names.UnitTag) *State {
}
}

func (st *State) call(method string, params, results interface{}) error {
return st.caller.Call(uniterFacade, "", method, params, results)
var call = func(st *State, method string, args, results interface{}) error {
return st.caller.Call(uniterFacade, "", method, args, results)
}

func (st *State) call(method string, args, results interface{}) error {
return call(st, method, args, results)
}

// life requests the lifecycle of the given entity from the server.
Expand Down
46 changes: 46 additions & 0 deletions state/apiserver/uniter/uniter.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,22 @@ func (u *UniterAPI) watchOneUnitConfigSettings(tag string) (string, error) {
}
return "", watcher.MustErr(watch)
}
func (u *UniterAPI) watchOneUnitActions(tag string) (params.StringsWatchResult, error) {
nothing := params.StringsWatchResult{}
unit, err := u.getUnit(tag)
if err != nil {
return nothing, err
}
watch := unit.WatchActions()

if changes, ok := <-watch.Changes(); ok {
return params.StringsWatchResult{
StringsWatcherId: u.resources.Register(watch),
Changes: changes,
}, nil
}
return nothing, watcher.MustErr(watch)
}

// WatchConfigSettings returns a NotifyWatcher for observing changes
// to each unit's service configuration settings. See also
Expand All @@ -456,6 +472,34 @@ func (u *UniterAPI) WatchConfigSettings(args params.Entities) (params.NotifyWatc
return result, nil
}

// WatchActions returns an ActionWatcher for observing incoming action calls
// to a unit. See also state/watcher.go Unit.WatchActions(). This method
// is called from state/api/uniter/uniter.go WatchActions().
func (u *UniterAPI) WatchActions(args params.Entities) (params.StringsWatchResults, error) {
nothing := params.StringsWatchResults{}

result := params.StringsWatchResults{
Results: make([]params.StringsWatchResult, len(args.Entities)),
}
canAccess, err := u.accessUnit()
if err != nil {
return nothing, err
}
for i, entity := range args.Entities {
_, err := names.ParseUnitTag(entity.Tag)
if err != nil {
return nothing, err
}

err = common.ErrPerm
if canAccess(entity.Tag) {
result.Results[i], err = u.watchOneUnitActions(entity.Tag)
}
result.Results[i].Error = common.ServerError(err)
}
return result, nil
}

// ConfigSettings returns the complete set of service charm config
// settings available to each given unit.
func (u *UniterAPI) ConfigSettings(args params.Entities) (params.ConfigSettingsResults, error) {
Expand Down Expand Up @@ -696,6 +740,8 @@ func (u *UniterAPI) getOneActionByTag(tag names.ActionTag) (params.ActionsQueryR
return result, nil
}

// Actions returns the Actions by Tags passed and ensures that the Unit asking
// for them is the same Unit that has the Actions.
func (u *UniterAPI) Actions(args params.Entities) (params.ActionsQueryResults, error) {
nothing := params.ActionsQueryResults{}

Expand Down
121 changes: 121 additions & 0 deletions state/apiserver/uniter/uniter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,127 @@ func (s *uniterSuite) TestWatchConfigSettings(c *gc.C) {
wc.AssertNoChange()
}

func (s *uniterSuite) TestWatchActions(c *gc.C) {
err := s.wordpressUnit.SetCharmURL(s.wpCharm.URL())
c.Assert(err, gc.IsNil)

c.Assert(s.resources.Count(), gc.Equals, 0)

args := params.Entities{Entities: []params.Entity{
{Tag: "unit-mysql-0"},
{Tag: "unit-wordpress-0"},
{Tag: "unit-foo-42"},
}}
result, err := s.uniter.WatchActions(args)
c.Assert(err, gc.IsNil)
c.Assert(result, gc.DeepEquals, params.StringsWatchResults{
Results: []params.StringsWatchResult{
{Error: apiservertesting.ErrUnauthorized},
{StringsWatcherId: "1", Changes: []string{}},
{Error: apiservertesting.ErrUnauthorized},
},
})

// Verify the resource was registered and stop when done
c.Assert(s.resources.Count(), gc.Equals, 1)
resource := s.resources.Get("1")
defer statetesting.AssertStop(c, resource)

// Check that the Watch has consumed the initial event ("returned" in
// the Watch call)
wc := statetesting.NewStringsWatcherC(c, s.State, resource.(state.StringsWatcher))
wc.AssertNoChange()

addedAction, err := s.wordpressUnit.AddAction("snapshot", nil)

wc.AssertChange(addedAction.Id())
wc.AssertNoChange()
}

func (s *uniterSuite) TestWatchPreexistingActions(c *gc.C) {
err := s.wordpressUnit.SetCharmURL(s.wpCharm.URL())
c.Assert(err, gc.IsNil)

c.Assert(s.resources.Count(), gc.Equals, 0)

firstAction, err := s.wordpressUnit.AddAction("backup", nil)
c.Assert(err, gc.IsNil)
secondAction, err := s.wordpressUnit.AddAction("snapshot", nil)
c.Assert(err, gc.IsNil)

args := params.Entities{Entities: []params.Entity{
{Tag: "unit-wordpress-0"},
}}

result, err := s.uniter.WatchActions(args)
c.Assert(err, gc.IsNil)
c.Assert(result, gc.DeepEquals, params.StringsWatchResults{
Results: []params.StringsWatchResult{
{StringsWatcherId: "1", Changes: []string{
firstAction.Id(),
secondAction.Id(),
}},
},
})

// Verify the resource was registered and stop when done
c.Assert(s.resources.Count(), gc.Equals, 1)
resource := s.resources.Get("1")
defer statetesting.AssertStop(c, resource)

// Check that the Watch has consumed the initial event ("returned" in
// the Watch call)
wc := statetesting.NewStringsWatcherC(c, s.State, resource.(state.StringsWatcher))
// TODO: @jcw4 -- this should be:
// wc.AssertNoChange()
wc.AssertChange(firstAction.Id(), secondAction.Id())

addedAction, err := s.wordpressUnit.AddAction("backup", nil)
c.Assert(err, gc.IsNil)
wc.AssertChange(addedAction.Id())
wc.AssertNoChange()
}

func (s *uniterSuite) TestWatchActionsMalformedTag(c *gc.C) {
args := params.Entities{Entities: []params.Entity{
{Tag: "ewenit-mysql-0"},
}}
_, err := s.uniter.WatchActions(args)
c.Assert(err, gc.NotNil)
c.Assert(err.Error(), gc.Equals, `"ewenit-mysql-0" is not a valid tag`)
}

func (s *uniterSuite) TestWatchActionsMalformedUnitName(c *gc.C) {
args := params.Entities{Entities: []params.Entity{
{Tag: "unit-mysql-01"},
}}
_, err := s.uniter.WatchActions(args)
c.Assert(err, gc.NotNil)
c.Assert(err.Error(), gc.Equals, `"unit-mysql-01" is not a valid unit tag`)
}

func (s *uniterSuite) TestWatchActionsNotUnit(c *gc.C) {
args := params.Entities{Entities: []params.Entity{
{Tag: "action-mysql/0_a_0"},
}}
_, err := s.uniter.WatchActions(args)
c.Assert(err, gc.NotNil)
c.Assert(err.Error(), gc.Equals, `"action-mysql/0_a_0" is not a valid unit tag`)
}

func (s *uniterSuite) TestWatchActionsPermissionDenied(c *gc.C) {
args := params.Entities{Entities: []params.Entity{
{Tag: "unit-nonexistentgarbage-0"},
}}
results, err := s.uniter.WatchActions(args)
c.Assert(err, gc.IsNil)
c.Assert(results, gc.NotNil)
c.Assert(len(results.Results), gc.Equals, 1)
result := results.Results[0]
c.Assert(result.Error, gc.NotNil)
c.Assert(result.Error.Message, gc.Equals, "permission denied")
}

func (s *uniterSuite) TestConfigSettings(c *gc.C) {
err := s.wordpressUnit.SetCharmURL(s.wpCharm.URL())
c.Assert(err, gc.IsNil)
Expand Down

0 comments on commit ade4674

Please sign in to comment.