Skip to content

Commit

Permalink
Merge remote-tracking branch 'stickupkid/unpin-machine-applications-o…
Browse files Browse the repository at this point in the history
…n-destroy' into port-stickupkid/unpin-machine-applications-on-destroy
  • Loading branch information
wallyworld committed May 29, 2020
2 parents d05284c + 383e945 commit 6f6656e
Show file tree
Hide file tree
Showing 9 changed files with 419 additions and 99 deletions.
124 changes: 83 additions & 41 deletions apiserver/common/leadership.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,10 @@ func (s leadershipPinningBackend) Machine(name string) (LeadershipMachine, error
return leadershipMachine{m}, nil
}

// API exposes leadership pinning and unpinning functionality for remote use.
type LeadershipPinningAPI interface {
PinMachineApplications() (params.PinApplicationsResults, error)
UnpinMachineApplications() (params.PinApplicationsResults, error)
PinnedLeadership() (params.PinnedLeadershipResult, error)
}

// NewLeadershipPinningFacade creates and returns a new leadership API.
// NewLeadershipPinningFromContext creates and returns a new leadership from
// a facade context.
// This signature is suitable for facade registration.
func NewLeadershipPinningFacade(ctx facade.Context) (LeadershipPinningAPI, error) {
func NewLeadershipPinningFromContext(ctx facade.Context) (*LeadershipPinning, error) {
st := ctx.State()
model, err := st.Model()
if err != nil {
Expand All @@ -63,23 +57,25 @@ func NewLeadershipPinningFacade(ctx facade.Context) (LeadershipPinningAPI, error
if err != nil {
return nil, errors.Trace(err)
}
return NewLeadershipPinningAPI(leadershipPinningBackend{st}, model.ModelTag(), pinner, ctx.Auth())
return NewLeadershipPinning(leadershipPinningBackend{st}, model.ModelTag(), pinner, ctx.Auth())
}

// NewLeadershipPinningAPI creates and returns a new leadership API from the
// NewLeadershipPinning creates and returns a new leadership API from the
// input tag, Pinner implementation and facade Authorizer.
func NewLeadershipPinningAPI(
func NewLeadershipPinning(
st LeadershipPinningBackend, modelTag names.ModelTag, pinner leadership.Pinner, authorizer facade.Authorizer,
) (LeadershipPinningAPI, error) {
return &leadershipPinningAPI{
) (*LeadershipPinning, error) {
return &LeadershipPinning{
st: st,
modelTag: modelTag,
pinner: pinner,
authorizer: authorizer,
}, nil
}

type leadershipPinningAPI struct {
// LeadershipPinning defines a type for pinning and unpinning application
// leaders.
type LeadershipPinning struct {
st LeadershipPinningBackend
modelTag names.ModelTag
pinner leadership.Pinner
Expand All @@ -88,7 +84,7 @@ type leadershipPinningAPI struct {

// PinnedLeadership returns all pinned applications and the entities that
// require their pinned behaviour, for leadership in the current model.
func (a *leadershipPinningAPI) PinnedLeadership() (params.PinnedLeadershipResult, error) {
func (a *LeadershipPinning) PinnedLeadership() (params.PinnedLeadershipResult, error) {
result := params.PinnedLeadershipResult{}

canAccess, err := a.authorizer.HasPermission(permission.ReadAccess, a.modelTag)
Expand All @@ -103,47 +99,93 @@ func (a *leadershipPinningAPI) PinnedLeadership() (params.PinnedLeadershipResult
return result, nil
}

// TODO (manadart 2018-10-29): Rename the two methods below (and on the client
// side) to be [Un]PinApplicationLeaders, and derive the list of applications
// based on the authenticating entity.

// PinMachineApplications pins leadership for applications represented by units
// running on the auth'd machine.
func (a *leadershipPinningAPI) PinMachineApplications() (params.PinApplicationsResults, error) {
// PinApplicationLeaders pins leadership for applications based on the auth
// tag provided.
func (a *LeadershipPinning) PinApplicationLeaders() (params.PinApplicationsResults, error) {
if !a.authorizer.AuthMachineAgent() {
return params.PinApplicationsResults{}, ErrPerm
}
return a.pinMachineAppsOps(a.pinner.PinLeadership)
}

// UnpinMachineApplications unpins leadership for applications represented by
// units running on the auth'd machine.
func (a *leadershipPinningAPI) UnpinMachineApplications() (params.PinApplicationsResults, error) {
if !a.authorizer.AuthMachineAgent() {
tag := a.authorizer.GetAuthTag()
switch tag.Kind() {
case names.MachineTagKind:
return a.pinMachineApplications(tag)
default:
return params.PinApplicationsResults{}, ErrPerm
}
return a.pinMachineAppsOps(a.pinner.UnpinLeadership)
}

// pinMachineAppsOps runs the input pin/unpin operation against all
// applications represented by units on the authorised machine.
// An assumption is made that the validity of the auth tag has been verified
// by the caller.
func (a *leadershipPinningAPI) pinMachineAppsOps(op func(string, string) error) (params.PinApplicationsResults, error) {
result := params.PinApplicationsResults{}
// UnpinApplicationLeaders unpins leadership for applications based on the auth
// tag provided.
func (a *LeadershipPinning) UnpinApplicationLeaders() (params.PinApplicationsResults, error) {
if !a.authorizer.AuthMachineAgent() {
return params.PinApplicationsResults{}, ErrPerm
}

tag := a.authorizer.GetAuthTag()
m, err := a.st.Machine(tag.Id())
switch tag.Kind() {
case names.MachineTagKind:
return a.unpinMachineApplications(tag)
default:
return params.PinApplicationsResults{}, ErrPerm
}
}

// GetMachineApplicationNames returns the applications associated with a
// machine.
func (a *LeadershipPinning) GetMachineApplicationNames(id string) ([]string, error) {
m, err := a.st.Machine(id)
if err != nil {
return result, errors.Trace(err)
return nil, errors.Trace(err)
}
apps, err := m.ApplicationNames()
if err != nil {
return result, errors.Trace(err)
return nil, errors.Trace(err)
}
return apps, nil
}

// PinApplicationLeadersByName takes a slice of application names and attempts
// to pin them accordingly.
func (a *LeadershipPinning) PinApplicationLeadersByName(tag names.Tag, appNames []string) (params.PinApplicationsResults, error) {
return a.pinAppLeadersOps(tag, appNames, a.pinner.PinLeadership)
}

// UnpinApplicationLeadersByName takes a slice of application names and
// attempts to unpin them accordingly.
func (a *LeadershipPinning) UnpinApplicationLeadersByName(tag names.Tag, appNames []string) (params.PinApplicationsResults, error) {
return a.pinAppLeadersOps(tag, appNames, a.pinner.UnpinLeadership)
}

// pinMachineApplications pins leadership for applications represented by units
// running on the auth'd machine.
func (a *LeadershipPinning) pinMachineApplications(tag names.Tag) (params.PinApplicationsResults, error) {
appNames, err := a.GetMachineApplicationNames(tag.Id())
if err != nil {
return params.PinApplicationsResults{}, ErrPerm
}
return a.pinAppLeadersOps(tag, appNames, a.pinner.PinLeadership)
}

// unpinMachineApplications unpins leadership for applications represented by
// units running on the auth'd machine.
func (a *LeadershipPinning) unpinMachineApplications(tag names.Tag) (params.PinApplicationsResults, error) {
appNames, err := a.GetMachineApplicationNames(tag.Id())
if err != nil {
return params.PinApplicationsResults{}, ErrPerm
}
return a.pinAppLeadersOps(tag, appNames, a.pinner.UnpinLeadership)
}

// pinAppLeadersOps runs the input pin/unpin operation against all
// applications entities.
// An assumption is made that the validity of the auth tag has been verified
// by the caller.
func (a *LeadershipPinning) pinAppLeadersOps(tag names.Tag, appNames []string, op func(string, string) error) (params.PinApplicationsResults, error) {
var result params.PinApplicationsResults

results := make([]params.PinApplicationResult, len(apps))
for i, app := range apps {
results := make([]params.PinApplicationResult, len(appNames))
for i, app := range appNames {
results[i] = params.PinApplicationResult{
ApplicationName: app,
}
Expand Down
90 changes: 77 additions & 13 deletions apiserver/common/leadership_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type LeadershipSuite struct {

modelTag names.ModelTag
authTag names.Tag
api common.LeadershipPinningAPI
api *common.LeadershipPinning
machineApps []string
}

Expand Down Expand Up @@ -65,73 +65,137 @@ func (s *LeadershipSuite) TestPinnedLeadershipPermissionDenied(c *gc.C) {
c.Check(err, gc.ErrorMatches, "permission denied")
}

func (s *LeadershipSuite) TestPinMachineApplicationsSuccess(c *gc.C) {
func (s *LeadershipSuite) TestPinApplicationLeadersSuccess(c *gc.C) {
defer s.setup(c).Finish()

for _, app := range s.machineApps {
s.pinner.EXPECT().PinLeadership(app, s.authTag.String()).Return(nil)
}

res, err := s.api.PinMachineApplications()
res, err := s.api.PinApplicationLeaders()
c.Assert(err, jc.ErrorIsNil)
c.Check(res, gc.DeepEquals, params.PinApplicationsResults{Results: s.pinApplicationsSuccessResults()})
}

func (s *LeadershipSuite) TestPinMachineApplicationsPartialError(c *gc.C) {
func (s *LeadershipSuite) TestPinApplicationLeadersPartialError(c *gc.C) {
defer s.setup(c).Finish()

errorRes := errors.New("boom")
s.pinner.EXPECT().PinLeadership("mysql", s.authTag.String()).Return(nil)
s.pinner.EXPECT().PinLeadership("redis", s.authTag.String()).Return(nil)
s.pinner.EXPECT().PinLeadership("wordpress", s.authTag.String()).Return(errorRes)

res, err := s.api.PinMachineApplications()
res, err := s.api.PinApplicationLeaders()
c.Assert(err, jc.ErrorIsNil)

results := s.pinApplicationsSuccessResults()
results[2].Error = common.ServerError(errorRes)
c.Check(res, gc.DeepEquals, params.PinApplicationsResults{Results: results})
}

func (s *LeadershipSuite) TestUnpinMachineApplicationsSuccess(c *gc.C) {
func (s *LeadershipSuite) TestUnpinApplicationLeadersSuccess(c *gc.C) {
defer s.setup(c).Finish()

for _, app := range s.machineApps {
s.pinner.EXPECT().UnpinLeadership(app, s.authTag.String()).Return(nil)
}

res, err := s.api.UnpinMachineApplications()
res, err := s.api.UnpinApplicationLeaders()
c.Assert(err, jc.ErrorIsNil)
c.Check(res, gc.DeepEquals, params.PinApplicationsResults{Results: s.pinApplicationsSuccessResults()})
}

func (s *LeadershipSuite) TestUnpinMachineApplicationsPartialError(c *gc.C) {
func (s *LeadershipSuite) TestUnpinApplicationLeadersPartialError(c *gc.C) {
defer s.setup(c).Finish()

errorRes := errors.New("boom")
s.pinner.EXPECT().UnpinLeadership("mysql", s.authTag.String()).Return(nil)
s.pinner.EXPECT().UnpinLeadership("redis", s.authTag.String()).Return(errorRes)
s.pinner.EXPECT().UnpinLeadership("wordpress", s.authTag.String()).Return(nil)

res, err := s.api.UnpinMachineApplications()
res, err := s.api.UnpinApplicationLeaders()
c.Assert(err, jc.ErrorIsNil)

results := s.pinApplicationsSuccessResults()
results[1].Error = common.ServerError(errorRes)
c.Check(res, gc.DeepEquals, params.PinApplicationsResults{Results: results})
}

func (s *LeadershipSuite) TestPinMachineApplicationsPermissionDenied(c *gc.C) {
func (s *LeadershipSuite) TestPinApplicationLeadersPermissionDenied(c *gc.C) {
s.authTag = names.NewUserTag("some-random-cat")
defer s.setup(c).Finish()

_, err := s.api.PinMachineApplications()
_, err := s.api.PinApplicationLeaders()
c.Assert(err, gc.ErrorMatches, "permission denied")

_, err = s.api.UnpinMachineApplications()
_, err = s.api.UnpinApplicationLeaders()
c.Assert(err, gc.ErrorMatches, "permission denied")
}

func (s *LeadershipSuite) TestGetMachineApplicationNamesSuccess(c *gc.C) {
defer s.setup(c).Finish()

appNames, err := s.api.GetMachineApplicationNames(s.authTag.Id())
c.Assert(err, jc.ErrorIsNil)
c.Check(appNames, gc.DeepEquals, s.machineApps)
}

func (s *LeadershipSuite) TestPinApplicationLeadersByNameSuccess(c *gc.C) {
defer s.setup(c).Finish()

for _, app := range s.machineApps {
s.pinner.EXPECT().PinLeadership(app, s.authTag.String()).Return(nil)
}

res, err := s.api.PinApplicationLeadersByName(s.authTag, s.machineApps)
c.Assert(err, jc.ErrorIsNil)
c.Check(res, gc.DeepEquals, params.PinApplicationsResults{Results: s.pinApplicationsSuccessResults()})
}

func (s *LeadershipSuite) TestPinApplicationLeadersByNamePartialError(c *gc.C) {
defer s.setup(c).Finish()

errorRes := errors.New("boom")
s.pinner.EXPECT().PinLeadership("mysql", s.authTag.String()).Return(nil)
s.pinner.EXPECT().PinLeadership("redis", s.authTag.String()).Return(errorRes)
s.pinner.EXPECT().PinLeadership("wordpress", s.authTag.String()).Return(nil)

res, err := s.api.PinApplicationLeadersByName(s.authTag, s.machineApps)
c.Assert(err, jc.ErrorIsNil)

results := s.pinApplicationsSuccessResults()
results[1].Error = common.ServerError(errorRes)
c.Check(res, gc.DeepEquals, params.PinApplicationsResults{Results: results})
}

func (s *LeadershipSuite) TestUnpinApplicationLeadersByNameSuccess(c *gc.C) {
defer s.setup(c).Finish()

for _, app := range s.machineApps {
s.pinner.EXPECT().UnpinLeadership(app, s.authTag.String()).Return(nil)
}

res, err := s.api.UnpinApplicationLeadersByName(s.authTag, s.machineApps)
c.Assert(err, jc.ErrorIsNil)
c.Check(res, gc.DeepEquals, params.PinApplicationsResults{Results: s.pinApplicationsSuccessResults()})
}

func (s *LeadershipSuite) TestUnpinApplicationLeadersByNamePartialError(c *gc.C) {
defer s.setup(c).Finish()

errorRes := errors.New("boom")
s.pinner.EXPECT().UnpinLeadership("mysql", s.authTag.String()).Return(nil)
s.pinner.EXPECT().UnpinLeadership("redis", s.authTag.String()).Return(errorRes)
s.pinner.EXPECT().UnpinLeadership("wordpress", s.authTag.String()).Return(nil)

res, err := s.api.UnpinApplicationLeadersByName(s.authTag, s.machineApps)
c.Assert(err, jc.ErrorIsNil)

results := s.pinApplicationsSuccessResults()
results[1].Error = common.ServerError(errorRes)
c.Check(res, gc.DeepEquals, params.PinApplicationsResults{Results: results})
}

func (s *LeadershipSuite) setup(c *gc.C) *gomock.Controller {
ctrl := gomock.NewController(c)

Expand All @@ -147,7 +211,7 @@ func (s *LeadershipSuite) setup(c *gc.C) *gomock.Controller {
}

var err error
s.api, err = common.NewLeadershipPinningAPI(
s.api, err = common.NewLeadershipPinning(
s.backend,
s.modelTag,
s.pinner,
Expand Down
Loading

0 comments on commit 6f6656e

Please sign in to comment.