...

Source file src/go.etcd.io/etcd/server/v3/etcdserver/api/v2store/store_ttl_test.go

Documentation: go.etcd.io/etcd/server/v3/etcdserver/api/v2store

     1  // Copyright 2017 The etcd Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package v2store
    16  
    17  import (
    18  	"testing"
    19  	"time"
    20  
    21  	"go.etcd.io/etcd/client/pkg/v3/testutil"
    22  	"go.etcd.io/etcd/server/v3/etcdserver/api/v2error"
    23  
    24  	"github.com/jonboulle/clockwork"
    25  )
    26  
    27  // TestMinExpireTime to ensure that any TTL <= minExpireTime becomes Permanent
    28  func TestMinExpireTime(t *testing.T) {
    29  	s := newStore()
    30  	fc := clockwork.NewFakeClock()
    31  	s.clock = fc
    32  	// FakeClock starts at 0, so minExpireTime should be far in the future.. but just in case
    33  	testutil.AssertTrue(t, minExpireTime.After(fc.Now()), "minExpireTime should be ahead of FakeClock!")
    34  	s.Create("/foo", false, "Y", false, TTLOptionSet{ExpireTime: fc.Now().Add(3 * time.Second)})
    35  	fc.Advance(5 * time.Second)
    36  	// Ensure it hasn't expired
    37  	s.DeleteExpiredKeys(fc.Now())
    38  	var eidx uint64 = 1
    39  	e, err := s.Get("/foo", true, false)
    40  	testutil.AssertNil(t, err)
    41  	testutil.AssertEqual(t, e.EtcdIndex, eidx)
    42  	testutil.AssertEqual(t, e.Action, "get")
    43  	testutil.AssertEqual(t, e.Node.Key, "/foo")
    44  	testutil.AssertEqual(t, e.Node.TTL, int64(0))
    45  }
    46  
    47  // TestStoreGetDirectory to ensure that the store can recursively retrieve a directory listing.
    48  // Note that hidden files should not be returned.
    49  func TestStoreGetDirectory(t *testing.T) {
    50  	s := newStore()
    51  	fc := newFakeClock()
    52  	s.clock = fc
    53  	s.Create("/foo", true, "", false, TTLOptionSet{ExpireTime: Permanent})
    54  	s.Create("/foo/bar", false, "X", false, TTLOptionSet{ExpireTime: Permanent})
    55  	s.Create("/foo/_hidden", false, "*", false, TTLOptionSet{ExpireTime: Permanent})
    56  	s.Create("/foo/baz", true, "", false, TTLOptionSet{ExpireTime: Permanent})
    57  	s.Create("/foo/baz/bat", false, "Y", false, TTLOptionSet{ExpireTime: Permanent})
    58  	s.Create("/foo/baz/_hidden", false, "*", false, TTLOptionSet{ExpireTime: Permanent})
    59  	s.Create("/foo/baz/ttl", false, "Y", false, TTLOptionSet{ExpireTime: fc.Now().Add(time.Second * 3)})
    60  	var eidx uint64 = 7
    61  	e, err := s.Get("/foo", true, false)
    62  	testutil.AssertNil(t, err)
    63  	testutil.AssertEqual(t, e.EtcdIndex, eidx)
    64  	testutil.AssertEqual(t, e.Action, "get")
    65  	testutil.AssertEqual(t, e.Node.Key, "/foo")
    66  	testutil.AssertEqual(t, len(e.Node.Nodes), 2)
    67  	var bazNodes NodeExterns
    68  	for _, node := range e.Node.Nodes {
    69  		switch node.Key {
    70  		case "/foo/bar":
    71  			testutil.AssertEqual(t, *node.Value, "X")
    72  			testutil.AssertEqual(t, node.Dir, false)
    73  		case "/foo/baz":
    74  			testutil.AssertEqual(t, node.Dir, true)
    75  			testutil.AssertEqual(t, len(node.Nodes), 2)
    76  			bazNodes = node.Nodes
    77  		default:
    78  			t.Errorf("key = %s, not matched", node.Key)
    79  		}
    80  	}
    81  	for _, node := range bazNodes {
    82  		switch node.Key {
    83  		case "/foo/baz/bat":
    84  			testutil.AssertEqual(t, *node.Value, "Y")
    85  			testutil.AssertEqual(t, node.Dir, false)
    86  		case "/foo/baz/ttl":
    87  			testutil.AssertEqual(t, *node.Value, "Y")
    88  			testutil.AssertEqual(t, node.Dir, false)
    89  			testutil.AssertEqual(t, node.TTL, int64(3))
    90  		default:
    91  			t.Errorf("key = %s, not matched", node.Key)
    92  		}
    93  	}
    94  }
    95  
    96  // TestStoreUpdateValueTTL to ensure that the store can update the TTL on a value.
    97  func TestStoreUpdateValueTTL(t *testing.T) {
    98  	s := newStore()
    99  	fc := newFakeClock()
   100  	s.clock = fc
   101  
   102  	var eidx uint64 = 2
   103  	s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: Permanent})
   104  	_, err := s.Update("/foo", "baz", TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond)})
   105  	testutil.AssertNil(t, err)
   106  	e, _ := s.Get("/foo", false, false)
   107  	testutil.AssertEqual(t, *e.Node.Value, "baz")
   108  	testutil.AssertEqual(t, e.EtcdIndex, eidx)
   109  	fc.Advance(600 * time.Millisecond)
   110  	s.DeleteExpiredKeys(fc.Now())
   111  	e, err = s.Get("/foo", false, false)
   112  	testutil.AssertNil(t, e)
   113  	testutil.AssertEqual(t, err.(*v2error.Error).ErrorCode, v2error.EcodeKeyNotFound)
   114  }
   115  
   116  // TestStoreUpdateDirTTL to ensure that the store can update the TTL on a directory.
   117  func TestStoreUpdateDirTTL(t *testing.T) {
   118  	s := newStore()
   119  	fc := newFakeClock()
   120  	s.clock = fc
   121  
   122  	var eidx uint64 = 3
   123  	_, err := s.Create("/foo", true, "", false, TTLOptionSet{ExpireTime: Permanent})
   124  	testutil.AssertNil(t, err)
   125  	_, err = s.Create("/foo/bar", false, "baz", false, TTLOptionSet{ExpireTime: Permanent})
   126  	testutil.AssertNil(t, err)
   127  	e, err := s.Update("/foo/bar", "", TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond)})
   128  	testutil.AssertNil(t, err)
   129  	testutil.AssertEqual(t, e.Node.Dir, false)
   130  	testutil.AssertEqual(t, e.EtcdIndex, eidx)
   131  	e, _ = s.Get("/foo/bar", false, false)
   132  	testutil.AssertEqual(t, *e.Node.Value, "")
   133  	testutil.AssertEqual(t, e.EtcdIndex, eidx)
   134  
   135  	fc.Advance(600 * time.Millisecond)
   136  	s.DeleteExpiredKeys(fc.Now())
   137  	e, err = s.Get("/foo/bar", false, false)
   138  	testutil.AssertNil(t, e)
   139  	testutil.AssertEqual(t, err.(*v2error.Error).ErrorCode, v2error.EcodeKeyNotFound)
   140  }
   141  
   142  // TestStoreWatchExpire to ensure that the store can watch for key expiration.
   143  func TestStoreWatchExpire(t *testing.T) {
   144  	s := newStore()
   145  	fc := newFakeClock()
   146  	s.clock = fc
   147  
   148  	var eidx uint64 = 3
   149  	s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: fc.Now().Add(400 * time.Millisecond)})
   150  	s.Create("/foofoo", false, "barbarbar", false, TTLOptionSet{ExpireTime: fc.Now().Add(450 * time.Millisecond)})
   151  	s.Create("/foodir", true, "", false, TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond)})
   152  
   153  	w, _ := s.Watch("/", true, false, 0)
   154  	testutil.AssertEqual(t, w.StartIndex(), eidx)
   155  	c := w.EventChan()
   156  	e := nbselect(c)
   157  	testutil.AssertNil(t, e)
   158  	fc.Advance(600 * time.Millisecond)
   159  	s.DeleteExpiredKeys(fc.Now())
   160  	eidx = 4
   161  	e = nbselect(c)
   162  	testutil.AssertEqual(t, e.EtcdIndex, eidx)
   163  	testutil.AssertEqual(t, e.Action, "expire")
   164  	testutil.AssertEqual(t, e.Node.Key, "/foo")
   165  	w, _ = s.Watch("/", true, false, 5)
   166  	eidx = 6
   167  	testutil.AssertEqual(t, w.StartIndex(), eidx)
   168  	e = nbselect(w.EventChan())
   169  	testutil.AssertEqual(t, e.EtcdIndex, eidx)
   170  	testutil.AssertEqual(t, e.Action, "expire")
   171  	testutil.AssertEqual(t, e.Node.Key, "/foofoo")
   172  	w, _ = s.Watch("/", true, false, 6)
   173  	e = nbselect(w.EventChan())
   174  	testutil.AssertEqual(t, e.EtcdIndex, eidx)
   175  	testutil.AssertEqual(t, e.Action, "expire")
   176  	testutil.AssertEqual(t, e.Node.Key, "/foodir")
   177  	testutil.AssertEqual(t, e.Node.Dir, true)
   178  }
   179  
   180  // TestStoreWatchExpireRefresh to ensure that the store can watch for key expiration when refreshing.
   181  func TestStoreWatchExpireRefresh(t *testing.T) {
   182  	s := newStore()
   183  	fc := newFakeClock()
   184  	s.clock = fc
   185  
   186  	var eidx uint64 = 2
   187  	s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond), Refresh: true})
   188  	s.Create("/foofoo", false, "barbarbar", false, TTLOptionSet{ExpireTime: fc.Now().Add(1200 * time.Millisecond), Refresh: true})
   189  
   190  	// Make sure we set watch updates when Refresh is true for newly created keys
   191  	w, _ := s.Watch("/", true, false, 0)
   192  	testutil.AssertEqual(t, w.StartIndex(), eidx)
   193  	c := w.EventChan()
   194  	e := nbselect(c)
   195  	testutil.AssertNil(t, e)
   196  	fc.Advance(600 * time.Millisecond)
   197  	s.DeleteExpiredKeys(fc.Now())
   198  	eidx = 3
   199  	e = nbselect(c)
   200  	testutil.AssertEqual(t, e.EtcdIndex, eidx)
   201  	testutil.AssertEqual(t, e.Action, "expire")
   202  	testutil.AssertEqual(t, e.Node.Key, "/foo")
   203  
   204  	s.Update("/foofoo", "", TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond), Refresh: true})
   205  	w, _ = s.Watch("/", true, false, 4)
   206  	fc.Advance(700 * time.Millisecond)
   207  	s.DeleteExpiredKeys(fc.Now())
   208  	eidx = 5 // We should skip 4 because a TTL update should occur with no watch notification if set `TTLOptionSet.Refresh` to true
   209  	testutil.AssertEqual(t, w.StartIndex(), eidx-1)
   210  	e = nbselect(w.EventChan())
   211  	testutil.AssertEqual(t, e.EtcdIndex, eidx)
   212  	testutil.AssertEqual(t, e.Action, "expire")
   213  	testutil.AssertEqual(t, e.Node.Key, "/foofoo")
   214  }
   215  
   216  // TestStoreWatchExpireEmptyRefresh to ensure that the store can watch for key expiration when refreshing with an empty value.
   217  func TestStoreWatchExpireEmptyRefresh(t *testing.T) {
   218  	s := newStore()
   219  	fc := newFakeClock()
   220  	s.clock = fc
   221  
   222  	var eidx uint64
   223  	s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond), Refresh: true})
   224  	// Should be no-op
   225  	fc.Advance(200 * time.Millisecond)
   226  	s.DeleteExpiredKeys(fc.Now())
   227  
   228  	s.Update("/foo", "", TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond), Refresh: true})
   229  	w, _ := s.Watch("/", true, false, 2)
   230  	fc.Advance(700 * time.Millisecond)
   231  	s.DeleteExpiredKeys(fc.Now())
   232  	eidx = 3 // We should skip 2 because a TTL update should occur with no watch notification if set `TTLOptionSet.Refresh` to true
   233  	testutil.AssertEqual(t, w.StartIndex(), eidx-1)
   234  	e := nbselect(w.EventChan())
   235  	testutil.AssertEqual(t, e.EtcdIndex, eidx)
   236  	testutil.AssertEqual(t, e.Action, "expire")
   237  	testutil.AssertEqual(t, e.Node.Key, "/foo")
   238  	testutil.AssertEqual(t, *e.PrevNode.Value, "bar")
   239  }
   240  
   241  // TestStoreWatchNoRefresh to update TTL of a key (set TTLOptionSet.Refresh to false) and send notification
   242  func TestStoreWatchNoRefresh(t *testing.T) {
   243  	s := newStore()
   244  	fc := newFakeClock()
   245  	s.clock = fc
   246  
   247  	var eidx uint64
   248  	s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond), Refresh: true})
   249  	// Should be no-op
   250  	fc.Advance(200 * time.Millisecond)
   251  	s.DeleteExpiredKeys(fc.Now())
   252  
   253  	// Update key's TTL with setting `TTLOptionSet.Refresh` to false will cause an update event
   254  	s.Update("/foo", "", TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond), Refresh: false})
   255  	w, _ := s.Watch("/", true, false, 2)
   256  	fc.Advance(700 * time.Millisecond)
   257  	s.DeleteExpiredKeys(fc.Now())
   258  	eidx = 2
   259  	testutil.AssertEqual(t, w.StartIndex(), eidx)
   260  	e := nbselect(w.EventChan())
   261  	testutil.AssertEqual(t, e.EtcdIndex, eidx)
   262  	testutil.AssertEqual(t, e.Action, "update")
   263  	testutil.AssertEqual(t, e.Node.Key, "/foo")
   264  	testutil.AssertEqual(t, *e.PrevNode.Value, "bar")
   265  }
   266  
   267  // TestStoreRefresh to ensure that the store can update the TTL on a value with refresh.
   268  func TestStoreRefresh(t *testing.T) {
   269  	s := newStore()
   270  	fc := newFakeClock()
   271  	s.clock = fc
   272  
   273  	s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond)})
   274  	s.Create("/bar", true, "bar", false, TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond)})
   275  	s.Create("/bar/z", false, "bar", false, TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond)})
   276  	_, err := s.Update("/foo", "", TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond), Refresh: true})
   277  	testutil.AssertNil(t, err)
   278  
   279  	_, err = s.Set("/foo", false, "", TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond), Refresh: true})
   280  	testutil.AssertNil(t, err)
   281  
   282  	_, err = s.Update("/bar/z", "", TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond), Refresh: true})
   283  	testutil.AssertNil(t, err)
   284  
   285  	_, err = s.CompareAndSwap("/foo", "bar", 0, "", TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond), Refresh: true})
   286  	testutil.AssertNil(t, err)
   287  }
   288  
   289  // TestStoreRecoverWithExpiration to ensure that the store can recover from a previously saved state that includes an expiring key.
   290  func TestStoreRecoverWithExpiration(t *testing.T) {
   291  	s := newStore()
   292  	s.clock = newFakeClock()
   293  
   294  	fc := newFakeClock()
   295  
   296  	var eidx uint64 = 4
   297  	s.Create("/foo", true, "", false, TTLOptionSet{ExpireTime: Permanent})
   298  	s.Create("/foo/x", false, "bar", false, TTLOptionSet{ExpireTime: Permanent})
   299  	s.Create("/foo/y", false, "baz", false, TTLOptionSet{ExpireTime: fc.Now().Add(5 * time.Millisecond)})
   300  	b, err := s.Save()
   301  	testutil.AssertNil(t, err)
   302  
   303  	time.Sleep(10 * time.Millisecond)
   304  
   305  	s2 := newStore()
   306  	s2.clock = fc
   307  
   308  	s2.Recovery(b)
   309  
   310  	fc.Advance(600 * time.Millisecond)
   311  	s.DeleteExpiredKeys(fc.Now())
   312  
   313  	e, err := s.Get("/foo/x", false, false)
   314  	testutil.AssertNil(t, err)
   315  	testutil.AssertEqual(t, e.EtcdIndex, eidx)
   316  	testutil.AssertEqual(t, *e.Node.Value, "bar")
   317  
   318  	e, err = s.Get("/foo/y", false, false)
   319  	testutil.AssertNotNil(t, err)
   320  	testutil.AssertNil(t, e)
   321  }
   322  
   323  // TestStoreWatchExpireWithHiddenKey to ensure that the store doesn't see expirations of hidden keys.
   324  func TestStoreWatchExpireWithHiddenKey(t *testing.T) {
   325  	s := newStore()
   326  	fc := newFakeClock()
   327  	s.clock = fc
   328  
   329  	s.Create("/_foo", false, "bar", false, TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond)})
   330  	s.Create("/foofoo", false, "barbarbar", false, TTLOptionSet{ExpireTime: fc.Now().Add(1000 * time.Millisecond)})
   331  
   332  	w, _ := s.Watch("/", true, false, 0)
   333  	c := w.EventChan()
   334  	e := nbselect(c)
   335  	testutil.AssertNil(t, e)
   336  	fc.Advance(600 * time.Millisecond)
   337  	s.DeleteExpiredKeys(fc.Now())
   338  	e = nbselect(c)
   339  	testutil.AssertNil(t, e)
   340  	fc.Advance(600 * time.Millisecond)
   341  	s.DeleteExpiredKeys(fc.Now())
   342  	e = nbselect(c)
   343  	testutil.AssertEqual(t, e.Action, "expire")
   344  	testutil.AssertEqual(t, e.Node.Key, "/foofoo")
   345  }
   346  
   347  // newFakeClock creates a new FakeClock that has been advanced to at least minExpireTime
   348  func newFakeClock() clockwork.FakeClock {
   349  	fc := clockwork.NewFakeClock()
   350  	for minExpireTime.After(fc.Now()) {
   351  		fc.Advance((0x1 << 62) * time.Nanosecond)
   352  	}
   353  	return fc
   354  }
   355  
   356  // Performs a non-blocking select on an event channel.
   357  func nbselect(c <-chan *Event) *Event {
   358  	select {
   359  	case e := <-c:
   360  		return e
   361  	default:
   362  		return nil
   363  	}
   364  }
   365  

View as plain text