github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/lease/bound.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package lease
     5  
     6  import (
     7  	"context"
     8  	"time"
     9  
    10  	"github.com/juju/errors"
    11  
    12  	"github.com/juju/juju/core/lease"
    13  )
    14  
    15  // broker describes methods for manipulating and checking leases.
    16  type broker interface {
    17  	lease.Checker
    18  	lease.Claimer
    19  	lease.Revoker
    20  	lease.Pinner
    21  	lease.Reader
    22  }
    23  
    24  // boundManager implements the broker interface.
    25  // It represents a lease manager for a specific namespace and model.
    26  type boundManager struct {
    27  	manager   *Manager
    28  	secretary Secretary
    29  	namespace string
    30  	modelUUID string
    31  }
    32  
    33  // Claim is part of the lease.Claimer interface.
    34  func (b *boundManager) Claim(leaseName, holderName string, duration time.Duration) error {
    35  	key := b.leaseKey(leaseName)
    36  	if err := b.secretary.CheckLease(key); err != nil {
    37  		return errors.Annotatef(err, "cannot claim lease %q", leaseName)
    38  	}
    39  	if err := b.secretary.CheckHolder(holderName); err != nil {
    40  		return errors.Annotatef(err, "cannot claim lease for holder %q", holderName)
    41  	}
    42  	if err := b.secretary.CheckDuration(duration); err != nil {
    43  		return errors.Annotatef(err, "cannot claim lease for %s", duration)
    44  	}
    45  
    46  	return claim{
    47  		leaseKey:   key,
    48  		holderName: holderName,
    49  		duration:   duration,
    50  		response:   make(chan error),
    51  		stop:       b.manager.tomb.Dying(),
    52  	}.invoke(b.manager.claims)
    53  }
    54  
    55  // Revoke is part of the lease.Revoker interface.
    56  func (b *boundManager) Revoke(leaseName, holderName string) error {
    57  	key := b.leaseKey(leaseName)
    58  	if err := b.secretary.CheckLease(key); err != nil {
    59  		return errors.Annotatef(err, "cannot revoke lease %q", leaseName)
    60  	}
    61  	if err := b.secretary.CheckHolder(holderName); err != nil {
    62  		return errors.Annotatef(err, "cannot revoke lease for holder %q", holderName)
    63  	}
    64  
    65  	return revoke{
    66  		leaseKey:   key,
    67  		holderName: holderName,
    68  		response:   make(chan error),
    69  		stop:       b.manager.tomb.Dying(),
    70  	}.invoke(b.manager.revokes)
    71  }
    72  
    73  // WaitUntilExpired is part of the lease.Claimer interface.
    74  func (b *boundManager) WaitUntilExpired(leaseName string, cancel <-chan struct{}) error {
    75  	key := b.leaseKey(leaseName)
    76  	if err := b.secretary.CheckLease(key); err != nil {
    77  		return errors.Annotatef(err, "cannot wait for lease %q expiry", leaseName)
    78  	}
    79  
    80  	return block{
    81  		leaseKey: key,
    82  		unblock:  make(chan struct{}),
    83  		stop:     b.manager.tomb.Dying(),
    84  		cancel:   cancel,
    85  	}.invoke(b.manager.blocks)
    86  }
    87  
    88  // Token is part of the lease.Checker interface.
    89  func (b *boundManager) Token(leaseName, holderName string) lease.Token {
    90  	return token{
    91  		leaseKey:   b.leaseKey(leaseName),
    92  		holderName: holderName,
    93  		secretary:  b.secretary,
    94  		checks:     b.manager.checks,
    95  		stop:       b.manager.tomb.Dying(),
    96  	}
    97  }
    98  
    99  // Leases (lease.Reader) returns all leases and holders
   100  // in the bound namespace/model.
   101  func (b *boundManager) Leases() (map[string]string, error) {
   102  	ctx, cancel := b.tombContextWithCancel()
   103  	defer cancel()
   104  
   105  	leases, err := b.manager.leases(ctx, b.namespace, b.modelUUID)
   106  	return leases, errors.Trace(err)
   107  }
   108  
   109  // Pinned (lease.Pinner) returns applications and the entities requiring their
   110  // pinned behaviour, for pinned leases in the bound namespace/model.
   111  func (b *boundManager) Pinned() (map[string][]string, error) {
   112  	ctx, cancel := b.tombContextWithCancel()
   113  	defer cancel()
   114  
   115  	pinned, err := b.manager.pinned(ctx, b.namespace, b.modelUUID)
   116  	return pinned, errors.Trace(err)
   117  }
   118  
   119  // tombContextWithCancel is a work-around for the bound manager that exposes
   120  // calls to lease store methods outside the worker loop.
   121  // Here, we create a new cancelable context and use that as a parent for
   122  // tomb.Context. This means that killing the tomb will cancel the returned
   123  // context, ensuring that these calls cannot block worker shutdown.
   124  // Every cancel func returned from this method must be called when the
   125  // function its context was passed to returns. Contexts are stored in the tomb,
   126  // which deletes those that are `done` whenever a new context is added.
   127  func (b *boundManager) tombContextWithCancel() (context.Context, func()) {
   128  	parent, cancel := context.WithCancel(context.Background())
   129  	return b.manager.tomb.Context(parent), cancel
   130  }
   131  
   132  // Pin (lease.Pinner) sends a pin message to the worker loop.
   133  func (b *boundManager) Pin(leaseName string, entity string) error {
   134  	return errors.Trace(b.pinOp(leaseName, entity, b.manager.pins))
   135  }
   136  
   137  // Unpin (lease.Pinner) sends an unpin message to the worker loop.
   138  func (b *boundManager) Unpin(leaseName string, entity string) error {
   139  	return errors.Trace(b.pinOp(leaseName, entity, b.manager.unpins))
   140  }
   141  
   142  // pinOp creates a pin instance from the input lease name,
   143  // then sends it on the input channel.
   144  func (b *boundManager) pinOp(leaseName string, entity string, ch chan pin) error {
   145  	return errors.Trace(pin{
   146  		leaseKey: b.leaseKey(leaseName),
   147  		entity:   entity,
   148  		response: make(chan error),
   149  		stop:     b.manager.tomb.Dying(),
   150  	}.invoke(ch))
   151  }
   152  
   153  // leaseKey returns a key for the manager's binding and the input lease name.
   154  func (b *boundManager) leaseKey(leaseName string) lease.Key {
   155  	return lease.Key{
   156  		Namespace: b.namespace,
   157  		ModelUUID: b.modelUUID,
   158  		Lease:     leaseName,
   159  	}
   160  }