github.imxd.top/hashicorp/consul@v1.4.5/agent/consul/state/graveyard_test.go (about) 1 package state 2 3 import ( 4 "reflect" 5 "testing" 6 "time" 7 ) 8 9 func TestGraveyard_Lifecycle(t *testing.T) { 10 g := NewGraveyard(nil) 11 12 // Make a donor state store to steal its database, all prepared for 13 // tombstones. 14 s := testStateStore(t) 15 16 // Create some tombstones. 17 func() { 18 tx := s.db.Txn(true) 19 defer tx.Abort() 20 21 if err := g.InsertTxn(tx, "foo/in/the/house", 2); err != nil { 22 t.Fatalf("err: %s", err) 23 } 24 if err := g.InsertTxn(tx, "foo/bar/baz", 5); err != nil { 25 t.Fatalf("err: %s", err) 26 } 27 if err := g.InsertTxn(tx, "foo/bar/zoo", 8); err != nil { 28 t.Fatalf("err: %s", err) 29 } 30 if err := g.InsertTxn(tx, "some/other/path", 9); err != nil { 31 t.Fatalf("err: %s", err) 32 } 33 tx.Commit() 34 }() 35 36 // Check some prefixes. 37 func() { 38 tx := s.db.Txn(false) 39 defer tx.Abort() 40 41 if idx, err := g.GetMaxIndexTxn(tx, "foo"); idx != 8 || err != nil { 42 t.Fatalf("bad: %d (%s)", idx, err) 43 } 44 if idx, err := g.GetMaxIndexTxn(tx, "foo/in/the/house"); idx != 2 || err != nil { 45 t.Fatalf("bad: %d (%s)", idx, err) 46 } 47 if idx, err := g.GetMaxIndexTxn(tx, "foo/bar/baz"); idx != 5 || err != nil { 48 t.Fatalf("bad: %d (%s)", idx, err) 49 } 50 if idx, err := g.GetMaxIndexTxn(tx, "foo/bar/zoo"); idx != 8 || err != nil { 51 t.Fatalf("bad: %d (%s)", idx, err) 52 } 53 if idx, err := g.GetMaxIndexTxn(tx, "some/other/path"); idx != 9 || err != nil { 54 t.Fatalf("bad: %d (%s)", idx, err) 55 } 56 if idx, err := g.GetMaxIndexTxn(tx, ""); idx != 9 || err != nil { 57 t.Fatalf("bad: %d (%s)", idx, err) 58 } 59 if idx, err := g.GetMaxIndexTxn(tx, "nope"); idx != 0 || err != nil { 60 t.Fatalf("bad: %d (%s)", idx, err) 61 } 62 }() 63 64 // Reap some tombstones. 65 func() { 66 tx := s.db.Txn(true) 67 defer tx.Abort() 68 69 if err := g.ReapTxn(tx, 6); err != nil { 70 t.Fatalf("err: %s", err) 71 } 72 tx.Commit() 73 }() 74 75 // Check prefixes to see that the reap took effect at the right index. 76 func() { 77 tx := s.db.Txn(false) 78 defer tx.Abort() 79 80 if idx, err := g.GetMaxIndexTxn(tx, "foo"); idx != 8 || err != nil { 81 t.Fatalf("bad: %d (%s)", idx, err) 82 } 83 if idx, err := g.GetMaxIndexTxn(tx, "foo/in/the/house"); idx != 0 || err != nil { 84 t.Fatalf("bad: %d (%s)", idx, err) 85 } 86 if idx, err := g.GetMaxIndexTxn(tx, "foo/bar/baz"); idx != 0 || err != nil { 87 t.Fatalf("bad: %d (%s)", idx, err) 88 } 89 if idx, err := g.GetMaxIndexTxn(tx, "foo/bar/zoo"); idx != 8 || err != nil { 90 t.Fatalf("bad: %d (%s)", idx, err) 91 } 92 if idx, err := g.GetMaxIndexTxn(tx, "some/other/path"); idx != 9 || err != nil { 93 t.Fatalf("bad: %d (%s)", idx, err) 94 } 95 if idx, err := g.GetMaxIndexTxn(tx, ""); idx != 9 || err != nil { 96 t.Fatalf("bad: %d (%s)", idx, err) 97 } 98 if idx, err := g.GetMaxIndexTxn(tx, "nope"); idx != 0 || err != nil { 99 t.Fatalf("bad: %d (%s)", idx, err) 100 } 101 }() 102 } 103 104 func TestGraveyard_GC_Trigger(t *testing.T) { 105 // Set up a fast-expiring GC. 106 ttl, granularity := 100*time.Millisecond, 20*time.Millisecond 107 gc, err := NewTombstoneGC(ttl, granularity) 108 if err != nil { 109 t.Fatalf("err: %s", err) 110 } 111 112 // Make a new graveyard and assign the GC. 113 g := NewGraveyard(gc) 114 gc.SetEnabled(true) 115 116 // Make sure there's nothing already expiring. 117 if gc.PendingExpiration() { 118 t.Fatalf("should not have any expiring items") 119 } 120 121 // Create a tombstone but abort the transaction, this should not trigger 122 // GC. 123 s := testStateStore(t) 124 func() { 125 tx := s.db.Txn(true) 126 defer tx.Abort() 127 128 if err := g.InsertTxn(tx, "foo/in/the/house", 2); err != nil { 129 t.Fatalf("err: %s", err) 130 } 131 }() 132 133 // Make sure there's nothing already expiring. 134 if gc.PendingExpiration() { 135 t.Fatalf("should not have any expiring items") 136 } 137 138 // Now commit. 139 func() { 140 tx := s.db.Txn(true) 141 defer tx.Abort() 142 143 if err := g.InsertTxn(tx, "foo/in/the/house", 2); err != nil { 144 t.Fatalf("err: %s", err) 145 } 146 tx.Commit() 147 }() 148 149 // Make sure the GC got hinted. 150 if !gc.PendingExpiration() { 151 t.Fatalf("should have a pending expiration") 152 } 153 154 // Make sure the index looks good. 155 select { 156 case idx := <-gc.ExpireCh(): 157 if idx != 2 { 158 t.Fatalf("bad index: %d", idx) 159 } 160 case <-time.After(2 * ttl): 161 t.Fatalf("should have gotten an expire notice") 162 } 163 } 164 165 func TestGraveyard_Snapshot_Restore(t *testing.T) { 166 g := NewGraveyard(nil) 167 168 // Make a donor state store to steal its database, all prepared for 169 // tombstones. 170 s := testStateStore(t) 171 172 // Create some tombstones. 173 func() { 174 tx := s.db.Txn(true) 175 defer tx.Abort() 176 177 if err := g.InsertTxn(tx, "foo/in/the/house", 2); err != nil { 178 t.Fatalf("err: %s", err) 179 } 180 if err := g.InsertTxn(tx, "foo/bar/baz", 5); err != nil { 181 t.Fatalf("err: %s", err) 182 } 183 if err := g.InsertTxn(tx, "foo/bar/zoo", 8); err != nil { 184 t.Fatalf("err: %s", err) 185 } 186 if err := g.InsertTxn(tx, "some/other/path", 9); err != nil { 187 t.Fatalf("err: %s", err) 188 } 189 tx.Commit() 190 }() 191 192 // Verify the index was set correctly. 193 if idx := s.maxIndex("tombstones"); idx != 9 { 194 t.Fatalf("bad index: %d", idx) 195 } 196 197 // Dump them as if we are doing a snapshot. 198 dump := func() []*Tombstone { 199 tx := s.db.Txn(false) 200 defer tx.Abort() 201 202 iter, err := g.DumpTxn(tx) 203 if err != nil { 204 t.Fatalf("err: %s", err) 205 } 206 var dump []*Tombstone 207 for ti := iter.Next(); ti != nil; ti = iter.Next() { 208 dump = append(dump, ti.(*Tombstone)) 209 } 210 return dump 211 }() 212 213 // Verify the dump, which should be ordered by key. 214 expected := []*Tombstone{ 215 &Tombstone{Key: "foo/bar/baz", Index: 5}, 216 &Tombstone{Key: "foo/bar/zoo", Index: 8}, 217 &Tombstone{Key: "foo/in/the/house", Index: 2}, 218 &Tombstone{Key: "some/other/path", Index: 9}, 219 } 220 if !reflect.DeepEqual(dump, expected) { 221 t.Fatalf("bad: %v", dump) 222 } 223 224 // Make another state store and restore from the dump. 225 func() { 226 s := testStateStore(t) 227 func() { 228 tx := s.db.Txn(true) 229 defer tx.Abort() 230 231 for _, stone := range dump { 232 if err := g.RestoreTxn(tx, stone); err != nil { 233 t.Fatalf("err: %s", err) 234 } 235 } 236 tx.Commit() 237 }() 238 239 // Verify that the restore works. 240 if idx := s.maxIndex("tombstones"); idx != 9 { 241 t.Fatalf("bad index: %d", idx) 242 } 243 244 dump := func() []*Tombstone { 245 tx := s.db.Txn(false) 246 defer tx.Abort() 247 248 iter, err := g.DumpTxn(tx) 249 if err != nil { 250 t.Fatalf("err: %s", err) 251 } 252 var dump []*Tombstone 253 for ti := iter.Next(); ti != nil; ti = iter.Next() { 254 dump = append(dump, ti.(*Tombstone)) 255 } 256 return dump 257 }() 258 if !reflect.DeepEqual(dump, expected) { 259 t.Fatalf("bad: %v", dump) 260 } 261 }() 262 }