github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/core/raftlease/fsm_test.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package raftlease_test
     5  
     6  import (
     7  	"bytes"
     8  	"io"
     9  	"time"
    10  
    11  	"github.com/hashicorp/raft"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/testing"
    14  	jc "github.com/juju/testing/checkers"
    15  	gc "gopkg.in/check.v1"
    16  	"gopkg.in/juju/names.v2"
    17  	"gopkg.in/yaml.v2"
    18  
    19  	"github.com/juju/juju/core/globalclock"
    20  	"github.com/juju/juju/core/lease"
    21  	"github.com/juju/juju/core/raftlease"
    22  )
    23  
    24  var zero time.Time
    25  
    26  type fsmSuite struct {
    27  	testing.IsolationSuite
    28  
    29  	fsm *raftlease.FSM
    30  }
    31  
    32  var _ = gc.Suite(&fsmSuite{})
    33  
    34  func (s *fsmSuite) SetUpTest(c *gc.C) {
    35  	s.IsolationSuite.SetUpTest(c)
    36  	s.fsm = raftlease.NewFSM()
    37  }
    38  
    39  func (s *fsmSuite) apply(c *gc.C, command raftlease.Command) raftlease.FSMResponse {
    40  	data, err := command.Marshal()
    41  	c.Assert(err, jc.ErrorIsNil)
    42  	result := s.fsm.Apply(&raft.Log{Data: data})
    43  	response, ok := result.(raftlease.FSMResponse)
    44  	c.Assert(ok, gc.Equals, true)
    45  	return response
    46  }
    47  
    48  func (s *fsmSuite) TestClaim(c *gc.C) {
    49  	command := raftlease.Command{
    50  		Version:   1,
    51  		Operation: raftlease.OperationClaim,
    52  		Namespace: "ns",
    53  		ModelUUID: "model",
    54  		Lease:     "lease",
    55  		Holder:    "me",
    56  		Duration:  time.Second,
    57  	}
    58  	resp := s.apply(c, command)
    59  	c.Assert(resp.Error(), jc.ErrorIsNil)
    60  	assertClaimed(c, resp, lease.Key{Namespace: "ns", ModelUUID: "model", Lease: "lease"}, "me")
    61  
    62  	c.Assert(s.fsm.Leases(timeDelegate(zero)), gc.DeepEquals,
    63  		map[lease.Key]lease.Info{
    64  			{"ns", "model", "lease"}: {
    65  				Holder: "me",
    66  				Expiry: offset(time.Second),
    67  			},
    68  		},
    69  	)
    70  
    71  	// Can't claim it again.
    72  	resp = s.apply(c, command)
    73  	c.Assert(resp.Error(), jc.Satisfies, lease.IsInvalid)
    74  	assertNoNotifications(c, resp)
    75  
    76  	// Someone else trying to claim the lease.
    77  	command.Holder = "you"
    78  	resp = s.apply(c, command)
    79  	c.Assert(resp.Error(), jc.Satisfies, lease.IsInvalid)
    80  	assertNoNotifications(c, resp)
    81  }
    82  
    83  func offset(d time.Duration) time.Time {
    84  	return zero.Add(d)
    85  }
    86  
    87  func (s *fsmSuite) TestExtend(c *gc.C) {
    88  	// Can't extend unless we've previously claimed.
    89  	command := raftlease.Command{
    90  		Version:   1,
    91  		Operation: raftlease.OperationExtend,
    92  		Namespace: "ns",
    93  		ModelUUID: "model",
    94  		Lease:     "lease",
    95  		Holder:    "me",
    96  		Duration:  time.Second,
    97  	}
    98  	resp := s.apply(c, command)
    99  	c.Assert(resp.Error(), jc.Satisfies, lease.IsInvalid)
   100  	assertNoNotifications(c, resp)
   101  
   102  	// Ok, so we'll claim it.
   103  	command.Operation = raftlease.OperationClaim
   104  	resp = s.apply(c, command)
   105  	c.Assert(resp.Error(), jc.ErrorIsNil)
   106  	assertClaimed(c, resp, lease.Key{Namespace: "ns", ModelUUID: "model", Lease: "lease"}, "me")
   107  
   108  	// Now we can extend it.
   109  	command.Operation = raftlease.OperationExtend
   110  	command.Duration = 2 * time.Second
   111  	resp = s.apply(c, command)
   112  	c.Assert(resp.Error(), jc.ErrorIsNil)
   113  	assertNoNotifications(c, resp)
   114  
   115  	c.Assert(s.fsm.Leases(timeDelegate(zero)), gc.DeepEquals,
   116  		map[lease.Key]lease.Info{
   117  			{"ns", "model", "lease"}: {
   118  				Holder: "me",
   119  				Expiry: offset(2 * time.Second),
   120  			},
   121  		},
   122  	)
   123  
   124  	// Extending by a time less than the remaining duration doesn't
   125  	// shorten the lease (but does succeed).
   126  	command.Duration = time.Millisecond
   127  	resp = s.apply(c, command)
   128  	c.Assert(resp.Error(), jc.ErrorIsNil)
   129  	assertNoNotifications(c, resp)
   130  
   131  	c.Assert(s.fsm.Leases(timeDelegate(zero)), gc.DeepEquals,
   132  		map[lease.Key]lease.Info{
   133  			{"ns", "model", "lease"}: {
   134  				Holder: "me",
   135  				Expiry: offset(2 * time.Second),
   136  			},
   137  		},
   138  	)
   139  
   140  	// Someone else can't extend it.
   141  	command.Holder = "you"
   142  	resp = s.apply(c, command)
   143  	c.Assert(resp.Error(), jc.Satisfies, lease.IsInvalid)
   144  	assertNoNotifications(c, resp)
   145  }
   146  
   147  func (s *fsmSuite) TestSetTime(c *gc.C) {
   148  	// Time always starts at 0.
   149  	resp := s.apply(c, raftlease.Command{
   150  		Version:   1,
   151  		Operation: raftlease.OperationSetTime,
   152  		OldTime:   zero,
   153  		NewTime:   zero.Add(2 * time.Second),
   154  	})
   155  	c.Assert(resp.Error(), jc.ErrorIsNil)
   156  	assertNoNotifications(c, resp)
   157  	c.Assert(s.fsm.GlobalTime(), gc.Equals, zero.Add(2*time.Second))
   158  
   159  	c.Assert(s.apply(c, raftlease.Command{
   160  		Version:   1,
   161  		Operation: raftlease.OperationSetTime,
   162  		OldTime:   zero,
   163  		NewTime:   zero.Add(time.Second),
   164  	}).Error(), jc.Satisfies, globalclock.IsConcurrentUpdate)
   165  }
   166  
   167  func (s *fsmSuite) TestSetTimeExpiresLeases(c *gc.C) {
   168  	c.Assert(s.apply(c, raftlease.Command{
   169  		Version:   1,
   170  		Operation: raftlease.OperationSetTime,
   171  		OldTime:   zero,
   172  		NewTime:   offset(2 * time.Second),
   173  	}).Error(), jc.ErrorIsNil)
   174  	c.Assert(s.apply(c, raftlease.Command{
   175  		Version:   1,
   176  		Operation: raftlease.OperationClaim,
   177  		Namespace: "ns",
   178  		ModelUUID: "model",
   179  		Lease:     "much-earlier",
   180  		Holder:    "you",
   181  		Duration:  time.Second,
   182  	}).Error(), jc.ErrorIsNil)
   183  	c.Assert(s.apply(c, raftlease.Command{
   184  		Version:   1,
   185  		Operation: raftlease.OperationClaim,
   186  		Namespace: "ns",
   187  		ModelUUID: "model",
   188  		Lease:     "just-before",
   189  		Holder:    "you",
   190  		Duration:  (2 * time.Second) - time.Nanosecond,
   191  	}).Error(), jc.ErrorIsNil)
   192  	c.Assert(s.apply(c, raftlease.Command{
   193  		Version:   1,
   194  		Operation: raftlease.OperationClaim,
   195  		Namespace: "ns",
   196  		ModelUUID: "model",
   197  		Lease:     "bang-on",
   198  		Holder:    "you",
   199  		Duration:  2 * time.Second,
   200  	}).Error(), jc.ErrorIsNil)
   201  	c.Assert(s.apply(c, raftlease.Command{
   202  		Version:   1,
   203  		Operation: raftlease.OperationClaim,
   204  		Namespace: "ns",
   205  		ModelUUID: "model",
   206  		Lease:     "well-after",
   207  		Holder:    "them",
   208  		Duration:  time.Minute,
   209  	}).Error(), jc.ErrorIsNil)
   210  
   211  	// Advance time by another 2 seconds, and two of the leases are
   212  	// autoexpired.
   213  	resp := s.apply(c, raftlease.Command{
   214  		Version:   1,
   215  		Operation: raftlease.OperationSetTime,
   216  		OldTime:   offset(2 * time.Second),
   217  		NewTime:   offset(4 * time.Second),
   218  	})
   219  	c.Assert(resp.Error(), jc.ErrorIsNil)
   220  	assertExpired(c, resp,
   221  		lease.Key{Namespace: "ns", ModelUUID: "model", Lease: "much-earlier"},
   222  		lease.Key{Namespace: "ns", ModelUUID: "model", Lease: "just-before"},
   223  	)
   224  
   225  	// Using the same local time as global time to keep things clear.
   226  	c.Assert(s.fsm.Leases(timeDelegate(offset(4*time.Second))), gc.DeepEquals,
   227  		map[lease.Key]lease.Info{
   228  			{"ns", "model", "bang-on"}: {
   229  				Holder: "you",
   230  				Expiry: offset(4 * time.Second),
   231  			},
   232  			{"ns", "model", "well-after"}: {
   233  				Holder: "them",
   234  				Expiry: offset(62 * time.Second),
   235  			},
   236  		},
   237  	)
   238  }
   239  
   240  func (s *fsmSuite) TestPinUnpin(c *gc.C) {
   241  	c.Assert(s.apply(c, raftlease.Command{
   242  		Version:   1,
   243  		Operation: raftlease.OperationSetTime,
   244  		OldTime:   zero,
   245  		NewTime:   offset(2 * time.Second),
   246  	}).Error(), jc.ErrorIsNil)
   247  	c.Assert(s.apply(c, raftlease.Command{
   248  		Version:   1,
   249  		Operation: raftlease.OperationClaim,
   250  		Namespace: "ns",
   251  		ModelUUID: "model",
   252  		Lease:     "lease",
   253  		Holder:    "me",
   254  		Duration:  time.Second,
   255  	}).Error(), jc.ErrorIsNil)
   256  
   257  	machine := names.NewMachineTag("0").String()
   258  	c.Assert(s.apply(c, raftlease.Command{
   259  		Version:   1,
   260  		Operation: raftlease.OperationPin,
   261  		Namespace: "ns",
   262  		ModelUUID: "model",
   263  		Lease:     "lease",
   264  		PinEntity: machine,
   265  	}).Error(), jc.ErrorIsNil)
   266  
   267  	// Pinned lease does not expire.
   268  	resp := s.apply(c, raftlease.Command{
   269  		Version:   1,
   270  		Operation: raftlease.OperationSetTime,
   271  		OldTime:   offset(2 * time.Second),
   272  		NewTime:   offset(4 * time.Second),
   273  	})
   274  	c.Assert(resp.Error(), jc.ErrorIsNil)
   275  	assertExpired(c, resp)
   276  
   277  	exp := map[lease.Key][]string{{Namespace: "ns", ModelUUID: "model", Lease: "lease"}: {machine}}
   278  	c.Assert(s.fsm.Pinned(), gc.DeepEquals, exp)
   279  
   280  	c.Assert(s.apply(c, raftlease.Command{
   281  		Version:   1,
   282  		Operation: raftlease.OperationUnpin,
   283  		Namespace: "ns",
   284  		ModelUUID: "model",
   285  		Lease:     "lease",
   286  		PinEntity: machine,
   287  	}).Error(), jc.ErrorIsNil)
   288  
   289  	// Unpinned lease expires when time advances.
   290  	resp = s.apply(c, raftlease.Command{
   291  		Version:   1,
   292  		Operation: raftlease.OperationSetTime,
   293  		OldTime:   offset(4 * time.Second),
   294  		NewTime:   offset(6 * time.Second),
   295  	})
   296  	c.Assert(resp.Error(), jc.ErrorIsNil)
   297  	assertExpired(c, resp, lease.Key{Namespace: "ns", ModelUUID: "model", Lease: "lease"})
   298  
   299  	c.Assert(s.fsm.Pinned(), gc.DeepEquals, map[lease.Key][]string{})
   300  }
   301  
   302  func (s *fsmSuite) TestPinUnpinMultipleHoldersNoExpiry(c *gc.C) {
   303  	c.Assert(s.apply(c, raftlease.Command{
   304  		Version:   1,
   305  		Operation: raftlease.OperationSetTime,
   306  		OldTime:   zero,
   307  		NewTime:   offset(2 * time.Second),
   308  	}).Error(), jc.ErrorIsNil)
   309  	c.Assert(s.apply(c, raftlease.Command{
   310  		Version:   1,
   311  		Operation: raftlease.OperationClaim,
   312  		Namespace: "ns",
   313  		ModelUUID: "model",
   314  		Lease:     "lease",
   315  		Holder:    "me",
   316  		Duration:  time.Second,
   317  	}).Error(), jc.ErrorIsNil)
   318  
   319  	// Two different entities pin the same lease.
   320  	m0 := names.NewMachineTag("0").String()
   321  	c.Assert(s.apply(c, raftlease.Command{
   322  		Version:   1,
   323  		Operation: raftlease.OperationPin,
   324  		Namespace: "ns",
   325  		ModelUUID: "model",
   326  		Lease:     "lease",
   327  		PinEntity: m0,
   328  	}).Error(), jc.ErrorIsNil)
   329  
   330  	m1 := names.NewMachineTag("1").String()
   331  	c.Assert(s.apply(c, raftlease.Command{
   332  		Version:   1,
   333  		Operation: raftlease.OperationPin,
   334  		Namespace: "ns",
   335  		ModelUUID: "model",
   336  		Lease:     "lease",
   337  		PinEntity: m1,
   338  	}).Error(), jc.ErrorIsNil)
   339  
   340  	exp := map[lease.Key][]string{{Namespace: "ns", ModelUUID: "model", Lease: "lease"}: {m0, m1}}
   341  	c.Assert(s.fsm.Pinned(), gc.DeepEquals, exp)
   342  
   343  	// One entity releases.
   344  	c.Assert(s.apply(c, raftlease.Command{
   345  		Version:   1,
   346  		Operation: raftlease.OperationUnpin,
   347  		Namespace: "ns",
   348  		ModelUUID: "model",
   349  		Lease:     "lease",
   350  		PinEntity: m0,
   351  	}).Error(), jc.ErrorIsNil)
   352  
   353  	exp = map[lease.Key][]string{{Namespace: "ns", ModelUUID: "model", Lease: "lease"}: {m1}}
   354  	c.Assert(s.fsm.Pinned(), gc.DeepEquals, exp)
   355  
   356  	// Lease does not expire, as there is still one pin.
   357  	resp := s.apply(c, raftlease.Command{
   358  		Version:   1,
   359  		Operation: raftlease.OperationSetTime,
   360  		OldTime:   offset(2 * time.Second),
   361  		NewTime:   offset(4 * time.Second),
   362  	})
   363  	c.Assert(resp.Error(), jc.ErrorIsNil)
   364  	assertExpired(c, resp)
   365  }
   366  
   367  func (s *fsmSuite) TestLeases(c *gc.C) {
   368  	c.Assert(s.apply(c, raftlease.Command{
   369  		Version:   1,
   370  		Operation: raftlease.OperationClaim,
   371  		Namespace: "ns",
   372  		ModelUUID: "model",
   373  		Lease:     "lease",
   374  		Holder:    "me",
   375  		Duration:  time.Second,
   376  	}).Error(), jc.ErrorIsNil)
   377  	c.Assert(s.apply(c, raftlease.Command{
   378  		Version:   1,
   379  		Operation: raftlease.OperationClaim,
   380  		Namespace: "ns2",
   381  		ModelUUID: "model2",
   382  		Lease:     "lease",
   383  		Holder:    "you",
   384  		Duration:  4 * time.Second,
   385  	}).Error(), jc.ErrorIsNil)
   386  
   387  	c.Assert(s.fsm.Leases(timeDelegate(zero)), gc.DeepEquals,
   388  		map[lease.Key]lease.Info{
   389  			{"ns", "model", "lease"}: {
   390  				Holder: "me",
   391  				Expiry: offset(time.Second),
   392  			},
   393  			{"ns2", "model2", "lease"}: {
   394  				Holder: "you",
   395  				Expiry: offset(4 * time.Second),
   396  			},
   397  		},
   398  	)
   399  }
   400  
   401  func (s *fsmSuite) TestLeasesFilter(c *gc.C) {
   402  	c.Assert(s.apply(c, raftlease.Command{
   403  		Version:   1,
   404  		Operation: raftlease.OperationClaim,
   405  		Namespace: "ns",
   406  		ModelUUID: "model",
   407  		Lease:     "lease",
   408  		Holder:    "me",
   409  		Duration:  time.Second,
   410  	}).Error(), jc.ErrorIsNil)
   411  	c.Assert(s.apply(c, raftlease.Command{
   412  		Version:   1,
   413  		Operation: raftlease.OperationClaim,
   414  		Namespace: "ns2",
   415  		ModelUUID: "model2",
   416  		Lease:     "lease",
   417  		Holder:    "you",
   418  		Duration:  4 * time.Second,
   419  	}).Error(), jc.ErrorIsNil)
   420  
   421  	c.Assert(
   422  		s.fsm.Leases(timeDelegate(zero), lease.Key{Namespace: "ns", ModelUUID: "model", Lease: "lease"}),
   423  		gc.DeepEquals,
   424  		map[lease.Key]lease.Info{
   425  			{"ns", "model", "lease"}: {
   426  				Holder: "me",
   427  				Expiry: offset(time.Second),
   428  			},
   429  		},
   430  	)
   431  }
   432  
   433  func (s *fsmSuite) TestLeasesPinnedFutureExpiry(c *gc.C) {
   434  	c.Assert(s.apply(c, raftlease.Command{
   435  		Version:   1,
   436  		Operation: raftlease.OperationClaim,
   437  		Namespace: "ns",
   438  		ModelUUID: "model",
   439  		Lease:     "lease",
   440  		Holder:    "me",
   441  		Duration:  time.Second,
   442  	}).Error(), jc.ErrorIsNil)
   443  	c.Assert(s.apply(c, raftlease.Command{
   444  		Version:   1,
   445  		Operation: raftlease.OperationPin,
   446  		Namespace: "ns",
   447  		ModelUUID: "model",
   448  		Lease:     "lease",
   449  		PinEntity: names.NewMachineTag("0").String(),
   450  	}).Error(), jc.ErrorIsNil)
   451  
   452  	// Even though the lease duration is only one second,
   453  	// expiry should be represented as 30 seconds in the future.
   454  	c.Assert(s.fsm.Leases(timeDelegate(zero)), gc.DeepEquals,
   455  		map[lease.Key]lease.Info{
   456  			{"ns", "model", "lease"}: {
   457  				Holder: "me",
   458  				Expiry: offset(30 * time.Second),
   459  			},
   460  		},
   461  	)
   462  }
   463  
   464  func (s *fsmSuite) TestLeasesDifferentTime(c *gc.C) {
   465  	c.Assert(s.apply(c, raftlease.Command{
   466  		Version:   1,
   467  		Operation: raftlease.OperationClaim,
   468  		Namespace: "ns",
   469  		ModelUUID: "model",
   470  		Lease:     "lease",
   471  		Holder:    "me",
   472  		Duration:  5 * time.Second,
   473  	}).Error(), jc.ErrorIsNil)
   474  	c.Assert(s.apply(c, raftlease.Command{
   475  		Version:   1,
   476  		Operation: raftlease.OperationClaim,
   477  		Namespace: "ns2",
   478  		ModelUUID: "model2",
   479  		Lease:     "lease",
   480  		Holder:    "you",
   481  		Duration:  7 * time.Second,
   482  	}).Error(), jc.ErrorIsNil)
   483  	c.Assert(s.apply(c, raftlease.Command{
   484  		Version:   1,
   485  		Operation: raftlease.OperationSetTime,
   486  		OldTime:   zero,
   487  		NewTime:   zero.Add(2 * time.Second),
   488  	}).Error(), jc.ErrorIsNil)
   489  
   490  	// Global time is 00:00:02, but we think it's only 00:00:01
   491  	c.Assert(s.fsm.Leases(timeDelegate(offset(time.Second))), gc.DeepEquals,
   492  		map[lease.Key]lease.Info{
   493  			{"ns", "model", "lease"}: {
   494  				Holder: "me",
   495  				Expiry: offset(4 * time.Second),
   496  			},
   497  			{"ns2", "model2", "lease"}: {
   498  				Holder: "you",
   499  				Expiry: offset(6 * time.Second),
   500  			},
   501  		},
   502  	)
   503  
   504  	// Global time is 00:00:02, but we think it's 00:00:04!
   505  	c.Assert(s.fsm.Leases(timeDelegate(offset(4*time.Second))), gc.DeepEquals,
   506  		map[lease.Key]lease.Info{
   507  			{"ns", "model", "lease"}: {
   508  				Holder: "me",
   509  				Expiry: offset(7 * time.Second),
   510  			},
   511  			{"ns2", "model2", "lease"}: {
   512  				Holder: "you",
   513  				Expiry: offset(9 * time.Second),
   514  			},
   515  		},
   516  	)
   517  }
   518  
   519  func (s *fsmSuite) TestApplyInvalidCommand(c *gc.C) {
   520  	c.Assert(s.apply(c, raftlease.Command{
   521  		Version:   300,
   522  		Operation: raftlease.OperationSetTime,
   523  		OldTime:   zero,
   524  		NewTime:   zero.Add(2 * time.Second),
   525  	}).Error(), jc.Satisfies, errors.IsNotValid)
   526  	c.Assert(s.apply(c, raftlease.Command{
   527  		Version:   1,
   528  		Operation: "libera-me",
   529  	}).Error(), jc.Satisfies, errors.IsNotValid)
   530  }
   531  
   532  func (s *fsmSuite) TestSnapshot(c *gc.C) {
   533  	c.Assert(s.apply(c, raftlease.Command{
   534  		Version:   1,
   535  		Operation: raftlease.OperationClaim,
   536  		Namespace: "ns",
   537  		ModelUUID: "model",
   538  		Lease:     "lease",
   539  		Holder:    "me",
   540  		Duration:  3 * time.Second,
   541  	}).Error(), jc.ErrorIsNil)
   542  	c.Assert(s.apply(c, raftlease.Command{
   543  		Version:   1,
   544  		Operation: raftlease.OperationSetTime,
   545  		OldTime:   zero,
   546  		NewTime:   zero.Add(2 * time.Second),
   547  	}).Error(), jc.ErrorIsNil)
   548  	c.Assert(s.apply(c, raftlease.Command{
   549  		Version:   1,
   550  		Operation: raftlease.OperationClaim,
   551  		Namespace: "ns2",
   552  		ModelUUID: "model2",
   553  		Lease:     "lease",
   554  		Holder:    "you",
   555  		Duration:  4 * time.Second,
   556  	}).Error(), jc.ErrorIsNil)
   557  
   558  	machineTag := names.NewMachineTag("0")
   559  	c.Assert(s.apply(c, raftlease.Command{
   560  		Version:   1,
   561  		Operation: raftlease.OperationPin,
   562  		Namespace: "ns",
   563  		ModelUUID: "model",
   564  		Lease:     "lease",
   565  		PinEntity: machineTag.String(),
   566  	}).Error(), jc.ErrorIsNil)
   567  
   568  	snapshot, err := s.fsm.Snapshot()
   569  	c.Assert(err, jc.ErrorIsNil)
   570  	c.Assert(snapshot, gc.DeepEquals, &raftlease.Snapshot{
   571  		Version: 1,
   572  		Entries: map[raftlease.SnapshotKey]raftlease.SnapshotEntry{
   573  			{"ns", "model", "lease"}: {
   574  				Holder:   "me",
   575  				Start:    zero,
   576  				Duration: 3 * time.Second,
   577  			},
   578  			{"ns2", "model2", "lease"}: {
   579  				Holder:   "you",
   580  				Start:    zero.Add(2 * time.Second),
   581  				Duration: 4 * time.Second,
   582  			},
   583  		},
   584  		GlobalTime: zero.Add(2 * time.Second),
   585  		Pinned: map[raftlease.SnapshotKey][]string{
   586  			{"ns", "model", "lease"}: {machineTag.String()},
   587  		},
   588  	})
   589  }
   590  
   591  func (s *fsmSuite) TestRestore(c *gc.C) {
   592  	c.Assert(s.apply(c, raftlease.Command{
   593  		Version:   1,
   594  		Operation: raftlease.OperationClaim,
   595  		Namespace: "ns",
   596  		ModelUUID: "model",
   597  		Lease:     "lease",
   598  		Holder:    "me",
   599  		Duration:  time.Second,
   600  	}).Error(), jc.ErrorIsNil)
   601  
   602  	// Restoring overwrites the state.
   603  	reader := closer{Reader: bytes.NewBuffer([]byte(snapshotYaml))}
   604  	err := s.fsm.Restore(&reader)
   605  	c.Assert(err, jc.ErrorIsNil)
   606  
   607  	expected := &raftlease.Snapshot{
   608  		Version: 1,
   609  		Entries: map[raftlease.SnapshotKey]raftlease.SnapshotEntry{
   610  			{"ns", "model", "lease"}: {
   611  				Holder:   "me",
   612  				Start:    zero,
   613  				Duration: 5 * time.Second,
   614  			},
   615  			{"ns2", "model2", "lease"}: {
   616  				Holder:   "you",
   617  				Start:    zero.Add(2 * time.Second),
   618  				Duration: 10 * time.Second,
   619  			},
   620  		},
   621  		GlobalTime: zero.Add(3 * time.Second),
   622  		Pinned: map[raftlease.SnapshotKey][]string{
   623  			{"ns", "model", "lease"}: {names.NewMachineTag("0").String()},
   624  		},
   625  	}
   626  
   627  	actual, err := s.fsm.Snapshot()
   628  	c.Assert(err, jc.ErrorIsNil)
   629  	c.Assert(actual, gc.DeepEquals, expected)
   630  }
   631  
   632  func (s *fsmSuite) TestSnapshotPersist(c *gc.C) {
   633  	snapshot := &raftlease.Snapshot{
   634  		Version: 1,
   635  		Entries: map[raftlease.SnapshotKey]raftlease.SnapshotEntry{
   636  			{"ns", "model", "lease"}: {
   637  				Holder:   "me",
   638  				Start:    zero,
   639  				Duration: time.Second,
   640  			},
   641  			{"ns2", "model2", "lease"}: {
   642  				Holder:   "you",
   643  				Start:    zero.Add(2 * time.Second),
   644  				Duration: 4 * time.Second,
   645  			},
   646  		},
   647  		Pinned: map[raftlease.SnapshotKey][]string{
   648  			{"ns", "model", "lease"}: {names.NewMachineTag("0").String()},
   649  		},
   650  		GlobalTime: zero.Add(2 * time.Second),
   651  	}
   652  	var buffer bytes.Buffer
   653  	sink := fakeSnapshotSink{Writer: &buffer}
   654  	err := snapshot.Persist(&sink)
   655  	c.Assert(err, gc.ErrorMatches, "quam olim abrahe")
   656  	c.Assert(sink.cancelled, gc.Equals, true)
   657  
   658  	// Don't compare buffer bytes in output yaml directly, it's
   659  	// dependent on map ordering.
   660  	decoder := yaml.NewDecoder(&buffer)
   661  	var loaded raftlease.Snapshot
   662  	err = decoder.Decode(&loaded)
   663  	c.Assert(err, jc.ErrorIsNil)
   664  	c.Assert(&loaded, gc.DeepEquals, snapshot)
   665  }
   666  
   667  func (s *fsmSuite) TestCommandValidationClaim(c *gc.C) {
   668  	command := raftlease.Command{
   669  		Version:   1,
   670  		Operation: raftlease.OperationClaim,
   671  		Namespace: "namespace",
   672  		ModelUUID: "model",
   673  		Lease:     "lease",
   674  		Holder:    "you",
   675  		Duration:  time.Second,
   676  	}
   677  	c.Assert(command.Validate(), gc.Equals, nil)
   678  	command.OldTime = time.Now()
   679  	c.Assert(command.Validate(), gc.ErrorMatches, "claim with old time not valid")
   680  	command.OldTime = time.Time{}
   681  	command.Lease = ""
   682  	c.Assert(command.Validate(), gc.ErrorMatches, "claim with empty lease not valid")
   683  }
   684  
   685  func (s *fsmSuite) TestCommandValidationExtend(c *gc.C) {
   686  	command := raftlease.Command{
   687  		Version:   1,
   688  		Operation: raftlease.OperationExtend,
   689  		Namespace: "namespace",
   690  		ModelUUID: "model",
   691  		Lease:     "lease",
   692  		Holder:    "you",
   693  		Duration:  time.Second,
   694  	}
   695  	c.Assert(command.Validate(), gc.Equals, nil)
   696  	command.NewTime = time.Now()
   697  	c.Assert(command.Validate(), gc.ErrorMatches, "extend with new time not valid")
   698  	command.OldTime = time.Time{}
   699  	command.Namespace = ""
   700  	c.Assert(command.Validate(), gc.ErrorMatches, "extend with empty namespace not valid")
   701  }
   702  
   703  func (s *fsmSuite) TestCommandValidationSetTime(c *gc.C) {
   704  	command := raftlease.Command{
   705  		Version:   1,
   706  		Operation: raftlease.OperationSetTime,
   707  		OldTime:   time.Now(),
   708  		NewTime:   time.Now(),
   709  	}
   710  	c.Assert(command.Validate(), gc.Equals, nil)
   711  	command.Duration = time.Minute
   712  	c.Assert(command.Validate(), gc.ErrorMatches, "setTime with duration not valid")
   713  	command.Duration = 0
   714  	command.NewTime = time.Time{}
   715  	c.Assert(command.Validate(), gc.ErrorMatches, "setTime with zero new time not valid")
   716  }
   717  
   718  func (s *fsmSuite) TestCommandValidationPin(c *gc.C) {
   719  	command := raftlease.Command{
   720  		Version:   1,
   721  		Operation: raftlease.OperationPin,
   722  		Namespace: "namespace",
   723  		ModelUUID: "model",
   724  		Lease:     "lease",
   725  		PinEntity: names.NewMachineTag("0").String(),
   726  	}
   727  	c.Assert(command.Validate(), gc.Equals, nil)
   728  	command.NewTime = time.Now()
   729  	c.Assert(command.Validate(), gc.ErrorMatches, "pin with new time not valid")
   730  	command.NewTime = time.Time{}
   731  	command.Namespace = ""
   732  	c.Assert(command.Validate(), gc.ErrorMatches, "pin with empty namespace not valid")
   733  	command.Namespace = "namespace"
   734  	command.Duration = time.Minute
   735  	c.Assert(command.Validate(), gc.ErrorMatches, "pin with duration not valid")
   736  	command.Duration = 0
   737  	command.PinEntity = ""
   738  	c.Assert(command.Validate(), gc.ErrorMatches, "pin with empty pin entity not valid")
   739  }
   740  
   741  func assertClaimed(c *gc.C, resp raftlease.FSMResponse, key lease.Key, holder string) {
   742  	var target fakeTarget
   743  	resp.Notify(&target)
   744  	c.Assert(target.Calls(), gc.HasLen, 1)
   745  	target.CheckCall(c, 0, "Claimed", key, holder)
   746  }
   747  
   748  func assertExpired(c *gc.C, resp raftlease.FSMResponse, keys ...lease.Key) {
   749  	// Don't assume the keys are expired in the order given.
   750  	keySet := make(map[lease.Key]bool, len(keys))
   751  	for _, key := range keys {
   752  		keySet[key] = true
   753  	}
   754  	var target fakeTarget
   755  	resp.Notify(&target)
   756  	c.Assert(target.Calls(), gc.HasLen, len(keys))
   757  	for _, call := range target.Calls() {
   758  		c.Assert(call.FuncName, gc.Equals, "Expired")
   759  		c.Assert(call.Args, gc.HasLen, 1)
   760  		key, ok := call.Args[0].(lease.Key)
   761  		c.Assert(ok, gc.Equals, true)
   762  		_, found := keySet[key]
   763  		c.Assert(found, gc.Equals, true)
   764  		delete(keySet, key)
   765  	}
   766  }
   767  
   768  func assertNoNotifications(c *gc.C, resp raftlease.FSMResponse) {
   769  	var target fakeTarget
   770  	resp.Notify(&target)
   771  	c.Assert(target.Calls(), gc.HasLen, 0)
   772  }
   773  
   774  type fakeTarget struct {
   775  	testing.Stub
   776  }
   777  
   778  func (t *fakeTarget) Claimed(key lease.Key, holder string) {
   779  	t.AddCall("Claimed", key, holder)
   780  }
   781  
   782  func (t *fakeTarget) Expired(key lease.Key) {
   783  	t.AddCall("Expired", key)
   784  }
   785  
   786  type fakeSnapshotSink struct {
   787  	io.Writer
   788  	cancelled bool
   789  }
   790  
   791  func (s *fakeSnapshotSink) ID() string {
   792  	return "fakeSink"
   793  }
   794  
   795  func (s *fakeSnapshotSink) Cancel() error {
   796  	s.cancelled = true
   797  	return nil
   798  }
   799  
   800  func (s *fakeSnapshotSink) Close() error {
   801  	return errors.Errorf("quam olim abrahe")
   802  }
   803  
   804  type closer struct {
   805  	io.Reader
   806  	closed bool
   807  }
   808  
   809  func (c *closer) Close() error {
   810  	c.closed = true
   811  	return nil
   812  }
   813  
   814  var snapshotYaml = `
   815  version: 1
   816  entries:
   817    ? namespace: ns
   818      model-uuid: model
   819      lease: lease
   820    : holder: me
   821      start: 0001-01-01T00:00:00Z
   822      duration: 5s
   823    ? namespace: ns2
   824      model-uuid: model2
   825      lease: lease
   826    : holder: you
   827      start: 0001-01-01T00:00:02Z
   828      duration: 10s
   829  global-time: 0001-01-01T00:00:03Z
   830  pinned:
   831    ? namespace: ns
   832      model-uuid: model
   833      lease: lease
   834    : [machine-0]
   835  `[1:]
   836  
   837  // timeDelegate is a convenience wrapper for turning a time into a delegate
   838  // returning the input time.
   839  // It is intended for use with static time values in testing, so we don't care
   840  // that it does not do run-time evaluation.
   841  func timeDelegate(t time.Time) func() time.Time {
   842  	return func() time.Time { return t }
   843  }