github.imxd.top/hashicorp/consul@v1.4.5/agent/consul/state/intention_test.go (about) 1 package state 2 3 import ( 4 "testing" 5 "time" 6 7 "github.com/hashicorp/consul/agent/structs" 8 "github.com/hashicorp/go-memdb" 9 "github.com/stretchr/testify/assert" 10 "github.com/stretchr/testify/require" 11 ) 12 13 func TestStore_IntentionGet_none(t *testing.T) { 14 assert := assert.New(t) 15 s := testStateStore(t) 16 17 // Querying with no results returns nil. 18 ws := memdb.NewWatchSet() 19 idx, res, err := s.IntentionGet(ws, testUUID()) 20 assert.Equal(uint64(1), idx) 21 assert.Nil(res) 22 assert.Nil(err) 23 } 24 25 func TestStore_IntentionSetGet_basic(t *testing.T) { 26 assert := assert.New(t) 27 s := testStateStore(t) 28 29 // Call Get to populate the watch set 30 ws := memdb.NewWatchSet() 31 _, _, err := s.IntentionGet(ws, testUUID()) 32 assert.Nil(err) 33 34 // Build a valid intention 35 ixn := &structs.Intention{ 36 ID: testUUID(), 37 SourceNS: "default", 38 SourceName: "*", 39 DestinationNS: "default", 40 DestinationName: "web", 41 Meta: map[string]string{}, 42 } 43 44 // Inserting a with empty ID is disallowed. 45 assert.NoError(s.IntentionSet(1, ixn)) 46 47 // Make sure the index got updated. 48 assert.Equal(uint64(1), s.maxIndex(intentionsTableName)) 49 assert.True(watchFired(ws), "watch fired") 50 51 // Read it back out and verify it. 52 expected := &structs.Intention{ 53 ID: ixn.ID, 54 SourceNS: "default", 55 SourceName: "*", 56 DestinationNS: "default", 57 DestinationName: "web", 58 Meta: map[string]string{}, 59 RaftIndex: structs.RaftIndex{ 60 CreateIndex: 1, 61 ModifyIndex: 1, 62 }, 63 } 64 expected.UpdatePrecedence() 65 66 ws = memdb.NewWatchSet() 67 idx, actual, err := s.IntentionGet(ws, ixn.ID) 68 assert.NoError(err) 69 assert.Equal(expected.CreateIndex, idx) 70 assert.Equal(expected, actual) 71 72 // Change a value and test updating 73 ixn.SourceNS = "foo" 74 assert.NoError(s.IntentionSet(2, ixn)) 75 76 // Change a value that isn't in the unique 4 tuple and check we don't 77 // incorrectly consider this a duplicate when updating. 78 ixn.Action = structs.IntentionActionDeny 79 assert.NoError(s.IntentionSet(2, ixn)) 80 81 // Make sure the index got updated. 82 assert.Equal(uint64(2), s.maxIndex(intentionsTableName)) 83 assert.True(watchFired(ws), "watch fired") 84 85 // Read it back and verify the data was updated 86 expected.SourceNS = ixn.SourceNS 87 expected.Action = structs.IntentionActionDeny 88 expected.ModifyIndex = 2 89 ws = memdb.NewWatchSet() 90 idx, actual, err = s.IntentionGet(ws, ixn.ID) 91 assert.NoError(err) 92 assert.Equal(expected.ModifyIndex, idx) 93 assert.Equal(expected, actual) 94 95 // Attempt to insert another intention with duplicate 4-tuple 96 ixn = &structs.Intention{ 97 ID: testUUID(), 98 SourceNS: "default", 99 SourceName: "*", 100 DestinationNS: "default", 101 DestinationName: "web", 102 Meta: map[string]string{}, 103 } 104 105 // Duplicate 4-tuple should cause an error 106 ws = memdb.NewWatchSet() 107 assert.Error(s.IntentionSet(3, ixn)) 108 109 // Make sure the index did NOT get updated. 110 assert.Equal(uint64(2), s.maxIndex(intentionsTableName)) 111 assert.False(watchFired(ws), "watch not fired") 112 } 113 114 func TestStore_IntentionSet_emptyId(t *testing.T) { 115 assert := assert.New(t) 116 s := testStateStore(t) 117 118 ws := memdb.NewWatchSet() 119 _, _, err := s.IntentionGet(ws, testUUID()) 120 assert.NoError(err) 121 122 // Inserting a with empty ID is disallowed. 123 err = s.IntentionSet(1, &structs.Intention{}) 124 assert.Error(err) 125 assert.Contains(err.Error(), ErrMissingIntentionID.Error()) 126 127 // Index is not updated if nothing is saved. 128 assert.Equal(s.maxIndex(intentionsTableName), uint64(0)) 129 assert.False(watchFired(ws), "watch fired") 130 } 131 132 func TestStore_IntentionSet_updateCreatedAt(t *testing.T) { 133 assert := assert.New(t) 134 s := testStateStore(t) 135 136 // Build a valid intention 137 now := time.Now() 138 ixn := structs.Intention{ 139 ID: testUUID(), 140 CreatedAt: now, 141 } 142 143 // Insert 144 assert.NoError(s.IntentionSet(1, &ixn)) 145 146 // Change a value and test updating 147 ixnUpdate := ixn 148 ixnUpdate.CreatedAt = now.Add(10 * time.Second) 149 assert.NoError(s.IntentionSet(2, &ixnUpdate)) 150 151 // Read it back and verify 152 _, actual, err := s.IntentionGet(nil, ixn.ID) 153 assert.NoError(err) 154 assert.Equal(now, actual.CreatedAt) 155 } 156 157 func TestStore_IntentionSet_metaNil(t *testing.T) { 158 assert := assert.New(t) 159 s := testStateStore(t) 160 161 // Build a valid intention 162 ixn := structs.Intention{ 163 ID: testUUID(), 164 } 165 166 // Insert 167 assert.NoError(s.IntentionSet(1, &ixn)) 168 169 // Read it back and verify 170 _, actual, err := s.IntentionGet(nil, ixn.ID) 171 assert.NoError(err) 172 assert.NotNil(actual.Meta) 173 } 174 175 func TestStore_IntentionSet_metaSet(t *testing.T) { 176 assert := assert.New(t) 177 s := testStateStore(t) 178 179 // Build a valid intention 180 ixn := structs.Intention{ 181 ID: testUUID(), 182 Meta: map[string]string{"foo": "bar"}, 183 } 184 185 // Insert 186 assert.NoError(s.IntentionSet(1, &ixn)) 187 188 // Read it back and verify 189 _, actual, err := s.IntentionGet(nil, ixn.ID) 190 assert.NoError(err) 191 assert.Equal(ixn.Meta, actual.Meta) 192 } 193 194 func TestStore_IntentionDelete(t *testing.T) { 195 assert := assert.New(t) 196 s := testStateStore(t) 197 198 // Call Get to populate the watch set 199 ws := memdb.NewWatchSet() 200 _, _, err := s.IntentionGet(ws, testUUID()) 201 assert.NoError(err) 202 203 // Create 204 ixn := &structs.Intention{ID: testUUID()} 205 assert.NoError(s.IntentionSet(1, ixn)) 206 207 // Make sure the index got updated. 208 assert.Equal(s.maxIndex(intentionsTableName), uint64(1)) 209 assert.True(watchFired(ws), "watch fired") 210 211 // Delete 212 assert.NoError(s.IntentionDelete(2, ixn.ID)) 213 214 // Make sure the index got updated. 215 assert.Equal(s.maxIndex(intentionsTableName), uint64(2)) 216 assert.True(watchFired(ws), "watch fired") 217 218 // Sanity check to make sure it's not there. 219 idx, actual, err := s.IntentionGet(nil, ixn.ID) 220 assert.NoError(err) 221 assert.Equal(idx, uint64(2)) 222 assert.Nil(actual) 223 } 224 225 func TestStore_IntentionsList(t *testing.T) { 226 assert := assert.New(t) 227 s := testStateStore(t) 228 229 // Querying with no results returns nil. 230 ws := memdb.NewWatchSet() 231 idx, res, err := s.Intentions(ws) 232 assert.NoError(err) 233 assert.Nil(res) 234 assert.Equal(uint64(1), idx) 235 236 // Create some intentions 237 ixns := structs.Intentions{ 238 &structs.Intention{ 239 ID: testUUID(), 240 Meta: map[string]string{}, 241 }, 242 &structs.Intention{ 243 ID: testUUID(), 244 Meta: map[string]string{}, 245 }, 246 } 247 248 // Force deterministic sort order 249 ixns[0].ID = "a" + ixns[0].ID[1:] 250 ixns[1].ID = "b" + ixns[1].ID[1:] 251 252 // Create 253 for i, ixn := range ixns { 254 assert.NoError(s.IntentionSet(uint64(1+i), ixn)) 255 } 256 assert.True(watchFired(ws), "watch fired") 257 258 // Read it back and verify. 259 expected := structs.Intentions{ 260 &structs.Intention{ 261 ID: ixns[0].ID, 262 Meta: map[string]string{}, 263 RaftIndex: structs.RaftIndex{ 264 CreateIndex: 1, 265 ModifyIndex: 1, 266 }, 267 }, 268 &structs.Intention{ 269 ID: ixns[1].ID, 270 Meta: map[string]string{}, 271 RaftIndex: structs.RaftIndex{ 272 CreateIndex: 2, 273 ModifyIndex: 2, 274 }, 275 }, 276 } 277 for i := range expected { 278 expected[i].UpdatePrecedence() // to match what is returned... 279 } 280 idx, actual, err := s.Intentions(nil) 281 assert.NoError(err) 282 assert.Equal(idx, uint64(2)) 283 assert.Equal(expected, actual) 284 } 285 286 // Test the matrix of match logic. 287 // 288 // Note that this doesn't need to test the intention sort logic exhaustively 289 // since this is tested in their sort implementation in the structs. 290 func TestStore_IntentionMatch_table(t *testing.T) { 291 type testCase struct { 292 Name string 293 Insert [][]string // List of intentions to insert 294 Query [][]string // List of intentions to match 295 Expected [][][]string // List of matches, where each match is a list of intentions 296 } 297 298 cases := []testCase{ 299 { 300 "single exact namespace/name", 301 [][]string{ 302 {"foo", "*"}, 303 {"foo", "bar"}, 304 {"foo", "baz"}, // shouldn't match 305 {"bar", "bar"}, // shouldn't match 306 {"bar", "*"}, // shouldn't match 307 {"*", "*"}, 308 }, 309 [][]string{ 310 {"foo", "bar"}, 311 }, 312 [][][]string{ 313 { 314 {"foo", "bar"}, 315 {"foo", "*"}, 316 {"*", "*"}, 317 }, 318 }, 319 }, 320 321 { 322 "multiple exact namespace/name", 323 [][]string{ 324 {"foo", "*"}, 325 {"foo", "bar"}, 326 {"foo", "baz"}, // shouldn't match 327 {"bar", "bar"}, 328 {"bar", "*"}, 329 }, 330 [][]string{ 331 {"foo", "bar"}, 332 {"bar", "bar"}, 333 }, 334 [][][]string{ 335 { 336 {"foo", "bar"}, 337 {"foo", "*"}, 338 }, 339 { 340 {"bar", "bar"}, 341 {"bar", "*"}, 342 }, 343 }, 344 }, 345 346 { 347 "single exact namespace/name with duplicate destinations", 348 [][]string{ 349 // 4-tuple specifies src and destination to test duplicate destinations 350 // with different sources. We flip them around to test in both 351 // directions. The first pair are the ones searched on in both cases so 352 // the duplicates need to be there. 353 {"foo", "bar", "foo", "*"}, 354 {"foo", "bar", "bar", "*"}, 355 {"*", "*", "*", "*"}, 356 }, 357 [][]string{ 358 {"foo", "bar"}, 359 }, 360 [][][]string{ 361 { 362 // Note the first two have the same precedence so we rely on arbitrary 363 // lexicographical tie-break behavior. 364 {"foo", "bar", "bar", "*"}, 365 {"foo", "bar", "foo", "*"}, 366 {"*", "*", "*", "*"}, 367 }, 368 }, 369 }, 370 } 371 372 // testRunner implements the test for a single case, but can be 373 // parameterized to run for both source and destination so we can 374 // test both cases. 375 testRunner := func(t *testing.T, tc testCase, typ structs.IntentionMatchType) { 376 // Insert the set 377 assert := assert.New(t) 378 s := testStateStore(t) 379 var idx uint64 = 1 380 for _, v := range tc.Insert { 381 ixn := &structs.Intention{ID: testUUID()} 382 switch typ { 383 case structs.IntentionMatchDestination: 384 ixn.DestinationNS = v[0] 385 ixn.DestinationName = v[1] 386 if len(v) == 4 { 387 ixn.SourceNS = v[2] 388 ixn.SourceName = v[3] 389 } 390 case structs.IntentionMatchSource: 391 ixn.SourceNS = v[0] 392 ixn.SourceName = v[1] 393 if len(v) == 4 { 394 ixn.DestinationNS = v[2] 395 ixn.DestinationName = v[3] 396 } 397 } 398 399 assert.NoError(s.IntentionSet(idx, ixn)) 400 401 idx++ 402 } 403 404 // Build the arguments 405 args := &structs.IntentionQueryMatch{Type: typ} 406 for _, q := range tc.Query { 407 args.Entries = append(args.Entries, structs.IntentionMatchEntry{ 408 Namespace: q[0], 409 Name: q[1], 410 }) 411 } 412 413 // Match 414 _, matches, err := s.IntentionMatch(nil, args) 415 assert.NoError(err) 416 417 // Should have equal lengths 418 require.Len(t, matches, len(tc.Expected)) 419 420 // Verify matches 421 for i, expected := range tc.Expected { 422 var actual [][]string 423 for _, ixn := range matches[i] { 424 switch typ { 425 case structs.IntentionMatchDestination: 426 if len(expected) > 1 && len(expected[0]) == 4 { 427 actual = append(actual, []string{ 428 ixn.DestinationNS, 429 ixn.DestinationName, 430 ixn.SourceNS, 431 ixn.SourceName, 432 }) 433 } else { 434 actual = append(actual, []string{ixn.DestinationNS, ixn.DestinationName}) 435 } 436 case structs.IntentionMatchSource: 437 if len(expected) > 1 && len(expected[0]) == 4 { 438 actual = append(actual, []string{ 439 ixn.SourceNS, 440 ixn.SourceName, 441 ixn.DestinationNS, 442 ixn.DestinationName, 443 }) 444 } else { 445 actual = append(actual, []string{ixn.SourceNS, ixn.SourceName}) 446 } 447 } 448 } 449 450 assert.Equal(expected, actual) 451 } 452 } 453 454 for _, tc := range cases { 455 t.Run(tc.Name+" (destination)", func(t *testing.T) { 456 testRunner(t, tc, structs.IntentionMatchDestination) 457 }) 458 459 t.Run(tc.Name+" (source)", func(t *testing.T) { 460 testRunner(t, tc, structs.IntentionMatchSource) 461 }) 462 } 463 } 464 465 func TestStore_Intention_Snapshot_Restore(t *testing.T) { 466 assert := assert.New(t) 467 s := testStateStore(t) 468 469 // Create some intentions. 470 ixns := structs.Intentions{ 471 &structs.Intention{ 472 DestinationName: "foo", 473 }, 474 &structs.Intention{ 475 DestinationName: "bar", 476 }, 477 &structs.Intention{ 478 DestinationName: "baz", 479 }, 480 } 481 482 // Force the sort order of the UUIDs before we create them so the 483 // order is deterministic. 484 id := testUUID() 485 ixns[0].ID = "a" + id[1:] 486 ixns[1].ID = "b" + id[1:] 487 ixns[2].ID = "c" + id[1:] 488 489 // Now create 490 for i, ixn := range ixns { 491 assert.NoError(s.IntentionSet(uint64(4+i), ixn)) 492 } 493 494 // Snapshot the queries. 495 snap := s.Snapshot() 496 defer snap.Close() 497 498 // Alter the real state store. 499 assert.NoError(s.IntentionDelete(7, ixns[0].ID)) 500 501 // Verify the snapshot. 502 assert.Equal(snap.LastIndex(), uint64(6)) 503 504 // Expect them sorted in insertion order 505 expected := structs.Intentions{ 506 &structs.Intention{ 507 ID: ixns[0].ID, 508 DestinationName: "foo", 509 Meta: map[string]string{}, 510 RaftIndex: structs.RaftIndex{ 511 CreateIndex: 4, 512 ModifyIndex: 4, 513 }, 514 }, 515 &structs.Intention{ 516 ID: ixns[1].ID, 517 DestinationName: "bar", 518 Meta: map[string]string{}, 519 RaftIndex: structs.RaftIndex{ 520 CreateIndex: 5, 521 ModifyIndex: 5, 522 }, 523 }, 524 &structs.Intention{ 525 ID: ixns[2].ID, 526 DestinationName: "baz", 527 Meta: map[string]string{}, 528 RaftIndex: structs.RaftIndex{ 529 CreateIndex: 6, 530 ModifyIndex: 6, 531 }, 532 }, 533 } 534 for i := range expected { 535 expected[i].UpdatePrecedence() // to match what is returned... 536 } 537 dump, err := snap.Intentions() 538 assert.NoError(err) 539 assert.Equal(expected, dump) 540 541 // Restore the values into a new state store. 542 func() { 543 s := testStateStore(t) 544 restore := s.Restore() 545 for _, ixn := range dump { 546 assert.NoError(restore.Intention(ixn)) 547 } 548 restore.Commit() 549 550 // Read the restored values back out and verify that they match. Note that 551 // Intentions are returned precedence sorted unlike the snapshot so we need 552 // to rearrange the expected slice some. 553 expected[0], expected[1], expected[2] = expected[1], expected[2], expected[0] 554 idx, actual, err := s.Intentions(nil) 555 assert.NoError(err) 556 assert.Equal(idx, uint64(6)) 557 assert.Equal(expected, actual) 558 }() 559 }