github.com/sl1pm4t/consul@v1.4.5-0.20190325224627-74c31c540f9c/agent/consul/state/state_store_test.go (about) 1 package state 2 3 import ( 4 crand "crypto/rand" 5 "fmt" 6 "testing" 7 "time" 8 9 "github.com/hashicorp/consul/agent/structs" 10 "github.com/hashicorp/consul/types" 11 "github.com/hashicorp/go-memdb" 12 "github.com/stretchr/testify/require" 13 ) 14 15 func testUUID() string { 16 buf := make([]byte, 16) 17 if _, err := crand.Read(buf); err != nil { 18 panic(fmt.Errorf("failed to read random bytes: %v", err)) 19 } 20 21 return fmt.Sprintf("%08x-%04x-%04x-%04x-%12x", 22 buf[0:4], 23 buf[4:6], 24 buf[6:8], 25 buf[8:10], 26 buf[10:16]) 27 } 28 29 func testStateStore(t *testing.T) *Store { 30 s, err := NewStateStore(nil) 31 if err != nil { 32 t.Fatalf("err: %s", err) 33 } 34 if s == nil { 35 t.Fatalf("missing state store") 36 } 37 return s 38 } 39 40 func testRegisterNode(t *testing.T, s *Store, idx uint64, nodeID string) { 41 testRegisterNodeWithMeta(t, s, idx, nodeID, nil) 42 } 43 44 // testRegisterNodeWithChange registers a node and ensures it gets different from previous registration 45 func testRegisterNodeWithChange(t *testing.T, s *Store, idx uint64, nodeID string) { 46 testRegisterNodeWithMeta(t, s, idx, nodeID, map[string]string{ 47 "version": string(idx), 48 }) 49 } 50 51 func testRegisterNodeWithMeta(t *testing.T, s *Store, idx uint64, nodeID string, meta map[string]string) { 52 node := &structs.Node{Node: nodeID, Meta: meta} 53 if err := s.EnsureNode(idx, node); err != nil { 54 t.Fatalf("err: %s", err) 55 } 56 57 tx := s.db.Txn(false) 58 defer tx.Abort() 59 n, err := tx.First("nodes", "id", nodeID) 60 if err != nil { 61 t.Fatalf("err: %s", err) 62 } 63 if result, ok := n.(*structs.Node); !ok || result.Node != nodeID { 64 t.Fatalf("bad node: %#v", result) 65 } 66 } 67 68 // testRegisterServiceWithChange registers a service and allow ensuring the consul index is updated 69 // even if service already exists if using `modifyAccordingIndex`. 70 // This is done by setting the transaction ID in "version" meta so service will be updated if it already exists 71 func testRegisterServiceWithChange(t *testing.T, s *Store, idx uint64, nodeID, serviceID string, modifyAccordingIndex bool) { 72 meta := make(map[string]string) 73 if modifyAccordingIndex { 74 meta["version"] = string(idx) 75 } 76 svc := &structs.NodeService{ 77 ID: serviceID, 78 Service: serviceID, 79 Address: "1.1.1.1", 80 Port: 1111, 81 Meta: meta, 82 } 83 if err := s.EnsureService(idx, nodeID, svc); err != nil { 84 t.Fatalf("err: %s", err) 85 } 86 87 tx := s.db.Txn(false) 88 defer tx.Abort() 89 service, err := tx.First("services", "id", nodeID, serviceID) 90 if err != nil { 91 t.Fatalf("err: %s", err) 92 } 93 if result, ok := service.(*structs.ServiceNode); !ok || 94 result.Node != nodeID || 95 result.ServiceID != serviceID { 96 t.Fatalf("bad service: %#v", result) 97 } 98 } 99 100 // testRegisterService register a service with given transaction idx 101 // If the service already exists, transaction number might not be increased 102 // Use `testRegisterServiceWithChange()` if you want perform a registration that 103 // ensures the transaction is updated by setting idx in Meta of Service 104 func testRegisterService(t *testing.T, s *Store, idx uint64, nodeID, serviceID string) { 105 testRegisterServiceWithChange(t, s, idx, nodeID, serviceID, false) 106 } 107 108 func testRegisterCheck(t *testing.T, s *Store, idx uint64, 109 nodeID string, serviceID string, checkID types.CheckID, state string) { 110 chk := &structs.HealthCheck{ 111 Node: nodeID, 112 CheckID: checkID, 113 ServiceID: serviceID, 114 Status: state, 115 } 116 if err := s.EnsureCheck(idx, chk); err != nil { 117 t.Fatalf("err: %s", err) 118 } 119 120 tx := s.db.Txn(false) 121 defer tx.Abort() 122 c, err := tx.First("checks", "id", nodeID, string(checkID)) 123 if err != nil { 124 t.Fatalf("err: %s", err) 125 } 126 if result, ok := c.(*structs.HealthCheck); !ok || 127 result.Node != nodeID || 128 result.ServiceID != serviceID || 129 result.CheckID != checkID { 130 t.Fatalf("bad check: %#v", result) 131 } 132 } 133 134 func testRegisterSidecarProxy(t *testing.T, s *Store, idx uint64, nodeID string, targetServiceID string) { 135 svc := &structs.NodeService{ 136 ID: targetServiceID + "-sidecar-proxy", 137 Service: targetServiceID + "-sidecar-proxy", 138 Port: 20000, 139 Kind: structs.ServiceKindConnectProxy, 140 Proxy: structs.ConnectProxyConfig{ 141 DestinationServiceName: targetServiceID, 142 DestinationServiceID: targetServiceID, 143 }, 144 } 145 require.NoError(t, s.EnsureService(idx, nodeID, svc)) 146 } 147 148 func testRegisterConnectNativeService(t *testing.T, s *Store, idx uint64, nodeID string, serviceID string) { 149 svc := &structs.NodeService{ 150 ID: serviceID, 151 Service: serviceID, 152 Port: 1111, 153 Connect: structs.ServiceConnect{ 154 Native: true, 155 }, 156 } 157 require.NoError(t, s.EnsureService(idx, nodeID, svc)) 158 } 159 160 func testSetKey(t *testing.T, s *Store, idx uint64, key, value string) { 161 entry := &structs.DirEntry{Key: key, Value: []byte(value)} 162 if err := s.KVSSet(idx, entry); err != nil { 163 t.Fatalf("err: %s", err) 164 } 165 166 tx := s.db.Txn(false) 167 defer tx.Abort() 168 e, err := tx.First("kvs", "id", key) 169 if err != nil { 170 t.Fatalf("err: %s", err) 171 } 172 if result, ok := e.(*structs.DirEntry); !ok || result.Key != key { 173 t.Fatalf("bad kvs entry: %#v", result) 174 } 175 } 176 177 // watchFired is a helper for unit tests that returns if the given watch set 178 // fired (it doesn't care which watch actually fired). This uses a fixed 179 // timeout since we already expect the event happened before calling this and 180 // just need to distinguish a fire from a timeout. We do need a little time to 181 // allow the watch to set up any goroutines, though. 182 func watchFired(ws memdb.WatchSet) bool { 183 timedOut := ws.Watch(time.After(50 * time.Millisecond)) 184 return !timedOut 185 } 186 187 func TestStateStore_Restore_Abort(t *testing.T) { 188 s := testStateStore(t) 189 190 // The detailed restore functions are tested below, this just checks 191 // that abort works. 192 restore := s.Restore() 193 entry := &structs.DirEntry{ 194 Key: "foo", 195 Value: []byte("bar"), 196 RaftIndex: structs.RaftIndex{ 197 ModifyIndex: 5, 198 }, 199 } 200 if err := restore.KVS(entry); err != nil { 201 t.Fatalf("err: %s", err) 202 } 203 restore.Abort() 204 205 idx, entries, err := s.KVSList(nil, "") 206 if err != nil { 207 t.Fatalf("err: %s", err) 208 } 209 if idx != 0 { 210 t.Fatalf("bad index: %d", idx) 211 } 212 if len(entries) != 0 { 213 t.Fatalf("bad: %#v", entries) 214 } 215 } 216 217 func TestStateStore_Abandon(t *testing.T) { 218 s := testStateStore(t) 219 abandonCh := s.AbandonCh() 220 s.Abandon() 221 select { 222 case <-abandonCh: 223 default: 224 t.Fatalf("bad") 225 } 226 } 227 228 func TestStateStore_maxIndex(t *testing.T) { 229 s := testStateStore(t) 230 231 testRegisterNode(t, s, 0, "foo") 232 testRegisterNode(t, s, 1, "bar") 233 testRegisterService(t, s, 2, "foo", "consul") 234 235 if max := s.maxIndex("nodes", "services"); max != 2 { 236 t.Fatalf("bad max: %d", max) 237 } 238 } 239 240 func TestStateStore_indexUpdateMaxTxn(t *testing.T) { 241 s := testStateStore(t) 242 243 testRegisterNode(t, s, 0, "foo") 244 testRegisterNode(t, s, 1, "bar") 245 246 tx := s.db.Txn(true) 247 if err := indexUpdateMaxTxn(tx, 3, "nodes"); err != nil { 248 t.Fatalf("err: %s", err) 249 } 250 tx.Commit() 251 252 if max := s.maxIndex("nodes"); max != 3 { 253 t.Fatalf("bad max: %d", max) 254 } 255 }