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  }