github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/agent/leadership/leadership.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package leadership 5 6 import ( 7 "context" 8 "time" 9 10 "github.com/juju/errors" 11 "gopkg.in/juju/names.v2" 12 13 "github.com/juju/juju/apiserver/common" 14 "github.com/juju/juju/apiserver/facade" 15 "github.com/juju/juju/apiserver/params" 16 "github.com/juju/juju/core/leadership" 17 ) 18 19 const ( 20 // FacadeName is the string-representation of this API used both 21 // to register the service, and for the client to resolve the 22 // service endpoint. 23 FacadeName = "LeadershipService" 24 25 // MinLeaseRequest is the shortest duration for which we will accept 26 // a leadership claim. 27 MinLeaseRequest = 5 * time.Second 28 29 // MaxLeaseRequest is the longest duration for which we will accept 30 // a leadership claim. 31 MaxLeaseRequest = 5 * time.Minute 32 ) 33 34 // NewLeadershipServiceFacade constructs a new LeadershipService and presents 35 // a signature that can be used for facade registration. 36 func NewLeadershipServiceFacade(context facade.Context) (LeadershipService, error) { 37 claimer, err := context.LeadershipClaimer(context.State().ModelUUID()) 38 if err != nil { 39 return nil, errors.Trace(err) 40 } 41 return NewLeadershipService(claimer, context.Auth()) 42 } 43 44 // NewLeadershipService constructs a new LeadershipService. 45 func NewLeadershipService( 46 claimer leadership.Claimer, authorizer facade.Authorizer, 47 ) (LeadershipService, error) { 48 49 if !authorizer.AuthUnitAgent() && !authorizer.AuthApplicationAgent() { 50 return nil, errors.Unauthorizedf("permission denied") 51 } 52 53 return &leadershipService{ 54 claimer: claimer, 55 authorizer: authorizer, 56 }, nil 57 } 58 59 // leadershipService implements the LeadershipService interface and 60 // is the concrete implementation of the API endpoint. 61 type leadershipService struct { 62 claimer leadership.Claimer 63 authorizer facade.Authorizer 64 } 65 66 // ClaimLeadership is part of the LeadershipService interface. 67 func (m *leadershipService) ClaimLeadership(args params.ClaimLeadershipBulkParams) (params.ClaimLeadershipBulkResults, error) { 68 69 results := make([]params.ErrorResult, len(args.Params)) 70 for pIdx, p := range args.Params { 71 72 result := &results[pIdx] 73 applicationTag, unitTag, err := parseApplicationAndUnitTags(p.ApplicationTag, p.UnitTag) 74 if err != nil { 75 result.Error = common.ServerError(err) 76 continue 77 } 78 duration := time.Duration(p.DurationSeconds * float64(time.Second)) 79 if duration > MaxLeaseRequest || duration < MinLeaseRequest { 80 result.Error = common.ServerError(errors.New("invalid duration")) 81 continue 82 } 83 84 // In the future, situations may arise wherein units will make 85 // leadership claims for other units. For now, units can only 86 // claim leadership for themselves, for their own service. 87 authTag := m.authorizer.GetAuthTag() 88 canClaim := false 89 switch authTag.(type) { 90 case names.UnitTag: 91 canClaim = m.authorizer.AuthOwner(unitTag) && m.authMember(applicationTag) 92 case names.ApplicationTag: 93 canClaim = m.authorizer.AuthOwner(applicationTag) 94 } 95 if !canClaim { 96 result.Error = common.ServerError(common.ErrPerm) 97 continue 98 } 99 if err = m.claimer.ClaimLeadership(applicationTag.Id(), unitTag.Id(), duration); err != nil { 100 result.Error = common.ServerError(err) 101 } 102 } 103 104 return params.ClaimLeadershipBulkResults{results}, nil 105 } 106 107 // BlockUntilLeadershipReleased implements the LeadershipService interface. 108 func (m *leadershipService) BlockUntilLeadershipReleased(ctx context.Context, applicationTag names.ApplicationTag) (params.ErrorResult, error) { 109 authTag := m.authorizer.GetAuthTag() 110 hasPerm := false 111 switch authTag.(type) { 112 case names.UnitTag: 113 hasPerm = m.authMember(applicationTag) 114 case names.ApplicationTag: 115 hasPerm = m.authorizer.AuthOwner(applicationTag) 116 } 117 118 if !hasPerm { 119 return params.ErrorResult{Error: common.ServerError(common.ErrPerm)}, nil 120 } 121 122 if err := m.claimer.BlockUntilLeadershipReleased(applicationTag.Id(), ctx.Done()); err != nil { 123 return params.ErrorResult{Error: common.ServerError(err)}, nil 124 } 125 return params.ErrorResult{}, nil 126 } 127 128 func (m *leadershipService) authMember(applicationTag names.ApplicationTag) bool { 129 ownerTag := m.authorizer.GetAuthTag() 130 unitTag, ok := ownerTag.(names.UnitTag) 131 if !ok { 132 return false 133 } 134 unitId := unitTag.Id() 135 requireAppName, err := names.UnitApplication(unitId) 136 if err != nil { 137 return false 138 } 139 return applicationTag.Id() == requireAppName 140 } 141 142 // parseApplicationAndUnitTags takes in string representations of application 143 // and unit tags and returns their corresponding tags. 144 func parseApplicationAndUnitTags( 145 applicationTagString, unitTagString string, 146 ) ( 147 names.ApplicationTag, names.UnitTag, error, 148 ) { 149 // TODO(fwereade) 2015-02-25 bug #1425506 150 // These permissions errors are not appropriate -- there's no permission or 151 // security issue in play here, because our tag format is public, and the 152 // error only triggers when the strings fail to match that format. 153 applicationTag, err := names.ParseApplicationTag(applicationTagString) 154 if err != nil { 155 return names.ApplicationTag{}, names.UnitTag{}, common.ErrPerm 156 } 157 158 unitTag, err := names.ParseUnitTag(unitTagString) 159 if err != nil { 160 return names.ApplicationTag{}, names.UnitTag{}, common.ErrPerm 161 } 162 163 return applicationTag, unitTag, nil 164 }