github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/lease/util_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package lease_test
     5  
     6  import (
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/juju/errors"
    11  	jc "github.com/juju/testing/checkers"
    12  	gc "gopkg.in/check.v1"
    13  	"gopkg.in/juju/names.v2"
    14  
    15  	"github.com/juju/juju/core/lease"
    16  	coretesting "github.com/juju/juju/testing"
    17  )
    18  
    19  // Secretary implements lease.Secretary for testing purposes.
    20  type Secretary struct{}
    21  
    22  // CheckLease is part of the lease.Secretary interface.
    23  func (Secretary) CheckLease(key lease.Key) error {
    24  	return checkName(key.Lease)
    25  }
    26  
    27  // CheckHolder is part of the lease.Secretary interface.
    28  func (Secretary) CheckHolder(name string) error {
    29  	return checkName(name)
    30  }
    31  
    32  func checkName(name string) error {
    33  	if name == "INVALID" {
    34  		return errors.NotValidf("name")
    35  	}
    36  	return nil
    37  }
    38  
    39  // CheckDuration is part of the lease.Secretary interface.
    40  func (Secretary) CheckDuration(duration time.Duration) error {
    41  	if duration != time.Minute {
    42  		return errors.NotValidf("time")
    43  	}
    44  	return nil
    45  }
    46  
    47  // Store implements corelease.Store for testing purposes.
    48  type Store struct {
    49  	mu           sync.Mutex
    50  	autoexpire   bool
    51  	leases       map[lease.Key]lease.Info
    52  	expect       []call
    53  	failed       chan error
    54  	runningCalls int
    55  	done         chan struct{}
    56  }
    57  
    58  // NewStore initializes and returns a new store configured to report
    59  // the supplied leases and expect the supplied calls.
    60  func NewStore(autoexpire bool, leases map[lease.Key]lease.Info, expect []call) *Store {
    61  	if leases == nil {
    62  		leases = make(map[lease.Key]lease.Info)
    63  	}
    64  	done := make(chan struct{})
    65  	if len(expect) == 0 {
    66  		close(done)
    67  	}
    68  	return &Store{
    69  		leases:     leases,
    70  		expect:     expect,
    71  		done:       done,
    72  		failed:     make(chan error, 1000),
    73  		autoexpire: autoexpire,
    74  	}
    75  }
    76  
    77  // Wait will return when all expected calls have been made, or fail the test
    78  // if they don't happen within a second. (You control the clock; your tests
    79  // should pass in *way* less than 10 seconds of wall-clock time.)
    80  func (store *Store) Wait(c *gc.C) {
    81  	select {
    82  	case <-store.done:
    83  		select {
    84  		case err := <-store.failed:
    85  			c.Errorf(err.Error())
    86  		default:
    87  		}
    88  	case <-time.After(coretesting.LongWait):
    89  		store.mu.Lock()
    90  		remaining := make([]string, len(store.expect))
    91  		for i := range store.expect {
    92  			remaining[i] = store.expect[i].method
    93  		}
    94  		store.mu.Unlock()
    95  		c.Errorf("Store test took way too long, still expecting %v", remaining)
    96  	}
    97  }
    98  
    99  // Autoexpire is part of the lease.Store interface.
   100  func (store *Store) Autoexpire() bool {
   101  	return store.autoexpire
   102  }
   103  
   104  // Leases is part of the lease.Store interface.
   105  func (store *Store) Leases(keys ...lease.Key) map[lease.Key]lease.Info {
   106  	filter := make(map[lease.Key]bool)
   107  	filtering := len(keys) > 0
   108  	if filtering {
   109  		for _, key := range keys {
   110  			filter[key] = true
   111  		}
   112  	}
   113  
   114  	store.mu.Lock()
   115  	defer store.mu.Unlock()
   116  	result := make(map[lease.Key]lease.Info)
   117  	for k, v := range store.leases {
   118  		if filtering && !filter[k] {
   119  			continue
   120  		}
   121  		result[k] = v
   122  	}
   123  	return result
   124  }
   125  
   126  func (store *Store) closeIfEmpty() {
   127  	// This must be called with the lock held.
   128  	if store.runningCalls > 1 {
   129  		// The last one to leave should turn out the lights.
   130  		return
   131  	}
   132  	if len(store.expect) == 0 || len(store.failed) > 0 {
   133  		close(store.done)
   134  	}
   135  }
   136  
   137  // call implements the bulk of the lease.Store interface.
   138  func (store *Store) call(method string, args []interface{}) error {
   139  	store.mu.Lock()
   140  	defer store.mu.Unlock()
   141  
   142  	store.runningCalls++
   143  	defer func() {
   144  		store.runningCalls--
   145  	}()
   146  
   147  	select {
   148  	case <-store.done:
   149  		err := errors.Errorf("Store method called after test complete: %s %v", method, args)
   150  		store.failed <- err
   151  		return err
   152  	default:
   153  	}
   154  	defer store.closeIfEmpty()
   155  
   156  	if len(store.expect) < 1 {
   157  		err := errors.Errorf("store.%s called but was not expected", method)
   158  		store.failed <- err
   159  		return err
   160  	}
   161  	expect := store.expect[0]
   162  	store.expect = store.expect[1:]
   163  	if expect.parallelCallback != nil {
   164  		store.mu.Unlock()
   165  		expect.parallelCallback(&store.mu, store.leases)
   166  		store.mu.Lock()
   167  	}
   168  	if expect.callback != nil {
   169  		expect.callback(store.leases)
   170  	}
   171  
   172  	if method == expect.method {
   173  		if ok, _ := jc.DeepEqual(args, expect.args); ok {
   174  			return expect.err
   175  		}
   176  	}
   177  	err := errors.Errorf("unexpected Store call:\n  actual: %s %v\n  expect: %s %v",
   178  		method, args, expect.method, expect.args,
   179  	)
   180  	store.failed <- err
   181  	return err
   182  }
   183  
   184  // ClaimLease is part of the corelease.Store interface.
   185  func (store *Store) ClaimLease(key lease.Key, request lease.Request) error {
   186  	return store.call("ClaimLease", []interface{}{key, request})
   187  }
   188  
   189  // ExtendLease is part of the corelease.Store interface.
   190  func (store *Store) ExtendLease(key lease.Key, request lease.Request) error {
   191  	return store.call("ExtendLease", []interface{}{key, request})
   192  }
   193  
   194  // ExpireLease is part of the corelease.Store interface.
   195  func (store *Store) ExpireLease(key lease.Key) error {
   196  	return store.call("ExpireLease", []interface{}{key})
   197  }
   198  
   199  // Refresh is part of the lease.Store interface.
   200  func (store *Store) Refresh() error {
   201  	return store.call("Refresh", nil)
   202  }
   203  
   204  // PinLease is part of the corelease.Store interface.
   205  func (store *Store) PinLease(key lease.Key, entity string) error {
   206  	return store.call("PinLease", []interface{}{key, entity})
   207  }
   208  
   209  // UnpinLease is part of the corelease.Store interface.
   210  func (store *Store) UnpinLease(key lease.Key, entity string) error {
   211  	return store.call("UnpinLease", []interface{}{key, entity})
   212  }
   213  
   214  func (store *Store) Pinned() map[lease.Key][]string {
   215  	store.call("Pinned", nil)
   216  	return map[lease.Key][]string{
   217  		{
   218  			Namespace: "namespace",
   219  			ModelUUID: "modelUUID",
   220  			Lease:     "redis",
   221  		}: {names.NewMachineTag("0").String()},
   222  		{
   223  			Namespace: "ignored-namespace",
   224  			ModelUUID: "ignored modelUUID",
   225  			Lease:     "lolwut",
   226  		}: {names.NewMachineTag("666").String()},
   227  	}
   228  }
   229  
   230  // call defines a expected method call on a Store; it encodes:
   231  type call struct {
   232  
   233  	// method is the name of the method.
   234  	method string
   235  
   236  	// args is the expected arguments.
   237  	args []interface{}
   238  
   239  	// err is the error to return.
   240  	err error
   241  
   242  	// callback, if non-nil, will be passed the internal leases dict; for
   243  	// modification, if desired. Otherwise you can use it to, e.g., assert
   244  	// clock time.
   245  	callback func(leases map[lease.Key]lease.Info)
   246  
   247  	// parallelCallback is like callback, but is also passed the
   248  	// lock. It's for testing calls that happen in parallel, where one
   249  	// might take longer than another. Any update to the leases dict
   250  	// must only happen while the lock is held.
   251  	parallelCallback func(mu *sync.Mutex, leases map[lease.Key]lease.Info)
   252  }
   253  
   254  func key(args ...string) lease.Key {
   255  	result := lease.Key{
   256  		Namespace: "namespace",
   257  		ModelUUID: "modelUUID",
   258  		Lease:     "lease",
   259  	}
   260  	if len(args) == 1 {
   261  		result.Lease = args[0]
   262  	} else if len(args) == 3 {
   263  		result.Namespace = args[0]
   264  		result.ModelUUID = args[1]
   265  		result.Lease = args[2]
   266  	}
   267  	return result
   268  }