github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/worker/lease/manager.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package lease
     5  
     6  import (
     7  	"sort"
     8  	"time"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/loggo"
    12  	"github.com/juju/utils/clock"
    13  
    14  	"github.com/juju/juju/core/lease"
    15  	"github.com/juju/juju/worker/catacomb"
    16  )
    17  
    18  var logger = loggo.GetLogger("juju.worker.lease")
    19  
    20  // errStopped is returned to clients when an operation cannot complete because
    21  // the manager has started (and possibly finished) shutdown.
    22  var errStopped = errors.New("lease manager stopped")
    23  
    24  // NewManager returns a new *Manager configured as supplied. The caller takes
    25  // responsibility for killing, and handling errors from, the returned Worker.
    26  func NewManager(config ManagerConfig) (*Manager, error) {
    27  	if err := config.Validate(); err != nil {
    28  		return nil, errors.Trace(err)
    29  	}
    30  	manager := &Manager{
    31  		config: config,
    32  		claims: make(chan claim),
    33  		checks: make(chan check),
    34  		blocks: make(chan block),
    35  	}
    36  	err := catacomb.Invoke(catacomb.Plan{
    37  		Site: &manager.catacomb,
    38  		Work: manager.loop,
    39  	})
    40  	if err != nil {
    41  		return nil, errors.Trace(err)
    42  	}
    43  	return manager, nil
    44  }
    45  
    46  // Manager implements lease.Claimer, lease.Checker, and worker.Worker.
    47  type Manager struct {
    48  	catacomb catacomb.Catacomb
    49  
    50  	// config collects all external configuration and dependencies.
    51  	config ManagerConfig
    52  
    53  	// claims is used to deliver lease claim requests to the loop.
    54  	claims chan claim
    55  
    56  	// checks is used to deliver lease check requests to the loop.
    57  	checks chan check
    58  
    59  	// blocks is used to deliver expiry block requests to the loop.
    60  	blocks chan block
    61  }
    62  
    63  // Kill is part of the worker.Worker interface.
    64  func (manager *Manager) Kill() {
    65  	manager.catacomb.Kill(nil)
    66  }
    67  
    68  // Wait is part of the worker.Worker interface.
    69  func (manager *Manager) Wait() error {
    70  	return manager.catacomb.Wait()
    71  }
    72  
    73  // loop runs until the manager is stopped.
    74  func (manager *Manager) loop() error {
    75  	blocks := make(blocks)
    76  	for {
    77  		if err := manager.choose(blocks); err != nil {
    78  			return errors.Trace(err)
    79  		}
    80  
    81  		leases := manager.config.Client.Leases()
    82  		for leaseName := range blocks {
    83  			if _, found := leases[leaseName]; !found {
    84  				blocks.unblock(leaseName)
    85  			}
    86  		}
    87  	}
    88  }
    89  
    90  // choose breaks the select out of loop to make the blocking logic clearer.
    91  func (manager *Manager) choose(blocks blocks) error {
    92  	select {
    93  	case <-manager.catacomb.Dying():
    94  		return manager.catacomb.ErrDying()
    95  	case <-manager.nextTick():
    96  		return manager.tick()
    97  	case claim := <-manager.claims:
    98  		return manager.handleClaim(claim)
    99  	case check := <-manager.checks:
   100  		return manager.handleCheck(check)
   101  	case block := <-manager.blocks:
   102  		blocks.add(block)
   103  		return nil
   104  	}
   105  }
   106  
   107  // Claim is part of the lease.Claimer interface.
   108  func (manager *Manager) Claim(leaseName, holderName string, duration time.Duration) error {
   109  	if err := manager.config.Secretary.CheckLease(leaseName); err != nil {
   110  		return errors.Annotatef(err, "cannot claim lease %q", leaseName)
   111  	}
   112  	if err := manager.config.Secretary.CheckHolder(holderName); err != nil {
   113  		return errors.Annotatef(err, "cannot claim lease for holder %q", holderName)
   114  	}
   115  	if err := manager.config.Secretary.CheckDuration(duration); err != nil {
   116  		return errors.Annotatef(err, "cannot claim lease for %s", duration)
   117  	}
   118  	return claim{
   119  		leaseName:  leaseName,
   120  		holderName: holderName,
   121  		duration:   duration,
   122  		response:   make(chan bool),
   123  		abort:      manager.catacomb.Dying(),
   124  	}.invoke(manager.claims)
   125  }
   126  
   127  // handleClaim processes and responds to the supplied claim. It will only return
   128  // unrecoverable errors; mere failure to claim just indicates a bad request, and
   129  // is communicated back to the claim's originator.
   130  func (manager *Manager) handleClaim(claim claim) error {
   131  	client := manager.config.Client
   132  	request := lease.Request{claim.holderName, claim.duration}
   133  	err := lease.ErrInvalid
   134  	for err == lease.ErrInvalid {
   135  		select {
   136  		case <-manager.catacomb.Dying():
   137  			return manager.catacomb.ErrDying()
   138  		default:
   139  			info, found := client.Leases()[claim.leaseName]
   140  			switch {
   141  			case !found:
   142  				err = client.ClaimLease(claim.leaseName, request)
   143  			case info.Holder == claim.holderName:
   144  				err = client.ExtendLease(claim.leaseName, request)
   145  			default:
   146  				claim.respond(false)
   147  				return nil
   148  			}
   149  		}
   150  	}
   151  	if err != nil {
   152  		return errors.Trace(err)
   153  	}
   154  	claim.respond(true)
   155  	return nil
   156  }
   157  
   158  // Token is part of the lease.Checker interface.
   159  func (manager *Manager) Token(leaseName, holderName string) lease.Token {
   160  	return token{
   161  		leaseName:  leaseName,
   162  		holderName: holderName,
   163  		secretary:  manager.config.Secretary,
   164  		checks:     manager.checks,
   165  		abort:      manager.catacomb.Dying(),
   166  	}
   167  }
   168  
   169  // handleCheck processes and responds to the supplied check. It will only return
   170  // unrecoverable errors; mere untruth of the assertion just indicates a bad
   171  // request, and is communicated back to the check's originator.
   172  func (manager *Manager) handleCheck(check check) error {
   173  	client := manager.config.Client
   174  	info, found := client.Leases()[check.leaseName]
   175  	if !found || info.Holder != check.holderName {
   176  		if err := client.Refresh(); err != nil {
   177  			return errors.Trace(err)
   178  		}
   179  		info, found = client.Leases()[check.leaseName]
   180  	}
   181  
   182  	var response error
   183  	if !found || info.Holder != check.holderName {
   184  		response = lease.ErrNotHeld
   185  	} else if check.trapdoorKey != nil {
   186  		response = info.Trapdoor(check.trapdoorKey)
   187  	}
   188  	check.respond(errors.Trace(response))
   189  	return nil
   190  }
   191  
   192  // WaitUntilExpired is part of the lease.Claimer interface.
   193  func (manager *Manager) WaitUntilExpired(leaseName string) error {
   194  	if err := manager.config.Secretary.CheckLease(leaseName); err != nil {
   195  		return errors.Annotatef(err, "cannot wait for lease %q expiry", leaseName)
   196  	}
   197  	return block{
   198  		leaseName: leaseName,
   199  		unblock:   make(chan struct{}),
   200  		abort:     manager.catacomb.Dying(),
   201  	}.invoke(manager.blocks)
   202  }
   203  
   204  // nextTick returns a channel that will send a value at some point when
   205  // we expect to have to do some work; either because at least one lease
   206  // may be ready to expire, or because enough enough time has passed that
   207  // it's worth checking for stalled collaborators.
   208  func (manager *Manager) nextTick() <-chan time.Time {
   209  	now := manager.config.Clock.Now()
   210  	nextTick := now.Add(manager.config.MaxSleep)
   211  	for _, info := range manager.config.Client.Leases() {
   212  		if info.Expiry.After(nextTick) {
   213  			continue
   214  		}
   215  		nextTick = info.Expiry
   216  	}
   217  	logger.Debugf("waking to check leases at %s", nextTick)
   218  	return clock.Alarm(manager.config.Clock, nextTick)
   219  }
   220  
   221  // tick snapshots recent leases and expires any that it can. There
   222  // might be none that need attention; or those that do might already
   223  // have been extended or expired by someone else; so ErrInvalid is
   224  // expected, and ignored, comfortable that the client will have been
   225  // updated in the background; and that we'll see fresh info when we
   226  // subsequently check nextWake().
   227  //
   228  // It will return only unrecoverable errors.
   229  func (manager *Manager) tick() error {
   230  	logger.Tracef("refreshing leases...")
   231  	client := manager.config.Client
   232  	if err := client.Refresh(); err != nil {
   233  		return errors.Trace(err)
   234  	}
   235  	leases := client.Leases()
   236  
   237  	// Sort lease names so we expire in a predictable order for the tests.
   238  	names := make([]string, 0, len(leases))
   239  	for name := range leases {
   240  		names = append(names, name)
   241  	}
   242  	sort.Strings(names)
   243  
   244  	logger.Tracef("expiring leases...")
   245  	now := manager.config.Clock.Now()
   246  	for _, name := range names {
   247  		if leases[name].Expiry.After(now) {
   248  			continue
   249  		}
   250  		switch err := client.ExpireLease(name); err {
   251  		case nil, lease.ErrInvalid:
   252  		default:
   253  			return errors.Trace(err)
   254  		}
   255  	}
   256  	return nil
   257  }