1package state 2 3import ( 4 "testing" 5 "time" 6 7 memdb "github.com/hashicorp/go-memdb" 8 "github.com/stretchr/testify/require" 9 10 "github.com/hashicorp/consul/agent/structs" 11 "github.com/hashicorp/consul/sdk/testutil" 12) 13 14func TestStore_ConfigEntry(t *testing.T) { 15 require := require.New(t) 16 s := testConfigStateStore(t) 17 18 expected := &structs.ProxyConfigEntry{ 19 Kind: structs.ProxyDefaults, 20 Name: "global", 21 Config: map[string]interface{}{ 22 "DestinationServiceName": "foo", 23 }, 24 } 25 26 // Create 27 require.NoError(s.EnsureConfigEntry(0, expected)) 28 29 idx, config, err := s.ConfigEntry(nil, structs.ProxyDefaults, "global", nil) 30 require.NoError(err) 31 require.Equal(uint64(0), idx) 32 require.Equal(expected, config) 33 34 // Update 35 updated := &structs.ProxyConfigEntry{ 36 Kind: structs.ProxyDefaults, 37 Name: "global", 38 Config: map[string]interface{}{ 39 "DestinationServiceName": "bar", 40 }, 41 } 42 require.NoError(s.EnsureConfigEntry(1, updated)) 43 44 idx, config, err = s.ConfigEntry(nil, structs.ProxyDefaults, "global", nil) 45 require.NoError(err) 46 require.Equal(uint64(1), idx) 47 require.Equal(updated, config) 48 49 // Delete 50 require.NoError(s.DeleteConfigEntry(2, structs.ProxyDefaults, "global", nil)) 51 52 idx, config, err = s.ConfigEntry(nil, structs.ProxyDefaults, "global", nil) 53 require.NoError(err) 54 require.Equal(uint64(2), idx) 55 require.Nil(config) 56 57 // Set up a watch. 58 serviceConf := &structs.ServiceConfigEntry{ 59 Kind: structs.ServiceDefaults, 60 Name: "foo", 61 } 62 require.NoError(s.EnsureConfigEntry(3, serviceConf)) 63 64 ws := memdb.NewWatchSet() 65 _, _, err = s.ConfigEntry(ws, structs.ServiceDefaults, "foo", nil) 66 require.NoError(err) 67 68 // Make an unrelated modification and make sure the watch doesn't fire. 69 require.NoError(s.EnsureConfigEntry(4, updated)) 70 require.False(watchFired(ws)) 71 72 // Update the watched config and make sure it fires. 73 serviceConf.Protocol = "http" 74 require.NoError(s.EnsureConfigEntry(5, serviceConf)) 75 require.True(watchFired(ws)) 76} 77 78func TestStore_ConfigEntryCAS(t *testing.T) { 79 require := require.New(t) 80 s := testConfigStateStore(t) 81 82 expected := &structs.ProxyConfigEntry{ 83 Kind: structs.ProxyDefaults, 84 Name: "global", 85 Config: map[string]interface{}{ 86 "DestinationServiceName": "foo", 87 }, 88 } 89 90 // Create 91 require.NoError(s.EnsureConfigEntry(1, expected)) 92 93 idx, config, err := s.ConfigEntry(nil, structs.ProxyDefaults, "global", nil) 94 require.NoError(err) 95 require.Equal(uint64(1), idx) 96 require.Equal(expected, config) 97 98 // Update with invalid index 99 updated := &structs.ProxyConfigEntry{ 100 Kind: structs.ProxyDefaults, 101 Name: "global", 102 Config: map[string]interface{}{ 103 "DestinationServiceName": "bar", 104 }, 105 } 106 ok, err := s.EnsureConfigEntryCAS(2, 99, updated) 107 require.False(ok) 108 require.NoError(err) 109 110 // Entry should not be changed 111 idx, config, err = s.ConfigEntry(nil, structs.ProxyDefaults, "global", nil) 112 require.NoError(err) 113 require.Equal(uint64(1), idx) 114 require.Equal(expected, config) 115 116 // Update with a valid index 117 ok, err = s.EnsureConfigEntryCAS(2, 1, updated) 118 require.True(ok) 119 require.NoError(err) 120 121 // Entry should be updated 122 idx, config, err = s.ConfigEntry(nil, structs.ProxyDefaults, "global", nil) 123 require.NoError(err) 124 require.Equal(uint64(2), idx) 125 require.Equal(updated, config) 126} 127 128func TestStore_ConfigEntry_UpdateOver(t *testing.T) { 129 // This test uses ServiceIntentions because they are the only 130 // kind that implements UpdateOver() at this time. 131 132 s := testConfigStateStore(t) 133 134 var ( 135 idA = testUUID() 136 idB = testUUID() 137 138 loc = time.FixedZone("UTC-8", -8*60*60) 139 timeA = time.Date(1955, 11, 5, 6, 15, 0, 0, loc) 140 timeB = time.Date(1985, 10, 26, 1, 35, 0, 0, loc) 141 ) 142 require.NotEqual(t, idA, idB) 143 144 initial := &structs.ServiceIntentionsConfigEntry{ 145 Kind: structs.ServiceIntentions, 146 Name: "api", 147 Sources: []*structs.SourceIntention{ 148 { 149 LegacyID: idA, 150 Name: "web", 151 Action: structs.IntentionActionAllow, 152 LegacyCreateTime: &timeA, 153 LegacyUpdateTime: &timeA, 154 }, 155 }, 156 } 157 158 // Create 159 nextIndex := uint64(1) 160 require.NoError(t, s.EnsureConfigEntry(nextIndex, initial.Clone())) 161 162 idx, raw, err := s.ConfigEntry(nil, structs.ServiceIntentions, "api", nil) 163 require.NoError(t, err) 164 require.Equal(t, nextIndex, idx) 165 166 got, ok := raw.(*structs.ServiceIntentionsConfigEntry) 167 require.True(t, ok) 168 initial.RaftIndex = got.RaftIndex 169 require.Equal(t, initial, got) 170 171 t.Run("update and fail change legacyID", func(t *testing.T) { 172 // Update 173 updated := &structs.ServiceIntentionsConfigEntry{ 174 Kind: structs.ServiceIntentions, 175 Name: "api", 176 Sources: []*structs.SourceIntention{ 177 { 178 LegacyID: idB, 179 Name: "web", 180 Action: structs.IntentionActionDeny, 181 LegacyCreateTime: &timeB, 182 LegacyUpdateTime: &timeB, 183 }, 184 }, 185 } 186 187 nextIndex++ 188 err := s.EnsureConfigEntry(nextIndex, updated.Clone()) 189 testutil.RequireErrorContains(t, err, "cannot set this field to a different value") 190 }) 191 192 t.Run("update and do not update create time", func(t *testing.T) { 193 // Update 194 updated := &structs.ServiceIntentionsConfigEntry{ 195 Kind: structs.ServiceIntentions, 196 Name: "api", 197 Sources: []*structs.SourceIntention{ 198 { 199 LegacyID: idA, 200 Name: "web", 201 Action: structs.IntentionActionDeny, 202 LegacyCreateTime: &timeB, 203 LegacyUpdateTime: &timeB, 204 }, 205 }, 206 } 207 208 nextIndex++ 209 require.NoError(t, s.EnsureConfigEntry(nextIndex, updated.Clone())) 210 211 // check 212 idx, raw, err = s.ConfigEntry(nil, structs.ServiceIntentions, "api", nil) 213 require.NoError(t, err) 214 require.Equal(t, nextIndex, idx) 215 216 got, ok = raw.(*structs.ServiceIntentionsConfigEntry) 217 require.True(t, ok) 218 updated.RaftIndex = got.RaftIndex 219 updated.Sources[0].LegacyCreateTime = &timeA // UpdateOver will not replace this 220 require.Equal(t, updated, got) 221 }) 222} 223 224func TestStore_ConfigEntries(t *testing.T) { 225 require := require.New(t) 226 s := testConfigStateStore(t) 227 228 // Create some config entries. 229 entry1 := &structs.ProxyConfigEntry{ 230 Kind: structs.ProxyDefaults, 231 Name: "test1", 232 } 233 entry2 := &structs.ServiceConfigEntry{ 234 Kind: structs.ServiceDefaults, 235 Name: "test2", 236 } 237 entry3 := &structs.ServiceConfigEntry{ 238 Kind: structs.ServiceDefaults, 239 Name: "test3", 240 } 241 242 require.NoError(s.EnsureConfigEntry(0, entry1)) 243 require.NoError(s.EnsureConfigEntry(1, entry2)) 244 require.NoError(s.EnsureConfigEntry(2, entry3)) 245 246 // Get all entries 247 idx, entries, err := s.ConfigEntries(nil, nil) 248 require.NoError(err) 249 require.Equal(uint64(2), idx) 250 require.Equal([]structs.ConfigEntry{entry1, entry2, entry3}, entries) 251 252 // Get all proxy entries 253 idx, entries, err = s.ConfigEntriesByKind(nil, structs.ProxyDefaults, nil) 254 require.NoError(err) 255 require.Equal(uint64(2), idx) 256 require.Equal([]structs.ConfigEntry{entry1}, entries) 257 258 // Get all service entries 259 ws := memdb.NewWatchSet() 260 idx, entries, err = s.ConfigEntriesByKind(ws, structs.ServiceDefaults, nil) 261 require.NoError(err) 262 require.Equal(uint64(2), idx) 263 require.Equal([]structs.ConfigEntry{entry2, entry3}, entries) 264 265 // Watch should not have fired 266 require.False(watchFired(ws)) 267 268 // Now make an update and make sure the watch fires. 269 require.NoError(s.EnsureConfigEntry(3, &structs.ServiceConfigEntry{ 270 Kind: structs.ServiceDefaults, 271 Name: "test2", 272 Protocol: "tcp", 273 })) 274 require.True(watchFired(ws)) 275} 276 277func TestStore_ConfigEntry_GraphValidation(t *testing.T) { 278 type tcase struct { 279 entries []structs.ConfigEntry 280 op func(t *testing.T, s *Store) error 281 expectErr string 282 expectGraphErr bool 283 } 284 cases := map[string]tcase{ 285 "splitter fails without default protocol": { 286 entries: []structs.ConfigEntry{}, 287 op: func(t *testing.T, s *Store) error { 288 entry := &structs.ServiceSplitterConfigEntry{ 289 Kind: structs.ServiceSplitter, 290 Name: "main", 291 Splits: []structs.ServiceSplit{ 292 {Weight: 100}, 293 }, 294 } 295 return s.EnsureConfigEntry(0, entry) 296 }, 297 expectErr: "does not permit advanced routing or splitting behavior", 298 expectGraphErr: true, 299 }, 300 "splitter fails with tcp protocol": { 301 entries: []structs.ConfigEntry{ 302 &structs.ServiceConfigEntry{ 303 Kind: structs.ServiceDefaults, 304 Name: "main", 305 Protocol: "tcp", 306 }, 307 }, 308 op: func(t *testing.T, s *Store) error { 309 entry := &structs.ServiceSplitterConfigEntry{ 310 Kind: structs.ServiceSplitter, 311 Name: "main", 312 Splits: []structs.ServiceSplit{ 313 {Weight: 100}, 314 }, 315 } 316 return s.EnsureConfigEntry(0, entry) 317 }, 318 expectErr: "does not permit advanced routing or splitting behavior", 319 expectGraphErr: true, 320 }, 321 "splitter works with http protocol": { 322 entries: []structs.ConfigEntry{ 323 &structs.ProxyConfigEntry{ 324 Kind: structs.ProxyDefaults, 325 Name: structs.ProxyConfigGlobal, 326 Config: map[string]interface{}{ 327 "protocol": "tcp", // loses 328 }, 329 }, 330 &structs.ServiceConfigEntry{ 331 Kind: structs.ServiceDefaults, 332 Name: "main", 333 Protocol: "http", 334 EnterpriseMeta: *structs.DefaultEnterpriseMeta(), 335 }, 336 &structs.ServiceResolverConfigEntry{ 337 Kind: structs.ServiceResolver, 338 Name: "main", 339 Subsets: map[string]structs.ServiceResolverSubset{ 340 "v1": { 341 Filter: "Service.Meta.version == v1", 342 }, 343 "v2": { 344 Filter: "Service.Meta.version == v2", 345 }, 346 }, 347 }, 348 }, 349 op: func(t *testing.T, s *Store) error { 350 entry := &structs.ServiceSplitterConfigEntry{ 351 Kind: structs.ServiceSplitter, 352 Name: "main", 353 Splits: []structs.ServiceSplit{ 354 {Weight: 90, ServiceSubset: "v1"}, 355 {Weight: 10, ServiceSubset: "v2"}, 356 }, 357 EnterpriseMeta: *structs.DefaultEnterpriseMeta(), 358 } 359 return s.EnsureConfigEntry(0, entry) 360 }, 361 }, 362 "splitter works with http protocol (from proxy-defaults)": { 363 entries: []structs.ConfigEntry{ 364 &structs.ProxyConfigEntry{ 365 Kind: structs.ProxyDefaults, 366 Name: structs.ProxyConfigGlobal, 367 Config: map[string]interface{}{ 368 "protocol": "http", 369 }, 370 }, 371 &structs.ServiceResolverConfigEntry{ 372 Kind: structs.ServiceResolver, 373 Name: "main", 374 Subsets: map[string]structs.ServiceResolverSubset{ 375 "v1": { 376 Filter: "Service.Meta.version == v1", 377 }, 378 "v2": { 379 Filter: "Service.Meta.version == v2", 380 }, 381 }, 382 }, 383 }, 384 op: func(t *testing.T, s *Store) error { 385 entry := &structs.ServiceSplitterConfigEntry{ 386 Kind: structs.ServiceSplitter, 387 Name: "main", 388 Splits: []structs.ServiceSplit{ 389 {Weight: 90, ServiceSubset: "v1"}, 390 {Weight: 10, ServiceSubset: "v2"}, 391 }, 392 } 393 return s.EnsureConfigEntry(0, entry) 394 }, 395 }, 396 "router fails with tcp protocol": { 397 entries: []structs.ConfigEntry{ 398 &structs.ServiceConfigEntry{ 399 Kind: structs.ServiceDefaults, 400 Name: "main", 401 Protocol: "tcp", 402 }, 403 &structs.ServiceResolverConfigEntry{ 404 Kind: structs.ServiceResolver, 405 Name: "main", 406 Subsets: map[string]structs.ServiceResolverSubset{ 407 "other": { 408 Filter: "Service.Meta.version == other", 409 }, 410 }, 411 }, 412 }, 413 op: func(t *testing.T, s *Store) error { 414 entry := &structs.ServiceRouterConfigEntry{ 415 Kind: structs.ServiceRouter, 416 Name: "main", 417 Routes: []structs.ServiceRoute{ 418 { 419 Match: &structs.ServiceRouteMatch{ 420 HTTP: &structs.ServiceRouteHTTPMatch{ 421 PathExact: "/other", 422 }, 423 }, 424 Destination: &structs.ServiceRouteDestination{ 425 ServiceSubset: "other", 426 }, 427 }, 428 }, 429 } 430 return s.EnsureConfigEntry(0, entry) 431 }, 432 expectErr: "does not permit advanced routing or splitting behavior", 433 expectGraphErr: true, 434 }, 435 "router fails without default protocol": { 436 entries: []structs.ConfigEntry{ 437 &structs.ServiceResolverConfigEntry{ 438 Kind: structs.ServiceResolver, 439 Name: "main", 440 Subsets: map[string]structs.ServiceResolverSubset{ 441 "other": { 442 Filter: "Service.Meta.version == other", 443 }, 444 }, 445 }, 446 }, 447 op: func(t *testing.T, s *Store) error { 448 entry := &structs.ServiceRouterConfigEntry{ 449 Kind: structs.ServiceRouter, 450 Name: "main", 451 Routes: []structs.ServiceRoute{ 452 { 453 Match: &structs.ServiceRouteMatch{ 454 HTTP: &structs.ServiceRouteHTTPMatch{ 455 PathExact: "/other", 456 }, 457 }, 458 Destination: &structs.ServiceRouteDestination{ 459 ServiceSubset: "other", 460 }, 461 }, 462 }, 463 } 464 return s.EnsureConfigEntry(0, entry) 465 }, 466 expectErr: "does not permit advanced routing or splitting behavior", 467 expectGraphErr: true, 468 }, 469 ///////////////////////////////////////////////// 470 "cannot remove default protocol after splitter created": { 471 entries: []structs.ConfigEntry{ 472 &structs.ServiceConfigEntry{ 473 Kind: structs.ServiceDefaults, 474 Name: "main", 475 Protocol: "http", 476 }, 477 &structs.ServiceResolverConfigEntry{ 478 Kind: structs.ServiceResolver, 479 Name: "main", 480 Subsets: map[string]structs.ServiceResolverSubset{ 481 "v1": { 482 Filter: "Service.Meta.version == v1", 483 }, 484 "v2": { 485 Filter: "Service.Meta.version == v2", 486 }, 487 }, 488 }, 489 &structs.ServiceSplitterConfigEntry{ 490 Kind: structs.ServiceSplitter, 491 Name: "main", 492 Splits: []structs.ServiceSplit{ 493 {Weight: 90, ServiceSubset: "v1"}, 494 {Weight: 10, ServiceSubset: "v2"}, 495 }, 496 }, 497 }, 498 op: func(t *testing.T, s *Store) error { 499 return s.DeleteConfigEntry(0, structs.ServiceDefaults, "main", nil) 500 }, 501 expectErr: "does not permit advanced routing or splitting behavior", 502 expectGraphErr: true, 503 }, 504 "cannot remove global default protocol after splitter created": { 505 entries: []structs.ConfigEntry{ 506 &structs.ProxyConfigEntry{ 507 Kind: structs.ProxyDefaults, 508 Name: structs.ProxyConfigGlobal, 509 Config: map[string]interface{}{ 510 "protocol": "http", 511 }, 512 }, 513 &structs.ServiceResolverConfigEntry{ 514 Kind: structs.ServiceResolver, 515 Name: "main", 516 Subsets: map[string]structs.ServiceResolverSubset{ 517 "v1": { 518 Filter: "Service.Meta.version == v1", 519 }, 520 "v2": { 521 Filter: "Service.Meta.version == v2", 522 }, 523 }, 524 }, 525 &structs.ServiceSplitterConfigEntry{ 526 Kind: structs.ServiceSplitter, 527 Name: "main", 528 Splits: []structs.ServiceSplit{ 529 {Weight: 90, ServiceSubset: "v1"}, 530 {Weight: 10, ServiceSubset: "v2"}, 531 }, 532 }, 533 }, 534 op: func(t *testing.T, s *Store) error { 535 return s.DeleteConfigEntry(0, structs.ProxyDefaults, structs.ProxyConfigGlobal, nil) 536 }, 537 expectErr: "does not permit advanced routing or splitting behavior", 538 expectGraphErr: true, 539 }, 540 "can remove global default protocol after splitter created if service default overrides it": { 541 entries: []structs.ConfigEntry{ 542 &structs.ProxyConfigEntry{ 543 Kind: structs.ProxyDefaults, 544 Name: structs.ProxyConfigGlobal, 545 Config: map[string]interface{}{ 546 "protocol": "http", 547 }, 548 }, 549 &structs.ServiceConfigEntry{ 550 Kind: structs.ServiceDefaults, 551 Name: "main", 552 Protocol: "http", 553 }, 554 &structs.ServiceResolverConfigEntry{ 555 Kind: structs.ServiceResolver, 556 Name: "main", 557 Subsets: map[string]structs.ServiceResolverSubset{ 558 "v1": { 559 Filter: "Service.Meta.version == v1", 560 }, 561 "v2": { 562 Filter: "Service.Meta.version == v2", 563 }, 564 }, 565 }, 566 &structs.ServiceSplitterConfigEntry{ 567 Kind: structs.ServiceSplitter, 568 Name: "main", 569 Splits: []structs.ServiceSplit{ 570 {Weight: 90, ServiceSubset: "v1"}, 571 {Weight: 10, ServiceSubset: "v2"}, 572 }, 573 }, 574 }, 575 op: func(t *testing.T, s *Store) error { 576 return s.DeleteConfigEntry(0, structs.ProxyDefaults, structs.ProxyConfigGlobal, nil) 577 }, 578 }, 579 "cannot change to tcp protocol after splitter created": { 580 entries: []structs.ConfigEntry{ 581 &structs.ServiceConfigEntry{ 582 Kind: structs.ServiceDefaults, 583 Name: "main", 584 Protocol: "http", 585 }, 586 &structs.ServiceResolverConfigEntry{ 587 Kind: structs.ServiceResolver, 588 Name: "main", 589 Subsets: map[string]structs.ServiceResolverSubset{ 590 "v1": { 591 Filter: "Service.Meta.version == v1", 592 }, 593 "v2": { 594 Filter: "Service.Meta.version == v2", 595 }, 596 }, 597 }, 598 &structs.ServiceSplitterConfigEntry{ 599 Kind: structs.ServiceSplitter, 600 Name: "main", 601 Splits: []structs.ServiceSplit{ 602 {Weight: 90, ServiceSubset: "v1"}, 603 {Weight: 10, ServiceSubset: "v2"}, 604 }, 605 }, 606 }, 607 op: func(t *testing.T, s *Store) error { 608 entry := &structs.ServiceConfigEntry{ 609 Kind: structs.ServiceDefaults, 610 Name: "main", 611 Protocol: "tcp", 612 } 613 return s.EnsureConfigEntry(0, entry) 614 }, 615 expectErr: "does not permit advanced routing or splitting behavior", 616 expectGraphErr: true, 617 }, 618 "cannot remove default protocol after router created": { 619 entries: []structs.ConfigEntry{ 620 &structs.ServiceConfigEntry{ 621 Kind: structs.ServiceDefaults, 622 Name: "main", 623 Protocol: "http", 624 }, 625 &structs.ServiceResolverConfigEntry{ 626 Kind: structs.ServiceResolver, 627 Name: "main", 628 Subsets: map[string]structs.ServiceResolverSubset{ 629 "other": { 630 Filter: "Service.Meta.version == other", 631 }, 632 }, 633 }, 634 &structs.ServiceRouterConfigEntry{ 635 Kind: structs.ServiceRouter, 636 Name: "main", 637 Routes: []structs.ServiceRoute{ 638 { 639 Match: &structs.ServiceRouteMatch{ 640 HTTP: &structs.ServiceRouteHTTPMatch{ 641 PathExact: "/other", 642 }, 643 }, 644 Destination: &structs.ServiceRouteDestination{ 645 ServiceSubset: "other", 646 }, 647 }, 648 }, 649 }, 650 }, 651 op: func(t *testing.T, s *Store) error { 652 return s.DeleteConfigEntry(0, structs.ServiceDefaults, "main", nil) 653 }, 654 expectErr: "does not permit advanced routing or splitting behavior", 655 expectGraphErr: true, 656 }, 657 "cannot change to tcp protocol after router created": { 658 entries: []structs.ConfigEntry{ 659 &structs.ServiceConfigEntry{ 660 Kind: structs.ServiceDefaults, 661 Name: "main", 662 Protocol: "http", 663 }, 664 &structs.ServiceResolverConfigEntry{ 665 Kind: structs.ServiceResolver, 666 Name: "main", 667 Subsets: map[string]structs.ServiceResolverSubset{ 668 "other": { 669 Filter: "Service.Meta.version == other", 670 }, 671 }, 672 }, 673 &structs.ServiceRouterConfigEntry{ 674 Kind: structs.ServiceRouter, 675 Name: "main", 676 Routes: []structs.ServiceRoute{ 677 { 678 Match: &structs.ServiceRouteMatch{ 679 HTTP: &structs.ServiceRouteHTTPMatch{ 680 PathExact: "/other", 681 }, 682 }, 683 Destination: &structs.ServiceRouteDestination{ 684 ServiceSubset: "other", 685 }, 686 }, 687 }, 688 }, 689 }, 690 op: func(t *testing.T, s *Store) error { 691 entry := &structs.ServiceConfigEntry{ 692 Kind: structs.ServiceDefaults, 693 Name: "main", 694 Protocol: "tcp", 695 } 696 return s.EnsureConfigEntry(0, entry) 697 }, 698 expectErr: "does not permit advanced routing or splitting behavior", 699 expectGraphErr: true, 700 }, 701 ///////////////////////////////////////////////// 702 "cannot split to a service using tcp": { 703 entries: []structs.ConfigEntry{ 704 &structs.ServiceConfigEntry{ 705 Kind: structs.ServiceDefaults, 706 Name: "main", 707 Protocol: "http", 708 }, 709 &structs.ServiceConfigEntry{ 710 Kind: structs.ServiceDefaults, 711 Name: "other", 712 Protocol: "tcp", 713 }, 714 }, 715 op: func(t *testing.T, s *Store) error { 716 entry := &structs.ServiceSplitterConfigEntry{ 717 Kind: structs.ServiceSplitter, 718 Name: "main", 719 Splits: []structs.ServiceSplit{ 720 {Weight: 90}, 721 {Weight: 10, Service: "other"}, 722 }, 723 } 724 return s.EnsureConfigEntry(0, entry) 725 }, 726 expectErr: "uses inconsistent protocols", 727 expectGraphErr: true, 728 }, 729 "cannot route to a service using tcp": { 730 entries: []structs.ConfigEntry{ 731 &structs.ServiceConfigEntry{ 732 Kind: structs.ServiceDefaults, 733 Name: "main", 734 Protocol: "http", 735 }, 736 &structs.ServiceConfigEntry{ 737 Kind: structs.ServiceDefaults, 738 Name: "other", 739 Protocol: "tcp", 740 }, 741 }, 742 op: func(t *testing.T, s *Store) error { 743 entry := &structs.ServiceRouterConfigEntry{ 744 Kind: structs.ServiceRouter, 745 Name: "main", 746 Routes: []structs.ServiceRoute{ 747 { 748 Match: &structs.ServiceRouteMatch{ 749 HTTP: &structs.ServiceRouteHTTPMatch{ 750 PathExact: "/other", 751 }, 752 }, 753 Destination: &structs.ServiceRouteDestination{ 754 Service: "other", 755 }, 756 }, 757 }, 758 } 759 return s.EnsureConfigEntry(0, entry) 760 }, 761 expectErr: "uses inconsistent protocols", 762 expectGraphErr: true, 763 }, 764 ///////////////////////////////////////////////// 765 "cannot failover to a service using a different protocol": { 766 entries: []structs.ConfigEntry{ 767 &structs.ServiceConfigEntry{ 768 Kind: structs.ServiceDefaults, 769 Name: "main", 770 Protocol: "grpc", 771 }, 772 &structs.ServiceConfigEntry{ 773 Kind: structs.ServiceDefaults, 774 Name: "other", 775 Protocol: "tcp", 776 }, 777 &structs.ServiceResolverConfigEntry{ 778 Kind: structs.ServiceResolver, 779 Name: "main", 780 ConnectTimeout: 33 * time.Second, 781 }, 782 }, 783 op: func(t *testing.T, s *Store) error { 784 entry := &structs.ServiceResolverConfigEntry{ 785 Kind: structs.ServiceResolver, 786 Name: "main", 787 Failover: map[string]structs.ServiceResolverFailover{ 788 "*": { 789 Service: "other", 790 }, 791 }, 792 } 793 return s.EnsureConfigEntry(0, entry) 794 }, 795 expectErr: "uses inconsistent protocols", 796 expectGraphErr: true, 797 }, 798 "cannot redirect to a service using a different protocol": { 799 entries: []structs.ConfigEntry{ 800 &structs.ServiceConfigEntry{ 801 Kind: structs.ServiceDefaults, 802 Name: "main", 803 Protocol: "grpc", 804 }, 805 &structs.ServiceConfigEntry{ 806 Kind: structs.ServiceDefaults, 807 Name: "other", 808 Protocol: "tcp", 809 }, 810 &structs.ServiceResolverConfigEntry{ 811 Kind: structs.ServiceResolver, 812 Name: "main", 813 ConnectTimeout: 33 * time.Second, 814 }, 815 }, 816 op: func(t *testing.T, s *Store) error { 817 entry := &structs.ServiceResolverConfigEntry{ 818 Kind: structs.ServiceResolver, 819 Name: "main", 820 Redirect: &structs.ServiceResolverRedirect{ 821 Service: "other", 822 }, 823 } 824 return s.EnsureConfigEntry(0, entry) 825 }, 826 expectErr: "uses inconsistent protocols", 827 expectGraphErr: true, 828 }, 829 ///////////////////////////////////////////////// 830 "redirect to a subset that does exist is fine": { 831 entries: []structs.ConfigEntry{ 832 &structs.ServiceResolverConfigEntry{ 833 Kind: structs.ServiceResolver, 834 Name: "other", 835 ConnectTimeout: 33 * time.Second, 836 Subsets: map[string]structs.ServiceResolverSubset{ 837 "v1": { 838 Filter: "Service.Meta.version == v1", 839 }, 840 }, 841 }, 842 }, 843 op: func(t *testing.T, s *Store) error { 844 entry := &structs.ServiceResolverConfigEntry{ 845 Kind: structs.ServiceResolver, 846 Name: "main", 847 Redirect: &structs.ServiceResolverRedirect{ 848 Service: "other", 849 ServiceSubset: "v1", 850 }, 851 } 852 return s.EnsureConfigEntry(0, entry) 853 }, 854 }, 855 "cannot redirect to a subset that does not exist": { 856 entries: []structs.ConfigEntry{ 857 &structs.ServiceResolverConfigEntry{ 858 Kind: structs.ServiceResolver, 859 Name: "other", 860 ConnectTimeout: 33 * time.Second, 861 }, 862 }, 863 op: func(t *testing.T, s *Store) error { 864 entry := &structs.ServiceResolverConfigEntry{ 865 Kind: structs.ServiceResolver, 866 Name: "main", 867 Redirect: &structs.ServiceResolverRedirect{ 868 Service: "other", 869 ServiceSubset: "v1", 870 }, 871 } 872 return s.EnsureConfigEntry(0, entry) 873 }, 874 expectErr: `does not have a subset named "v1"`, 875 expectGraphErr: true, 876 }, 877 ///////////////////////////////////////////////// 878 "cannot introduce circular resolver redirect": { 879 entries: []structs.ConfigEntry{ 880 &structs.ServiceResolverConfigEntry{ 881 Kind: structs.ServiceResolver, 882 Name: "other", 883 Redirect: &structs.ServiceResolverRedirect{ 884 Service: "main", 885 }, 886 }, 887 }, 888 op: func(t *testing.T, s *Store) error { 889 entry := &structs.ServiceResolverConfigEntry{ 890 Kind: structs.ServiceResolver, 891 Name: "main", 892 Redirect: &structs.ServiceResolverRedirect{ 893 Service: "other", 894 }, 895 } 896 return s.EnsureConfigEntry(0, entry) 897 }, 898 expectErr: `detected circular resolver redirect`, 899 expectGraphErr: true, 900 }, 901 "cannot introduce circular split": { 902 entries: []structs.ConfigEntry{ 903 &structs.ProxyConfigEntry{ 904 Kind: structs.ProxyDefaults, 905 Name: structs.ProxyConfigGlobal, 906 Config: map[string]interface{}{ 907 "protocol": "http", 908 }, 909 }, 910 &structs.ServiceSplitterConfigEntry{ 911 Kind: "service-splitter", 912 Name: "other", 913 Splits: []structs.ServiceSplit{ 914 {Weight: 100, Service: "main"}, 915 }, 916 }, 917 }, 918 op: func(t *testing.T, s *Store) error { 919 entry := &structs.ServiceSplitterConfigEntry{ 920 Kind: "service-splitter", 921 Name: "main", 922 Splits: []structs.ServiceSplit{ 923 {Weight: 100, Service: "other"}, 924 }, 925 } 926 return s.EnsureConfigEntry(0, entry) 927 }, 928 expectErr: `detected circular reference`, 929 expectGraphErr: true, 930 }, 931 } 932 933 for name, tc := range cases { 934 name := name 935 tc := tc 936 937 t.Run(name, func(t *testing.T) { 938 s := testConfigStateStore(t) 939 for _, entry := range tc.entries { 940 require.NoError(t, entry.Normalize()) 941 require.NoError(t, s.EnsureConfigEntry(0, entry)) 942 } 943 944 err := tc.op(t, s) 945 if tc.expectErr != "" { 946 require.Error(t, err) 947 require.Contains(t, err.Error(), tc.expectErr) 948 _, ok := err.(*structs.ConfigEntryGraphError) 949 if tc.expectGraphErr { 950 require.True(t, ok, "%T is not a *ConfigEntryGraphError", err) 951 } else { 952 require.False(t, ok, "did not expect a *ConfigEntryGraphError here: %v", err) 953 } 954 } else { 955 require.NoError(t, err) 956 } 957 }) 958 } 959} 960 961func TestStore_ReadDiscoveryChainConfigEntries_Overrides(t *testing.T) { 962 for _, tc := range []struct { 963 name string 964 entries []structs.ConfigEntry 965 expectBefore []ConfigEntryKindName 966 overrides map[ConfigEntryKindName]structs.ConfigEntry 967 expectAfter []ConfigEntryKindName 968 expectAfterErr string 969 checkAfter func(t *testing.T, entrySet *structs.DiscoveryChainConfigEntries) 970 }{ 971 { 972 name: "mask service-defaults", 973 entries: []structs.ConfigEntry{ 974 &structs.ServiceConfigEntry{ 975 Kind: structs.ServiceDefaults, 976 Name: "main", 977 Protocol: "tcp", 978 }, 979 }, 980 expectBefore: []ConfigEntryKindName{ 981 NewConfigEntryKindName(structs.ServiceDefaults, "main", nil), 982 }, 983 overrides: map[ConfigEntryKindName]structs.ConfigEntry{ 984 NewConfigEntryKindName(structs.ServiceDefaults, "main", nil): nil, 985 }, 986 expectAfter: []ConfigEntryKindName{ 987 // nothing 988 }, 989 }, 990 { 991 name: "edit service-defaults", 992 entries: []structs.ConfigEntry{ 993 &structs.ServiceConfigEntry{ 994 Kind: structs.ServiceDefaults, 995 Name: "main", 996 Protocol: "tcp", 997 }, 998 }, 999 expectBefore: []ConfigEntryKindName{ 1000 NewConfigEntryKindName(structs.ServiceDefaults, "main", nil), 1001 }, 1002 overrides: map[ConfigEntryKindName]structs.ConfigEntry{ 1003 NewConfigEntryKindName(structs.ServiceDefaults, "main", nil): &structs.ServiceConfigEntry{ 1004 Kind: structs.ServiceDefaults, 1005 Name: "main", 1006 Protocol: "grpc", 1007 }, 1008 }, 1009 expectAfter: []ConfigEntryKindName{ 1010 NewConfigEntryKindName(structs.ServiceDefaults, "main", nil), 1011 }, 1012 checkAfter: func(t *testing.T, entrySet *structs.DiscoveryChainConfigEntries) { 1013 defaults := entrySet.GetService(structs.NewServiceID("main", nil)) 1014 require.NotNil(t, defaults) 1015 require.Equal(t, "grpc", defaults.Protocol) 1016 }, 1017 }, 1018 1019 { 1020 name: "mask service-router", 1021 entries: []structs.ConfigEntry{ 1022 &structs.ServiceConfigEntry{ 1023 Kind: structs.ServiceDefaults, 1024 Name: "main", 1025 Protocol: "http", 1026 }, 1027 &structs.ServiceRouterConfigEntry{ 1028 Kind: structs.ServiceRouter, 1029 Name: "main", 1030 }, 1031 }, 1032 expectBefore: []ConfigEntryKindName{ 1033 NewConfigEntryKindName(structs.ServiceDefaults, "main", nil), 1034 NewConfigEntryKindName(structs.ServiceRouter, "main", nil), 1035 }, 1036 overrides: map[ConfigEntryKindName]structs.ConfigEntry{ 1037 NewConfigEntryKindName(structs.ServiceRouter, "main", nil): nil, 1038 }, 1039 expectAfter: []ConfigEntryKindName{ 1040 NewConfigEntryKindName(structs.ServiceDefaults, "main", nil), 1041 }, 1042 }, 1043 { 1044 name: "edit service-router", 1045 entries: []structs.ConfigEntry{ 1046 &structs.ServiceConfigEntry{ 1047 Kind: structs.ServiceDefaults, 1048 Name: "main", 1049 Protocol: "http", 1050 }, 1051 &structs.ServiceResolverConfigEntry{ 1052 Kind: structs.ServiceResolver, 1053 Name: "main", 1054 Subsets: map[string]structs.ServiceResolverSubset{ 1055 "v1": {Filter: "Service.Meta.version == v1"}, 1056 "v2": {Filter: "Service.Meta.version == v2"}, 1057 "v3": {Filter: "Service.Meta.version == v3"}, 1058 }, 1059 }, 1060 &structs.ServiceRouterConfigEntry{ 1061 Kind: structs.ServiceRouter, 1062 Name: "main", 1063 Routes: []structs.ServiceRoute{ 1064 { 1065 Match: &structs.ServiceRouteMatch{ 1066 HTTP: &structs.ServiceRouteHTTPMatch{ 1067 PathExact: "/admin", 1068 }, 1069 }, 1070 Destination: &structs.ServiceRouteDestination{ 1071 ServiceSubset: "v2", 1072 }, 1073 }, 1074 }, 1075 }, 1076 }, 1077 expectBefore: []ConfigEntryKindName{ 1078 NewConfigEntryKindName(structs.ServiceDefaults, "main", nil), 1079 NewConfigEntryKindName(structs.ServiceResolver, "main", nil), 1080 NewConfigEntryKindName(structs.ServiceRouter, "main", nil), 1081 }, 1082 overrides: map[ConfigEntryKindName]structs.ConfigEntry{ 1083 NewConfigEntryKindName(structs.ServiceRouter, "main", nil): &structs.ServiceRouterConfigEntry{ 1084 Kind: structs.ServiceRouter, 1085 Name: "main", 1086 Routes: []structs.ServiceRoute{ 1087 { 1088 Match: &structs.ServiceRouteMatch{ 1089 HTTP: &structs.ServiceRouteHTTPMatch{ 1090 PathExact: "/admin", 1091 }, 1092 }, 1093 Destination: &structs.ServiceRouteDestination{ 1094 ServiceSubset: "v3", 1095 }, 1096 }, 1097 }, 1098 }, 1099 }, 1100 expectAfter: []ConfigEntryKindName{ 1101 NewConfigEntryKindName(structs.ServiceDefaults, "main", nil), 1102 NewConfigEntryKindName(structs.ServiceResolver, "main", nil), 1103 NewConfigEntryKindName(structs.ServiceRouter, "main", nil), 1104 }, 1105 checkAfter: func(t *testing.T, entrySet *structs.DiscoveryChainConfigEntries) { 1106 router := entrySet.GetRouter(structs.NewServiceID("main", nil)) 1107 require.NotNil(t, router) 1108 require.Len(t, router.Routes, 1) 1109 1110 expect := structs.ServiceRoute{ 1111 Match: &structs.ServiceRouteMatch{ 1112 HTTP: &structs.ServiceRouteHTTPMatch{ 1113 PathExact: "/admin", 1114 }, 1115 }, 1116 Destination: &structs.ServiceRouteDestination{ 1117 ServiceSubset: "v3", 1118 }, 1119 } 1120 require.Equal(t, expect, router.Routes[0]) 1121 }, 1122 }, 1123 1124 { 1125 name: "mask service-splitter", 1126 entries: []structs.ConfigEntry{ 1127 &structs.ServiceConfigEntry{ 1128 Kind: structs.ServiceDefaults, 1129 Name: "main", 1130 Protocol: "http", 1131 }, 1132 &structs.ServiceSplitterConfigEntry{ 1133 Kind: structs.ServiceSplitter, 1134 Name: "main", 1135 Splits: []structs.ServiceSplit{ 1136 {Weight: 100}, 1137 }, 1138 }, 1139 }, 1140 expectBefore: []ConfigEntryKindName{ 1141 NewConfigEntryKindName(structs.ServiceDefaults, "main", nil), 1142 NewConfigEntryKindName(structs.ServiceSplitter, "main", nil), 1143 }, 1144 overrides: map[ConfigEntryKindName]structs.ConfigEntry{ 1145 NewConfigEntryKindName(structs.ServiceSplitter, "main", nil): nil, 1146 }, 1147 expectAfter: []ConfigEntryKindName{ 1148 NewConfigEntryKindName(structs.ServiceDefaults, "main", nil), 1149 }, 1150 }, 1151 { 1152 name: "edit service-splitter", 1153 entries: []structs.ConfigEntry{ 1154 &structs.ServiceConfigEntry{ 1155 Kind: structs.ServiceDefaults, 1156 Name: "main", 1157 Protocol: "http", 1158 }, 1159 &structs.ServiceSplitterConfigEntry{ 1160 Kind: structs.ServiceSplitter, 1161 Name: "main", 1162 Splits: []structs.ServiceSplit{ 1163 {Weight: 100}, 1164 }, 1165 }, 1166 }, 1167 expectBefore: []ConfigEntryKindName{ 1168 NewConfigEntryKindName(structs.ServiceDefaults, "main", nil), 1169 NewConfigEntryKindName(structs.ServiceSplitter, "main", nil), 1170 }, 1171 overrides: map[ConfigEntryKindName]structs.ConfigEntry{ 1172 NewConfigEntryKindName(structs.ServiceSplitter, "main", nil): &structs.ServiceSplitterConfigEntry{ 1173 Kind: structs.ServiceSplitter, 1174 Name: "main", 1175 Splits: []structs.ServiceSplit{ 1176 {Weight: 85, ServiceSubset: "v1"}, 1177 {Weight: 15, ServiceSubset: "v2"}, 1178 }, 1179 }, 1180 }, 1181 expectAfter: []ConfigEntryKindName{ 1182 NewConfigEntryKindName(structs.ServiceDefaults, "main", nil), 1183 NewConfigEntryKindName(structs.ServiceSplitter, "main", nil), 1184 }, 1185 checkAfter: func(t *testing.T, entrySet *structs.DiscoveryChainConfigEntries) { 1186 splitter := entrySet.GetSplitter(structs.NewServiceID("main", nil)) 1187 require.NotNil(t, splitter) 1188 require.Len(t, splitter.Splits, 2) 1189 1190 expect := []structs.ServiceSplit{ 1191 {Weight: 85, ServiceSubset: "v1"}, 1192 {Weight: 15, ServiceSubset: "v2"}, 1193 } 1194 require.Equal(t, expect, splitter.Splits) 1195 }, 1196 }, 1197 1198 { 1199 name: "mask service-resolver", 1200 entries: []structs.ConfigEntry{ 1201 &structs.ServiceResolverConfigEntry{ 1202 Kind: structs.ServiceResolver, 1203 Name: "main", 1204 }, 1205 }, 1206 expectBefore: []ConfigEntryKindName{ 1207 NewConfigEntryKindName(structs.ServiceResolver, "main", nil), 1208 }, 1209 overrides: map[ConfigEntryKindName]structs.ConfigEntry{ 1210 NewConfigEntryKindName(structs.ServiceResolver, "main", nil): nil, 1211 }, 1212 expectAfter: []ConfigEntryKindName{ 1213 // nothing 1214 }, 1215 }, 1216 { 1217 name: "edit service-resolver", 1218 entries: []structs.ConfigEntry{ 1219 &structs.ServiceResolverConfigEntry{ 1220 Kind: structs.ServiceResolver, 1221 Name: "main", 1222 }, 1223 }, 1224 expectBefore: []ConfigEntryKindName{ 1225 NewConfigEntryKindName(structs.ServiceResolver, "main", nil), 1226 }, 1227 overrides: map[ConfigEntryKindName]structs.ConfigEntry{ 1228 NewConfigEntryKindName(structs.ServiceResolver, "main", nil): &structs.ServiceResolverConfigEntry{ 1229 Kind: structs.ServiceResolver, 1230 Name: "main", 1231 ConnectTimeout: 33 * time.Second, 1232 }, 1233 }, 1234 expectAfter: []ConfigEntryKindName{ 1235 NewConfigEntryKindName(structs.ServiceResolver, "main", nil), 1236 }, 1237 checkAfter: func(t *testing.T, entrySet *structs.DiscoveryChainConfigEntries) { 1238 resolver := entrySet.GetResolver(structs.NewServiceID("main", nil)) 1239 require.NotNil(t, resolver) 1240 require.Equal(t, 33*time.Second, resolver.ConnectTimeout) 1241 }, 1242 }, 1243 } { 1244 tc := tc 1245 1246 t.Run(tc.name, func(t *testing.T) { 1247 s := testConfigStateStore(t) 1248 for _, entry := range tc.entries { 1249 require.NoError(t, s.EnsureConfigEntry(0, entry)) 1250 } 1251 1252 t.Run("without override", func(t *testing.T) { 1253 _, entrySet, err := s.readDiscoveryChainConfigEntries(nil, "main", nil, nil) 1254 require.NoError(t, err) 1255 got := entrySetToKindNames(entrySet) 1256 require.ElementsMatch(t, tc.expectBefore, got) 1257 }) 1258 1259 t.Run("with override", func(t *testing.T) { 1260 _, entrySet, err := s.readDiscoveryChainConfigEntries(nil, "main", tc.overrides, nil) 1261 1262 if tc.expectAfterErr != "" { 1263 require.Error(t, err) 1264 require.Contains(t, err.Error(), tc.expectAfterErr) 1265 } else { 1266 require.NoError(t, err) 1267 got := entrySetToKindNames(entrySet) 1268 require.ElementsMatch(t, tc.expectAfter, got) 1269 1270 if tc.checkAfter != nil { 1271 tc.checkAfter(t, entrySet) 1272 } 1273 } 1274 }) 1275 }) 1276 } 1277} 1278 1279func entrySetToKindNames(entrySet *structs.DiscoveryChainConfigEntries) []ConfigEntryKindName { 1280 var out []ConfigEntryKindName 1281 for _, entry := range entrySet.Routers { 1282 out = append(out, NewConfigEntryKindName( 1283 entry.Kind, 1284 entry.Name, 1285 &entry.EnterpriseMeta, 1286 )) 1287 } 1288 for _, entry := range entrySet.Splitters { 1289 out = append(out, NewConfigEntryKindName( 1290 entry.Kind, 1291 entry.Name, 1292 &entry.EnterpriseMeta, 1293 )) 1294 } 1295 for _, entry := range entrySet.Resolvers { 1296 out = append(out, NewConfigEntryKindName( 1297 entry.Kind, 1298 entry.Name, 1299 &entry.EnterpriseMeta, 1300 )) 1301 } 1302 for _, entry := range entrySet.Services { 1303 out = append(out, NewConfigEntryKindName( 1304 entry.Kind, 1305 entry.Name, 1306 &entry.EnterpriseMeta, 1307 )) 1308 } 1309 return out 1310} 1311 1312func TestStore_ReadDiscoveryChainConfigEntries_SubsetSplit(t *testing.T) { 1313 s := testConfigStateStore(t) 1314 1315 entries := []structs.ConfigEntry{ 1316 &structs.ServiceConfigEntry{ 1317 Kind: structs.ServiceDefaults, 1318 Name: "main", 1319 Protocol: "http", 1320 }, 1321 &structs.ServiceResolverConfigEntry{ 1322 Kind: structs.ServiceResolver, 1323 Name: "main", 1324 Subsets: map[string]structs.ServiceResolverSubset{ 1325 "v1": { 1326 Filter: "Service.Meta.version == v1", 1327 }, 1328 "v2": { 1329 Filter: "Service.Meta.version == v2", 1330 }, 1331 }, 1332 }, 1333 &structs.ServiceSplitterConfigEntry{ 1334 Kind: structs.ServiceSplitter, 1335 Name: "main", 1336 Splits: []structs.ServiceSplit{ 1337 {Weight: 90, ServiceSubset: "v1"}, 1338 {Weight: 10, ServiceSubset: "v2"}, 1339 }, 1340 }, 1341 } 1342 1343 for _, entry := range entries { 1344 require.NoError(t, s.EnsureConfigEntry(0, entry)) 1345 } 1346 1347 _, entrySet, err := s.readDiscoveryChainConfigEntries(nil, "main", nil, nil) 1348 require.NoError(t, err) 1349 1350 require.Len(t, entrySet.Routers, 0) 1351 require.Len(t, entrySet.Splitters, 1) 1352 require.Len(t, entrySet.Resolvers, 1) 1353 require.Len(t, entrySet.Services, 1) 1354} 1355 1356// TODO(rb): add ServiceIntentions tests 1357 1358func TestStore_ValidateGatewayNamesCannotBeShared(t *testing.T) { 1359 s := testConfigStateStore(t) 1360 1361 ingress := &structs.IngressGatewayConfigEntry{ 1362 Kind: structs.IngressGateway, 1363 Name: "gateway", 1364 } 1365 require.NoError(t, s.EnsureConfigEntry(0, ingress)) 1366 1367 terminating := &structs.TerminatingGatewayConfigEntry{ 1368 Kind: structs.TerminatingGateway, 1369 Name: "gateway", 1370 } 1371 // Cannot have 2 gateways with same service name 1372 require.Error(t, s.EnsureConfigEntry(1, terminating)) 1373 1374 ingress = &structs.IngressGatewayConfigEntry{ 1375 Kind: structs.IngressGateway, 1376 Name: "gateway", 1377 Listeners: []structs.IngressListener{ 1378 {Port: 8080}, 1379 }, 1380 } 1381 require.NoError(t, s.EnsureConfigEntry(2, ingress)) 1382 require.NoError(t, s.DeleteConfigEntry(3, structs.IngressGateway, "gateway", nil)) 1383 1384 // Adding the terminating gateway with same name should now work 1385 require.NoError(t, s.EnsureConfigEntry(4, terminating)) 1386 1387 // Cannot have 2 gateways with same service name 1388 require.Error(t, s.EnsureConfigEntry(5, ingress)) 1389} 1390 1391func TestStore_ValidateIngressGatewayErrorOnMismatchedProtocols(t *testing.T) { 1392 newIngress := func(protocol, name string) *structs.IngressGatewayConfigEntry { 1393 return &structs.IngressGatewayConfigEntry{ 1394 Kind: structs.IngressGateway, 1395 Name: "gateway", 1396 Listeners: []structs.IngressListener{ 1397 { 1398 Port: 8080, 1399 Protocol: protocol, 1400 Services: []structs.IngressService{ 1401 {Name: name}, 1402 }, 1403 }, 1404 }, 1405 } 1406 } 1407 1408 t.Run("http ingress fails with http upstream later changed to tcp", func(t *testing.T) { 1409 s := testConfigStateStore(t) 1410 1411 // First set the target service as http 1412 expected := &structs.ServiceConfigEntry{ 1413 Kind: structs.ServiceDefaults, 1414 Name: "web", 1415 Protocol: "http", 1416 } 1417 require.NoError(t, s.EnsureConfigEntry(0, expected)) 1418 1419 // Next configure http ingress to route to the http service 1420 require.NoError(t, s.EnsureConfigEntry(1, newIngress("http", "web"))) 1421 1422 t.Run("via modification", func(t *testing.T) { 1423 // Now redefine the target service as tcp 1424 expected = &structs.ServiceConfigEntry{ 1425 Kind: structs.ServiceDefaults, 1426 Name: "web", 1427 Protocol: "tcp", 1428 } 1429 1430 err := s.EnsureConfigEntry(2, expected) 1431 require.Error(t, err) 1432 require.Contains(t, err.Error(), `has protocol "tcp"`) 1433 }) 1434 t.Run("via deletion", func(t *testing.T) { 1435 // This will fall back to the default tcp. 1436 err := s.DeleteConfigEntry(2, structs.ServiceDefaults, "web", nil) 1437 require.Error(t, err) 1438 require.Contains(t, err.Error(), `has protocol "tcp"`) 1439 }) 1440 }) 1441 1442 t.Run("tcp ingress ok with tcp upstream (defaulted) later changed to http", func(t *testing.T) { 1443 s := testConfigStateStore(t) 1444 1445 // First configure tcp ingress to route to a defaulted tcp service 1446 require.NoError(t, s.EnsureConfigEntry(0, newIngress("tcp", "web"))) 1447 1448 // Now redefine the target service as http 1449 expected := &structs.ServiceConfigEntry{ 1450 Kind: structs.ServiceDefaults, 1451 Name: "web", 1452 Protocol: "http", 1453 } 1454 require.NoError(t, s.EnsureConfigEntry(1, expected)) 1455 }) 1456 1457 t.Run("tcp ingress fails with tcp upstream (defaulted) later changed to http", func(t *testing.T) { 1458 s := testConfigStateStore(t) 1459 1460 // First configure tcp ingress to route to a defaulted tcp service 1461 require.NoError(t, s.EnsureConfigEntry(0, newIngress("tcp", "web"))) 1462 1463 // Now redefine the target service as http 1464 expected := &structs.ServiceConfigEntry{ 1465 Kind: structs.ServiceDefaults, 1466 Name: "web", 1467 Protocol: "http", 1468 } 1469 require.NoError(t, s.EnsureConfigEntry(1, expected)) 1470 1471 t.Run("and a router defined", func(t *testing.T) { 1472 // This part should fail. 1473 expected2 := &structs.ServiceRouterConfigEntry{ 1474 Kind: structs.ServiceRouter, 1475 Name: "web", 1476 } 1477 err := s.EnsureConfigEntry(2, expected2) 1478 require.Error(t, err) 1479 require.Contains(t, err.Error(), `has protocol "http"`) 1480 }) 1481 1482 t.Run("and a splitter defined", func(t *testing.T) { 1483 // This part should fail. 1484 expected2 := &structs.ServiceSplitterConfigEntry{ 1485 Kind: structs.ServiceSplitter, 1486 Name: "web", 1487 Splits: []structs.ServiceSplit{ 1488 {Weight: 100}, 1489 }, 1490 } 1491 err := s.EnsureConfigEntry(2, expected2) 1492 require.Error(t, err) 1493 require.Contains(t, err.Error(), `has protocol "http"`) 1494 }) 1495 }) 1496 1497 t.Run("http ingress fails with tcp upstream (defaulted)", func(t *testing.T) { 1498 s := testConfigStateStore(t) 1499 err := s.EnsureConfigEntry(0, newIngress("http", "web")) 1500 require.Error(t, err) 1501 require.Contains(t, err.Error(), `has protocol "tcp"`) 1502 }) 1503 1504 t.Run("http ingress fails with http2 upstream (via proxy-defaults)", func(t *testing.T) { 1505 s := testConfigStateStore(t) 1506 expected := &structs.ProxyConfigEntry{ 1507 Kind: structs.ProxyDefaults, 1508 Name: "global", 1509 Config: map[string]interface{}{ 1510 "protocol": "http2", 1511 }, 1512 } 1513 require.NoError(t, s.EnsureConfigEntry(0, expected)) 1514 1515 err := s.EnsureConfigEntry(1, newIngress("http", "web")) 1516 require.Error(t, err) 1517 require.Contains(t, err.Error(), `has protocol "http2"`) 1518 }) 1519 1520 t.Run("http ingress fails with grpc upstream (via service-defaults)", func(t *testing.T) { 1521 s := testConfigStateStore(t) 1522 expected := &structs.ServiceConfigEntry{ 1523 Kind: structs.ServiceDefaults, 1524 Name: "web", 1525 Protocol: "grpc", 1526 } 1527 require.NoError(t, s.EnsureConfigEntry(1, expected)) 1528 err := s.EnsureConfigEntry(2, newIngress("http", "web")) 1529 require.Error(t, err) 1530 require.Contains(t, err.Error(), `has protocol "grpc"`) 1531 }) 1532 1533 t.Run("http ingress ok with http upstream (via service-defaults)", func(t *testing.T) { 1534 s := testConfigStateStore(t) 1535 expected := &structs.ServiceConfigEntry{ 1536 Kind: structs.ServiceDefaults, 1537 Name: "web", 1538 Protocol: "http", 1539 } 1540 require.NoError(t, s.EnsureConfigEntry(2, expected)) 1541 require.NoError(t, s.EnsureConfigEntry(3, newIngress("http", "web"))) 1542 }) 1543 1544 t.Run("http ingress ignores wildcard specifier", func(t *testing.T) { 1545 s := testConfigStateStore(t) 1546 require.NoError(t, s.EnsureConfigEntry(4, newIngress("http", "*"))) 1547 }) 1548 1549 t.Run("deleting ingress config entry ok", func(t *testing.T) { 1550 s := testConfigStateStore(t) 1551 require.NoError(t, s.EnsureConfigEntry(1, newIngress("tcp", "web"))) 1552 require.NoError(t, s.DeleteConfigEntry(5, structs.IngressGateway, "gateway", nil)) 1553 }) 1554} 1555 1556func TestSourcesForTarget(t *testing.T) { 1557 defaultMeta := *structs.DefaultEnterpriseMeta() 1558 1559 type expect struct { 1560 idx uint64 1561 names []structs.ServiceName 1562 } 1563 tt := []struct { 1564 name string 1565 entries []structs.ConfigEntry 1566 expect expect 1567 }{ 1568 { 1569 name: "no relevant config entries", 1570 entries: []structs.ConfigEntry{}, 1571 expect: expect{ 1572 idx: 1, 1573 names: []structs.ServiceName{ 1574 {Name: "sink", EnterpriseMeta: defaultMeta}, 1575 }, 1576 }, 1577 }, 1578 { 1579 name: "from route match", 1580 entries: []structs.ConfigEntry{ 1581 &structs.ProxyConfigEntry{ 1582 Kind: structs.ProxyDefaults, 1583 Name: structs.ProxyConfigGlobal, 1584 Config: map[string]interface{}{ 1585 "protocol": "http", 1586 }, 1587 }, 1588 &structs.ServiceRouterConfigEntry{ 1589 Kind: structs.ServiceRouter, 1590 Name: "web", 1591 Routes: []structs.ServiceRoute{ 1592 { 1593 Match: &structs.ServiceRouteMatch{ 1594 HTTP: &structs.ServiceRouteHTTPMatch{ 1595 PathExact: "/sink", 1596 }, 1597 }, 1598 Destination: &structs.ServiceRouteDestination{ 1599 Service: "sink", 1600 }, 1601 }, 1602 }, 1603 }, 1604 }, 1605 expect: expect{ 1606 idx: 2, 1607 names: []structs.ServiceName{ 1608 {Name: "web", EnterpriseMeta: defaultMeta}, 1609 {Name: "sink", EnterpriseMeta: defaultMeta}, 1610 }, 1611 }, 1612 }, 1613 { 1614 name: "from redirect", 1615 entries: []structs.ConfigEntry{ 1616 &structs.ProxyConfigEntry{ 1617 Kind: structs.ProxyDefaults, 1618 Name: structs.ProxyConfigGlobal, 1619 Config: map[string]interface{}{ 1620 "protocol": "http", 1621 }, 1622 }, 1623 &structs.ServiceResolverConfigEntry{ 1624 Kind: structs.ServiceResolver, 1625 Name: "web", 1626 Redirect: &structs.ServiceResolverRedirect{ 1627 Service: "sink", 1628 }, 1629 }, 1630 }, 1631 expect: expect{ 1632 idx: 2, 1633 names: []structs.ServiceName{ 1634 {Name: "web", EnterpriseMeta: defaultMeta}, 1635 {Name: "sink", EnterpriseMeta: defaultMeta}, 1636 }, 1637 }, 1638 }, 1639 { 1640 name: "from failover", 1641 entries: []structs.ConfigEntry{ 1642 &structs.ProxyConfigEntry{ 1643 Kind: structs.ProxyDefaults, 1644 Name: structs.ProxyConfigGlobal, 1645 Config: map[string]interface{}{ 1646 "protocol": "http", 1647 }, 1648 }, 1649 &structs.ServiceResolverConfigEntry{ 1650 Kind: structs.ServiceResolver, 1651 Name: "web", 1652 Failover: map[string]structs.ServiceResolverFailover{ 1653 "*": { 1654 Service: "sink", 1655 Datacenters: []string{"dc2", "dc3"}, 1656 }, 1657 }, 1658 }, 1659 }, 1660 expect: expect{ 1661 idx: 2, 1662 names: []structs.ServiceName{ 1663 {Name: "web", EnterpriseMeta: defaultMeta}, 1664 {Name: "sink", EnterpriseMeta: defaultMeta}, 1665 }, 1666 }, 1667 }, 1668 { 1669 name: "from splitter", 1670 entries: []structs.ConfigEntry{ 1671 &structs.ProxyConfigEntry{ 1672 Kind: structs.ProxyDefaults, 1673 Name: structs.ProxyConfigGlobal, 1674 Config: map[string]interface{}{ 1675 "protocol": "http", 1676 }, 1677 }, 1678 &structs.ServiceSplitterConfigEntry{ 1679 Kind: structs.ServiceSplitter, 1680 Name: "web", 1681 Splits: []structs.ServiceSplit{ 1682 {Weight: 90, Service: "web"}, 1683 {Weight: 10, Service: "sink"}, 1684 }, 1685 }, 1686 }, 1687 expect: expect{ 1688 idx: 2, 1689 names: []structs.ServiceName{ 1690 {Name: "web", EnterpriseMeta: defaultMeta}, 1691 {Name: "sink", EnterpriseMeta: defaultMeta}, 1692 }, 1693 }, 1694 }, 1695 { 1696 name: "chained route redirect", 1697 entries: []structs.ConfigEntry{ 1698 &structs.ProxyConfigEntry{ 1699 Kind: structs.ProxyDefaults, 1700 Name: structs.ProxyConfigGlobal, 1701 Config: map[string]interface{}{ 1702 "protocol": "http", 1703 }, 1704 }, 1705 &structs.ServiceRouterConfigEntry{ 1706 Kind: structs.ServiceRouter, 1707 Name: "source", 1708 Routes: []structs.ServiceRoute{ 1709 { 1710 Match: &structs.ServiceRouteMatch{ 1711 HTTP: &structs.ServiceRouteHTTPMatch{ 1712 PathExact: "/route", 1713 }, 1714 }, 1715 Destination: &structs.ServiceRouteDestination{ 1716 Service: "routed", 1717 }, 1718 }, 1719 }, 1720 }, 1721 &structs.ServiceResolverConfigEntry{ 1722 Kind: structs.ServiceResolver, 1723 Name: "routed", 1724 Redirect: &structs.ServiceResolverRedirect{ 1725 Service: "sink", 1726 }, 1727 }, 1728 }, 1729 expect: expect{ 1730 idx: 3, 1731 names: []structs.ServiceName{ 1732 {Name: "source", EnterpriseMeta: defaultMeta}, 1733 {Name: "routed", EnterpriseMeta: defaultMeta}, 1734 {Name: "sink", EnterpriseMeta: defaultMeta}, 1735 }, 1736 }, 1737 }, 1738 { 1739 name: "kitchen sink with multiple services referencing sink directly", 1740 entries: []structs.ConfigEntry{ 1741 &structs.ProxyConfigEntry{ 1742 Kind: structs.ProxyDefaults, 1743 Name: structs.ProxyConfigGlobal, 1744 Config: map[string]interface{}{ 1745 "protocol": "http", 1746 }, 1747 }, 1748 &structs.ServiceRouterConfigEntry{ 1749 Kind: structs.ServiceRouter, 1750 Name: "routed", 1751 Routes: []structs.ServiceRoute{ 1752 { 1753 Match: &structs.ServiceRouteMatch{ 1754 HTTP: &structs.ServiceRouteHTTPMatch{ 1755 PathExact: "/sink", 1756 }, 1757 }, 1758 Destination: &structs.ServiceRouteDestination{ 1759 Service: "sink", 1760 }, 1761 }, 1762 }, 1763 }, 1764 &structs.ServiceResolverConfigEntry{ 1765 Kind: structs.ServiceResolver, 1766 Name: "redirected", 1767 Redirect: &structs.ServiceResolverRedirect{ 1768 Service: "sink", 1769 }, 1770 }, 1771 &structs.ServiceResolverConfigEntry{ 1772 Kind: structs.ServiceResolver, 1773 Name: "failed-over", 1774 Failover: map[string]structs.ServiceResolverFailover{ 1775 "*": { 1776 Service: "sink", 1777 Datacenters: []string{"dc2", "dc3"}, 1778 }, 1779 }, 1780 }, 1781 &structs.ServiceSplitterConfigEntry{ 1782 Kind: structs.ServiceSplitter, 1783 Name: "split", 1784 Splits: []structs.ServiceSplit{ 1785 {Weight: 90, Service: "no-op"}, 1786 {Weight: 10, Service: "sink"}, 1787 }, 1788 }, 1789 &structs.ServiceSplitterConfigEntry{ 1790 Kind: structs.ServiceSplitter, 1791 Name: "unrelated", 1792 Splits: []structs.ServiceSplit{ 1793 {Weight: 90, Service: "zip"}, 1794 {Weight: 10, Service: "zop"}, 1795 }, 1796 }, 1797 }, 1798 expect: expect{ 1799 idx: 6, 1800 names: []structs.ServiceName{ 1801 {Name: "split", EnterpriseMeta: defaultMeta}, 1802 {Name: "failed-over", EnterpriseMeta: defaultMeta}, 1803 {Name: "redirected", EnterpriseMeta: defaultMeta}, 1804 {Name: "routed", EnterpriseMeta: defaultMeta}, 1805 {Name: "sink", EnterpriseMeta: defaultMeta}, 1806 }, 1807 }, 1808 }, 1809 } 1810 1811 for _, tc := range tt { 1812 t.Run(tc.name, func(t *testing.T) { 1813 s := testStateStore(t) 1814 ws := memdb.NewWatchSet() 1815 1816 ca := &structs.CAConfiguration{ 1817 Provider: "consul", 1818 } 1819 err := s.CASetConfig(0, ca) 1820 require.NoError(t, err) 1821 1822 var i uint64 = 1 1823 for _, entry := range tc.entries { 1824 require.NoError(t, entry.Normalize()) 1825 require.NoError(t, s.EnsureConfigEntry(i, entry)) 1826 i++ 1827 } 1828 1829 tx := s.db.ReadTxn() 1830 defer tx.Abort() 1831 1832 sn := structs.NewServiceName("sink", structs.DefaultEnterpriseMeta()) 1833 idx, names, err := s.discoveryChainSourcesTxn(tx, ws, "dc1", sn) 1834 require.NoError(t, err) 1835 1836 require.Equal(t, tc.expect.idx, idx) 1837 require.ElementsMatch(t, tc.expect.names, names) 1838 }) 1839 } 1840} 1841 1842func TestTargetsForSource(t *testing.T) { 1843 defaultMeta := *structs.DefaultEnterpriseMeta() 1844 1845 type expect struct { 1846 idx uint64 1847 ids []structs.ServiceName 1848 } 1849 tt := []struct { 1850 name string 1851 entries []structs.ConfigEntry 1852 expect expect 1853 }{ 1854 { 1855 name: "from route match", 1856 entries: []structs.ConfigEntry{ 1857 &structs.ProxyConfigEntry{ 1858 Kind: structs.ProxyDefaults, 1859 Name: structs.ProxyConfigGlobal, 1860 Config: map[string]interface{}{ 1861 "protocol": "http", 1862 }, 1863 }, 1864 &structs.ServiceRouterConfigEntry{ 1865 Kind: structs.ServiceRouter, 1866 Name: "web", 1867 Routes: []structs.ServiceRoute{ 1868 { 1869 Match: &structs.ServiceRouteMatch{ 1870 HTTP: &structs.ServiceRouteHTTPMatch{ 1871 PathExact: "/sink", 1872 }, 1873 }, 1874 Destination: &structs.ServiceRouteDestination{ 1875 Service: "sink", 1876 }, 1877 }, 1878 }, 1879 }, 1880 }, 1881 expect: expect{ 1882 idx: 2, 1883 ids: []structs.ServiceName{ 1884 {Name: "web", EnterpriseMeta: defaultMeta}, 1885 {Name: "sink", EnterpriseMeta: defaultMeta}, 1886 }, 1887 }, 1888 }, 1889 { 1890 name: "from redirect", 1891 entries: []structs.ConfigEntry{ 1892 &structs.ProxyConfigEntry{ 1893 Kind: structs.ProxyDefaults, 1894 Name: structs.ProxyConfigGlobal, 1895 Config: map[string]interface{}{ 1896 "protocol": "http", 1897 }, 1898 }, 1899 &structs.ServiceResolverConfigEntry{ 1900 Kind: structs.ServiceResolver, 1901 Name: "web", 1902 Redirect: &structs.ServiceResolverRedirect{ 1903 Service: "sink", 1904 }, 1905 }, 1906 }, 1907 expect: expect{ 1908 idx: 2, 1909 ids: []structs.ServiceName{ 1910 {Name: "sink", EnterpriseMeta: defaultMeta}, 1911 }, 1912 }, 1913 }, 1914 { 1915 name: "from failover", 1916 entries: []structs.ConfigEntry{ 1917 &structs.ProxyConfigEntry{ 1918 Kind: structs.ProxyDefaults, 1919 Name: structs.ProxyConfigGlobal, 1920 Config: map[string]interface{}{ 1921 "protocol": "http", 1922 }, 1923 }, 1924 &structs.ServiceResolverConfigEntry{ 1925 Kind: structs.ServiceResolver, 1926 Name: "web", 1927 Failover: map[string]structs.ServiceResolverFailover{ 1928 "*": { 1929 Service: "remote-web", 1930 Datacenters: []string{"dc2", "dc3"}, 1931 }, 1932 }, 1933 }, 1934 }, 1935 expect: expect{ 1936 idx: 2, 1937 ids: []structs.ServiceName{ 1938 {Name: "web", EnterpriseMeta: defaultMeta}, 1939 }, 1940 }, 1941 }, 1942 { 1943 name: "from splitter", 1944 entries: []structs.ConfigEntry{ 1945 &structs.ProxyConfigEntry{ 1946 Kind: structs.ProxyDefaults, 1947 Name: structs.ProxyConfigGlobal, 1948 Config: map[string]interface{}{ 1949 "protocol": "http", 1950 }, 1951 }, 1952 &structs.ServiceSplitterConfigEntry{ 1953 Kind: structs.ServiceSplitter, 1954 Name: "web", 1955 Splits: []structs.ServiceSplit{ 1956 {Weight: 90, Service: "web"}, 1957 {Weight: 10, Service: "sink"}, 1958 }, 1959 }, 1960 }, 1961 expect: expect{ 1962 idx: 2, 1963 ids: []structs.ServiceName{ 1964 {Name: "web", EnterpriseMeta: defaultMeta}, 1965 {Name: "sink", EnterpriseMeta: defaultMeta}, 1966 }, 1967 }, 1968 }, 1969 { 1970 name: "chained route redirect", 1971 entries: []structs.ConfigEntry{ 1972 &structs.ProxyConfigEntry{ 1973 Kind: structs.ProxyDefaults, 1974 Name: structs.ProxyConfigGlobal, 1975 Config: map[string]interface{}{ 1976 "protocol": "http", 1977 }, 1978 }, 1979 &structs.ServiceRouterConfigEntry{ 1980 Kind: structs.ServiceRouter, 1981 Name: "web", 1982 Routes: []structs.ServiceRoute{ 1983 { 1984 Match: &structs.ServiceRouteMatch{ 1985 HTTP: &structs.ServiceRouteHTTPMatch{ 1986 PathExact: "/route", 1987 }, 1988 }, 1989 Destination: &structs.ServiceRouteDestination{ 1990 Service: "routed", 1991 }, 1992 }, 1993 }, 1994 }, 1995 &structs.ServiceResolverConfigEntry{ 1996 Kind: structs.ServiceResolver, 1997 Name: "routed", 1998 Redirect: &structs.ServiceResolverRedirect{ 1999 Service: "sink", 2000 }, 2001 }, 2002 }, 2003 expect: expect{ 2004 idx: 3, 2005 ids: []structs.ServiceName{ 2006 {Name: "web", EnterpriseMeta: defaultMeta}, 2007 {Name: "sink", EnterpriseMeta: defaultMeta}, 2008 }, 2009 }, 2010 }, 2011 } 2012 2013 for _, tc := range tt { 2014 t.Run(tc.name, func(t *testing.T) { 2015 s := testStateStore(t) 2016 ws := memdb.NewWatchSet() 2017 2018 ca := &structs.CAConfiguration{ 2019 Provider: "consul", 2020 } 2021 err := s.CASetConfig(0, ca) 2022 require.NoError(t, err) 2023 2024 var i uint64 = 1 2025 for _, entry := range tc.entries { 2026 require.NoError(t, entry.Normalize()) 2027 require.NoError(t, s.EnsureConfigEntry(i, entry)) 2028 i++ 2029 } 2030 2031 tx := s.db.ReadTxn() 2032 defer tx.Abort() 2033 2034 idx, ids, err := s.discoveryChainTargetsTxn(tx, ws, "dc1", "web", nil) 2035 require.NoError(t, err) 2036 2037 require.Equal(t, tc.expect.idx, idx) 2038 require.ElementsMatch(t, tc.expect.ids, ids) 2039 }) 2040 } 2041} 2042 2043func TestStore_ValidateServiceIntentionsErrorOnIncompatibleProtocols(t *testing.T) { 2044 l7perms := []*structs.IntentionPermission{ 2045 { 2046 Action: structs.IntentionActionAllow, 2047 HTTP: &structs.IntentionHTTPPermission{ 2048 PathPrefix: "/v2/", 2049 }, 2050 }, 2051 } 2052 2053 serviceDefaults := func(service, protocol string) *structs.ServiceConfigEntry { 2054 return &structs.ServiceConfigEntry{ 2055 Kind: structs.ServiceDefaults, 2056 Name: service, 2057 Protocol: protocol, 2058 } 2059 } 2060 2061 proxyDefaults := func(protocol string) *structs.ProxyConfigEntry { 2062 return &structs.ProxyConfigEntry{ 2063 Kind: structs.ProxyDefaults, 2064 Name: structs.ProxyConfigGlobal, 2065 Config: map[string]interface{}{ 2066 "protocol": protocol, 2067 }, 2068 } 2069 } 2070 2071 type operation struct { 2072 entry structs.ConfigEntry 2073 deletion bool 2074 } 2075 2076 type testcase struct { 2077 ops []operation 2078 expectLastErr string 2079 } 2080 2081 cases := map[string]testcase{ 2082 "L4 intention cannot upgrade to L7 when tcp": { 2083 ops: []operation{ 2084 { // set the target service as tcp 2085 entry: serviceDefaults("api", "tcp"), 2086 }, 2087 { // create an L4 intention 2088 entry: &structs.ServiceIntentionsConfigEntry{ 2089 Kind: structs.ServiceIntentions, 2090 Name: "api", 2091 Sources: []*structs.SourceIntention{ 2092 {Name: "web", Action: structs.IntentionActionAllow}, 2093 }, 2094 }, 2095 }, 2096 { // Should fail if converted to L7 2097 entry: &structs.ServiceIntentionsConfigEntry{ 2098 Kind: structs.ServiceIntentions, 2099 Name: "api", 2100 Sources: []*structs.SourceIntention{ 2101 {Name: "web", Permissions: l7perms}, 2102 }, 2103 }, 2104 }, 2105 }, 2106 expectLastErr: `has protocol "tcp"`, 2107 }, 2108 "L4 intention can upgrade to L7 when made http via service-defaults": { 2109 ops: []operation{ 2110 { // set the target service as tcp 2111 entry: serviceDefaults("api", "tcp"), 2112 }, 2113 { // create an L4 intention 2114 entry: &structs.ServiceIntentionsConfigEntry{ 2115 Kind: structs.ServiceIntentions, 2116 Name: "api", 2117 Sources: []*structs.SourceIntention{ 2118 {Name: "web", Action: structs.IntentionActionAllow}, 2119 }, 2120 }, 2121 }, 2122 { // set the target service as http 2123 entry: serviceDefaults("api", "http"), 2124 }, 2125 { // Should succeed if converted to L7 2126 entry: &structs.ServiceIntentionsConfigEntry{ 2127 Kind: structs.ServiceIntentions, 2128 Name: "api", 2129 Sources: []*structs.SourceIntention{ 2130 {Name: "web", Permissions: l7perms}, 2131 }, 2132 }, 2133 }, 2134 }, 2135 }, 2136 "L4 intention can upgrade to L7 when made http via proxy-defaults": { 2137 ops: []operation{ 2138 { // set the target service as tcp 2139 entry: proxyDefaults("tcp"), 2140 }, 2141 { // create an L4 intention 2142 entry: &structs.ServiceIntentionsConfigEntry{ 2143 Kind: structs.ServiceIntentions, 2144 Name: "api", 2145 Sources: []*structs.SourceIntention{ 2146 {Name: "web", Action: structs.IntentionActionAllow}, 2147 }, 2148 }, 2149 }, 2150 { // set the target service as http 2151 entry: proxyDefaults("http"), 2152 }, 2153 { // Should succeed if converted to L7 2154 entry: &structs.ServiceIntentionsConfigEntry{ 2155 Kind: structs.ServiceIntentions, 2156 Name: "api", 2157 Sources: []*structs.SourceIntention{ 2158 {Name: "web", Permissions: l7perms}, 2159 }, 2160 }, 2161 }, 2162 }, 2163 }, 2164 "L7 intention cannot have protocol downgraded to tcp via modification via service-defaults": { 2165 ops: []operation{ 2166 { // set the target service as http 2167 entry: serviceDefaults("api", "http"), 2168 }, 2169 { // create an L7 intention 2170 entry: &structs.ServiceIntentionsConfigEntry{ 2171 Kind: structs.ServiceIntentions, 2172 Name: "api", 2173 Sources: []*structs.SourceIntention{ 2174 {Name: "web", Permissions: l7perms}, 2175 }, 2176 }, 2177 }, 2178 { // setting the target service as tcp should fail 2179 entry: serviceDefaults("api", "tcp"), 2180 }, 2181 }, 2182 expectLastErr: `has protocol "tcp"`, 2183 }, 2184 "L7 intention cannot have protocol downgraded to tcp via modification via proxy-defaults": { 2185 ops: []operation{ 2186 { // set the target service as http 2187 entry: proxyDefaults("http"), 2188 }, 2189 { // create an L7 intention 2190 entry: &structs.ServiceIntentionsConfigEntry{ 2191 Kind: structs.ServiceIntentions, 2192 Name: "api", 2193 Sources: []*structs.SourceIntention{ 2194 {Name: "web", Permissions: l7perms}, 2195 }, 2196 }, 2197 }, 2198 { // setting the target service as tcp should fail 2199 entry: proxyDefaults("tcp"), 2200 }, 2201 }, 2202 expectLastErr: `has protocol "tcp"`, 2203 }, 2204 "L7 intention cannot have protocol downgraded to tcp via deletion of service-defaults": { 2205 ops: []operation{ 2206 { // set the target service as http 2207 entry: serviceDefaults("api", "http"), 2208 }, 2209 { // create an L7 intention 2210 entry: &structs.ServiceIntentionsConfigEntry{ 2211 Kind: structs.ServiceIntentions, 2212 Name: "api", 2213 Sources: []*structs.SourceIntention{ 2214 {Name: "web", Permissions: l7perms}, 2215 }, 2216 }, 2217 }, 2218 { // setting the target service as tcp should fail 2219 entry: serviceDefaults("api", "tcp"), 2220 deletion: true, 2221 }, 2222 }, 2223 expectLastErr: `has protocol "tcp"`, 2224 }, 2225 "L7 intention cannot have protocol downgraded to tcp via deletion of proxy-defaults": { 2226 ops: []operation{ 2227 { // set the target service as http 2228 entry: proxyDefaults("http"), 2229 }, 2230 { // create an L7 intention 2231 entry: &structs.ServiceIntentionsConfigEntry{ 2232 Kind: structs.ServiceIntentions, 2233 Name: "api", 2234 Sources: []*structs.SourceIntention{ 2235 {Name: "web", Permissions: l7perms}, 2236 }, 2237 }, 2238 }, 2239 { // setting the target service as tcp should fail 2240 entry: proxyDefaults("tcp"), 2241 deletion: true, 2242 }, 2243 }, 2244 expectLastErr: `has protocol "tcp"`, 2245 }, 2246 } 2247 2248 for name, tc := range cases { 2249 tc := tc 2250 t.Run(name, func(t *testing.T) { 2251 s := testStateStore(t) 2252 2253 var nextIndex = uint64(1) 2254 2255 for i, op := range tc.ops { 2256 isLast := (i == len(tc.ops)-1) 2257 2258 var err error 2259 if op.deletion { 2260 err = s.DeleteConfigEntry(nextIndex, op.entry.GetKind(), op.entry.GetName(), nil) 2261 } else { 2262 err = s.EnsureConfigEntry(nextIndex, op.entry) 2263 } 2264 2265 if isLast && tc.expectLastErr != "" { 2266 testutil.RequireErrorContains(t, err, `has protocol "tcp"`) 2267 } else { 2268 require.NoError(t, err) 2269 } 2270 } 2271 }) 2272 } 2273} 2274