Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port stickupkid/unpin machine applications on destroy #11641

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
124 changes: 83 additions & 41 deletions apiserver/common/leadership.go
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
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