1package api 2 3import ( 4 "encoding/json" 5 "testing" 6 "time" 7 8 "github.com/stretchr/testify/require" 9) 10 11func TestAPI_ConfigEntries(t *testing.T) { 12 t.Parallel() 13 c, s := makeClient(t) 14 defer s.Stop() 15 16 config_entries := c.ConfigEntries() 17 18 t.Run("Proxy Defaults", func(t *testing.T) { 19 global_proxy := &ProxyConfigEntry{ 20 Kind: ProxyDefaults, 21 Name: ProxyConfigGlobal, 22 Config: map[string]interface{}{ 23 "foo": "bar", 24 "bar": 1.0, 25 }, 26 Meta: map[string]string{ 27 "foo": "bar", 28 "gir": "zim", 29 }, 30 } 31 32 // set it 33 _, wm, err := config_entries.Set(global_proxy, nil) 34 require.NoError(t, err) 35 require.NotNil(t, wm) 36 require.NotEqual(t, 0, wm.RequestTime) 37 38 // get it 39 entry, qm, err := config_entries.Get(ProxyDefaults, ProxyConfigGlobal, nil) 40 require.NoError(t, err) 41 require.NotNil(t, qm) 42 require.NotEqual(t, 0, qm.RequestTime) 43 44 // verify it 45 readProxy, ok := entry.(*ProxyConfigEntry) 46 require.True(t, ok) 47 require.Equal(t, global_proxy.Kind, readProxy.Kind) 48 require.Equal(t, global_proxy.Name, readProxy.Name) 49 require.Equal(t, global_proxy.Config, readProxy.Config) 50 require.Equal(t, global_proxy.Meta, readProxy.Meta) 51 require.Equal(t, global_proxy.Meta, readProxy.GetMeta()) 52 53 global_proxy.Config["baz"] = true 54 // CAS update fail 55 written, _, err := config_entries.CAS(global_proxy, 0, nil) 56 require.NoError(t, err) 57 require.False(t, written) 58 59 // CAS update success 60 written, wm, err = config_entries.CAS(global_proxy, readProxy.ModifyIndex, nil) 61 require.NoError(t, err) 62 require.NotNil(t, wm) 63 require.NotEqual(t, 0, wm.RequestTime) 64 require.NoError(t, err) 65 require.True(t, written) 66 67 // Non CAS update 68 global_proxy.Config["baz"] = "baz" 69 _, wm, err = config_entries.Set(global_proxy, nil) 70 require.NoError(t, err) 71 require.NotNil(t, wm) 72 require.NotEqual(t, 0, wm.RequestTime) 73 74 // list it 75 entries, qm, err := config_entries.List(ProxyDefaults, nil) 76 require.NoError(t, err) 77 require.NotNil(t, qm) 78 require.NotEqual(t, 0, qm.RequestTime) 79 require.Len(t, entries, 1) 80 readProxy, ok = entries[0].(*ProxyConfigEntry) 81 require.True(t, ok) 82 require.Equal(t, global_proxy.Kind, readProxy.Kind) 83 require.Equal(t, global_proxy.Name, readProxy.Name) 84 require.Equal(t, global_proxy.Config, readProxy.Config) 85 86 // delete it 87 wm, err = config_entries.Delete(ProxyDefaults, ProxyConfigGlobal, nil) 88 require.NoError(t, err) 89 require.NotNil(t, wm) 90 require.NotEqual(t, 0, wm.RequestTime) 91 92 _, _, err = config_entries.Get(ProxyDefaults, ProxyConfigGlobal, nil) 93 require.Error(t, err) 94 }) 95 96 t.Run("Service Defaults", func(t *testing.T) { 97 service := &ServiceConfigEntry{ 98 Kind: ServiceDefaults, 99 Name: "foo", 100 Protocol: "udp", 101 Meta: map[string]string{ 102 "foo": "bar", 103 "gir": "zim", 104 }, 105 } 106 107 service2 := &ServiceConfigEntry{ 108 Kind: ServiceDefaults, 109 Name: "bar", 110 Protocol: "tcp", 111 } 112 113 // set it 114 _, wm, err := config_entries.Set(service, nil) 115 require.NoError(t, err) 116 require.NotNil(t, wm) 117 require.NotEqual(t, 0, wm.RequestTime) 118 119 // also set the second one 120 _, wm, err = config_entries.Set(service2, nil) 121 require.NoError(t, err) 122 require.NotNil(t, wm) 123 require.NotEqual(t, 0, wm.RequestTime) 124 125 // get it 126 entry, qm, err := config_entries.Get(ServiceDefaults, "foo", nil) 127 require.NoError(t, err) 128 require.NotNil(t, qm) 129 require.NotEqual(t, 0, qm.RequestTime) 130 131 // verify it 132 readService, ok := entry.(*ServiceConfigEntry) 133 require.True(t, ok) 134 require.Equal(t, service.Kind, readService.Kind) 135 require.Equal(t, service.Name, readService.Name) 136 require.Equal(t, service.Protocol, readService.Protocol) 137 require.Equal(t, service.Meta, readService.Meta) 138 require.Equal(t, service.Meta, readService.GetMeta()) 139 140 // update it 141 service.Protocol = "tcp" 142 143 // CAS fail 144 written, _, err := config_entries.CAS(service, 0, nil) 145 require.NoError(t, err) 146 require.False(t, written) 147 148 // CAS success 149 written, wm, err = config_entries.CAS(service, readService.ModifyIndex, nil) 150 require.NoError(t, err) 151 require.NotNil(t, wm) 152 require.NotEqual(t, 0, wm.RequestTime) 153 require.True(t, written) 154 155 // update no cas 156 service.Protocol = "http" 157 158 _, wm, err = config_entries.Set(service, nil) 159 require.NoError(t, err) 160 require.NotNil(t, wm) 161 require.NotEqual(t, 0, wm.RequestTime) 162 163 // list them 164 entries, qm, err := config_entries.List(ServiceDefaults, nil) 165 require.NoError(t, err) 166 require.NotNil(t, qm) 167 require.NotEqual(t, 0, qm.RequestTime) 168 require.Len(t, entries, 2) 169 170 for _, entry = range entries { 171 switch entry.GetName() { 172 case "foo": 173 // this also verifies that the update value was persisted and 174 // the updated values are seen 175 readService, ok = entry.(*ServiceConfigEntry) 176 require.True(t, ok) 177 require.Equal(t, service.Kind, readService.Kind) 178 require.Equal(t, service.Name, readService.Name) 179 require.Equal(t, service.Protocol, readService.Protocol) 180 case "bar": 181 readService, ok = entry.(*ServiceConfigEntry) 182 require.True(t, ok) 183 require.Equal(t, service2.Kind, readService.Kind) 184 require.Equal(t, service2.Name, readService.Name) 185 require.Equal(t, service2.Protocol, readService.Protocol) 186 } 187 } 188 189 // delete it 190 wm, err = config_entries.Delete(ServiceDefaults, "foo", nil) 191 require.NoError(t, err) 192 require.NotNil(t, wm) 193 require.NotEqual(t, 0, wm.RequestTime) 194 195 // verify deletion 196 _, _, err = config_entries.Get(ServiceDefaults, "foo", nil) 197 require.Error(t, err) 198 }) 199 200 t.Run("Mesh", func(t *testing.T) { 201 mesh := &MeshConfigEntry{ 202 TransparentProxy: TransparentProxyMeshConfig{MeshDestinationsOnly: true}, 203 Meta: map[string]string{ 204 "foo": "bar", 205 "gir": "zim", 206 }, 207 Namespace: defaultNamespace, 208 } 209 ce := c.ConfigEntries() 210 211 runStep(t, "set and get", func(t *testing.T) { 212 _, wm, err := ce.Set(mesh, nil) 213 require.NoError(t, err) 214 require.NotNil(t, wm) 215 require.NotEqual(t, 0, wm.RequestTime) 216 217 entry, qm, err := ce.Get(MeshConfig, MeshConfigMesh, nil) 218 require.NoError(t, err) 219 require.NotNil(t, qm) 220 require.NotEqual(t, 0, qm.RequestTime) 221 222 result, ok := entry.(*MeshConfigEntry) 223 require.True(t, ok) 224 225 // ignore indexes 226 result.CreateIndex = 0 227 result.ModifyIndex = 0 228 require.Equal(t, mesh, result) 229 }) 230 231 runStep(t, "list", func(t *testing.T) { 232 entries, qm, err := ce.List(MeshConfig, nil) 233 require.NoError(t, err) 234 require.NotNil(t, qm) 235 require.NotEqual(t, 0, qm.RequestTime) 236 require.Len(t, entries, 1) 237 }) 238 239 runStep(t, "delete", func(t *testing.T) { 240 wm, err := ce.Delete(MeshConfig, MeshConfigMesh, nil) 241 require.NoError(t, err) 242 require.NotNil(t, wm) 243 require.NotEqual(t, 0, wm.RequestTime) 244 245 // verify deletion 246 _, _, err = ce.Get(MeshConfig, MeshConfigMesh, nil) 247 require.Error(t, err) 248 }) 249 }) 250 251} 252 253func runStep(t *testing.T, name string, fn func(t *testing.T)) { 254 t.Helper() 255 if !t.Run(name, fn) { 256 t.FailNow() 257 } 258} 259 260func TestDecodeConfigEntry(t *testing.T) { 261 t.Parallel() 262 263 for _, tc := range []struct { 264 name string 265 body string 266 expect ConfigEntry 267 expectErr string 268 }{ 269 { 270 name: "expose-paths: kitchen sink proxy", 271 body: ` 272 { 273 "Kind": "proxy-defaults", 274 "Name": "global", 275 "Expose": { 276 "Checks": true, 277 "Paths": [ 278 { 279 "LocalPathPort": 8080, 280 "ListenerPort": 21500, 281 "Path": "/healthz", 282 "Protocol": "http2" 283 } 284 ] 285 } 286 } 287 `, 288 expect: &ProxyConfigEntry{ 289 Kind: "proxy-defaults", 290 Name: "global", 291 Expose: ExposeConfig{ 292 Checks: true, 293 Paths: []ExposePath{ 294 { 295 LocalPathPort: 8080, 296 ListenerPort: 21500, 297 Path: "/healthz", 298 Protocol: "http2", 299 }, 300 }, 301 }, 302 }, 303 }, 304 { 305 name: "expose-paths: kitchen sink service default", 306 body: ` 307 { 308 "Kind": "service-defaults", 309 "Name": "global", 310 "Expose": { 311 "Checks": true, 312 "Paths": [ 313 { 314 "LocalPathPort": 8080, 315 "ListenerPort": 21500, 316 "Path": "/healthz", 317 "Protocol": "http2" 318 } 319 ] 320 } 321 } 322 `, 323 expect: &ServiceConfigEntry{ 324 Kind: "service-defaults", 325 Name: "global", 326 Expose: ExposeConfig{ 327 Checks: true, 328 Paths: []ExposePath{ 329 { 330 LocalPathPort: 8080, 331 ListenerPort: 21500, 332 Path: "/healthz", 333 Protocol: "http2", 334 }, 335 }, 336 }, 337 }, 338 }, 339 { 340 name: "proxy-defaults", 341 body: ` 342 { 343 "Kind": "proxy-defaults", 344 "Name": "main", 345 "Meta" : { 346 "foo": "bar", 347 "gir": "zim" 348 }, 349 "Config": { 350 "foo": 19, 351 "bar": "abc", 352 "moreconfig": { 353 "moar": "config" 354 } 355 }, 356 "MeshGateway": { 357 "Mode": "remote" 358 }, 359 "Mode": "transparent", 360 "TransparentProxy": { 361 "OutboundListenerPort": 808, 362 "DialedDirectly": true 363 } 364 } 365 `, 366 expect: &ProxyConfigEntry{ 367 Kind: "proxy-defaults", 368 Name: "main", 369 Meta: map[string]string{ 370 "foo": "bar", 371 "gir": "zim", 372 }, 373 Config: map[string]interface{}{ 374 "foo": float64(19), 375 "bar": "abc", 376 "moreconfig": map[string]interface{}{ 377 "moar": "config", 378 }, 379 }, 380 MeshGateway: MeshGatewayConfig{ 381 Mode: MeshGatewayModeRemote, 382 }, 383 Mode: ProxyModeTransparent, 384 TransparentProxy: &TransparentProxyConfig{ 385 OutboundListenerPort: 808, 386 DialedDirectly: true, 387 }, 388 }, 389 }, 390 { 391 name: "service-defaults", 392 body: ` 393 { 394 "Kind": "service-defaults", 395 "Name": "main", 396 "Meta" : { 397 "foo": "bar", 398 "gir": "zim" 399 }, 400 "Protocol": "http", 401 "ExternalSNI": "abc-123", 402 "MeshGateway": { 403 "Mode": "remote" 404 }, 405 "Mode": "transparent", 406 "TransparentProxy": { 407 "OutboundListenerPort": 808, 408 "DialedDirectly": true 409 }, 410 "UpstreamConfig": { 411 "Overrides": [ 412 { 413 "Name": "redis", 414 "PassiveHealthCheck": { 415 "MaxFailures": 3, 416 "Interval": "2s" 417 } 418 }, 419 { 420 "Name": "finance--billing", 421 "MeshGateway": { 422 "Mode": "remote" 423 } 424 } 425 ], 426 "Defaults": { 427 "EnvoyClusterJSON": "zip", 428 "EnvoyListenerJSON": "zop", 429 "ConnectTimeoutMs": 5000, 430 "Protocol": "http", 431 "Limits": { 432 "MaxConnections": 3, 433 "MaxPendingRequests": 4, 434 "MaxConcurrentRequests": 5 435 }, 436 "PassiveHealthCheck": { 437 "MaxFailures": 5, 438 "Interval": "4s" 439 } 440 } 441 } 442 } 443 `, 444 expect: &ServiceConfigEntry{ 445 Kind: "service-defaults", 446 Name: "main", 447 Meta: map[string]string{ 448 "foo": "bar", 449 "gir": "zim", 450 }, 451 Protocol: "http", 452 ExternalSNI: "abc-123", 453 MeshGateway: MeshGatewayConfig{ 454 Mode: MeshGatewayModeRemote, 455 }, 456 Mode: ProxyModeTransparent, 457 TransparentProxy: &TransparentProxyConfig{ 458 OutboundListenerPort: 808, 459 DialedDirectly: true, 460 }, 461 UpstreamConfig: &UpstreamConfiguration{ 462 Overrides: []*UpstreamConfig{ 463 { 464 Name: "redis", 465 PassiveHealthCheck: &PassiveHealthCheck{ 466 MaxFailures: 3, 467 Interval: 2 * time.Second, 468 }, 469 }, 470 { 471 Name: "finance--billing", 472 MeshGateway: MeshGatewayConfig{Mode: "remote"}, 473 }, 474 }, 475 Defaults: &UpstreamConfig{ 476 EnvoyClusterJSON: "zip", 477 EnvoyListenerJSON: "zop", 478 Protocol: "http", 479 ConnectTimeoutMs: 5000, 480 Limits: &UpstreamLimits{ 481 MaxConnections: intPointer(3), 482 MaxPendingRequests: intPointer(4), 483 MaxConcurrentRequests: intPointer(5), 484 }, 485 PassiveHealthCheck: &PassiveHealthCheck{ 486 MaxFailures: 5, 487 Interval: 4 * time.Second, 488 }, 489 }, 490 }, 491 }, 492 }, 493 { 494 name: "service-router: kitchen sink", 495 body: ` 496 { 497 "Kind": "service-router", 498 "Name": "main", 499 "Meta" : { 500 "foo": "bar", 501 "gir": "zim" 502 }, 503 "Routes": [ 504 { 505 "Match": { 506 "HTTP": { 507 "PathExact": "/foo", 508 "Header": [ 509 { 510 "Name": "debug1", 511 "Present": true 512 }, 513 { 514 "Name": "debug2", 515 "Present": false, 516 "Invert": true 517 }, 518 { 519 "Name": "debug3", 520 "Exact": "1" 521 }, 522 { 523 "Name": "debug4", 524 "Prefix": "aaa" 525 }, 526 { 527 "Name": "debug5", 528 "Suffix": "bbb" 529 }, 530 { 531 "Name": "debug6", 532 "Regex": "a.*z" 533 } 534 ] 535 } 536 }, 537 "Destination": { 538 "Service": "carrot", 539 "ServiceSubset": "kale", 540 "Namespace": "leek", 541 "PrefixRewrite": "/alternate", 542 "RequestTimeout": "99s", 543 "NumRetries": 12345, 544 "RetryOnConnectFailure": true, 545 "RetryOnStatusCodes": [401, 209] 546 } 547 }, 548 { 549 "Match": { 550 "HTTP": { 551 "PathPrefix": "/foo", 552 "Methods": [ "GET", "DELETE" ], 553 "QueryParam": [ 554 { 555 "Name": "hack1", 556 "Present": true 557 }, 558 { 559 "Name": "hack2", 560 "Exact": "1" 561 }, 562 { 563 "Name": "hack3", 564 "Regex": "a.*z" 565 } 566 ] 567 } 568 } 569 }, 570 { 571 "Match": { 572 "HTTP": { 573 "PathRegex": "/foo" 574 } 575 } 576 } 577 ] 578 } 579 `, 580 expect: &ServiceRouterConfigEntry{ 581 Kind: "service-router", 582 Name: "main", 583 Meta: map[string]string{ 584 "foo": "bar", 585 "gir": "zim", 586 }, 587 Routes: []ServiceRoute{ 588 { 589 Match: &ServiceRouteMatch{ 590 HTTP: &ServiceRouteHTTPMatch{ 591 PathExact: "/foo", 592 Header: []ServiceRouteHTTPMatchHeader{ 593 { 594 Name: "debug1", 595 Present: true, 596 }, 597 { 598 Name: "debug2", 599 Present: false, 600 Invert: true, 601 }, 602 { 603 Name: "debug3", 604 Exact: "1", 605 }, 606 { 607 Name: "debug4", 608 Prefix: "aaa", 609 }, 610 { 611 Name: "debug5", 612 Suffix: "bbb", 613 }, 614 { 615 Name: "debug6", 616 Regex: "a.*z", 617 }, 618 }, 619 }, 620 }, 621 Destination: &ServiceRouteDestination{ 622 Service: "carrot", 623 ServiceSubset: "kale", 624 Namespace: "leek", 625 PrefixRewrite: "/alternate", 626 RequestTimeout: 99 * time.Second, 627 NumRetries: 12345, 628 RetryOnConnectFailure: true, 629 RetryOnStatusCodes: []uint32{401, 209}, 630 }, 631 }, 632 { 633 Match: &ServiceRouteMatch{ 634 HTTP: &ServiceRouteHTTPMatch{ 635 PathPrefix: "/foo", 636 Methods: []string{"GET", "DELETE"}, 637 QueryParam: []ServiceRouteHTTPMatchQueryParam{ 638 { 639 Name: "hack1", 640 Present: true, 641 }, 642 { 643 Name: "hack2", 644 Exact: "1", 645 }, 646 { 647 Name: "hack3", 648 Regex: "a.*z", 649 }, 650 }, 651 }, 652 }, 653 }, 654 { 655 Match: &ServiceRouteMatch{ 656 HTTP: &ServiceRouteHTTPMatch{ 657 PathRegex: "/foo", 658 }, 659 }, 660 }, 661 }, 662 }, 663 }, 664 { 665 name: "service-splitter: kitchen sink", 666 body: ` 667 { 668 "Kind": "service-splitter", 669 "Name": "main", 670 "Meta" : { 671 "foo": "bar", 672 "gir": "zim" 673 }, 674 "Splits": [ 675 { 676 "Weight": 99.1, 677 "ServiceSubset": "v1" 678 }, 679 { 680 "Weight": 0.9, 681 "Service": "other", 682 "Namespace": "alt" 683 } 684 ] 685 } 686 `, 687 expect: &ServiceSplitterConfigEntry{ 688 Kind: ServiceSplitter, 689 Name: "main", 690 Meta: map[string]string{ 691 "foo": "bar", 692 "gir": "zim", 693 }, 694 Splits: []ServiceSplit{ 695 { 696 Weight: 99.1, 697 ServiceSubset: "v1", 698 }, 699 { 700 Weight: 0.9, 701 Service: "other", 702 Namespace: "alt", 703 }, 704 }, 705 }, 706 }, 707 { 708 name: "service-resolver: subsets with failover", 709 body: ` 710 { 711 "Kind": "service-resolver", 712 "Name": "main", 713 "Meta" : { 714 "foo": "bar", 715 "gir": "zim" 716 }, 717 "DefaultSubset": "v1", 718 "ConnectTimeout": "15s", 719 "Subsets": { 720 "v1": { 721 "Filter": "Service.Meta.version == v1" 722 }, 723 "v2": { 724 "Filter": "Service.Meta.version == v2", 725 "OnlyPassing": true 726 } 727 }, 728 "Failover": { 729 "v2": { 730 "Service": "failcopy", 731 "ServiceSubset": "sure", 732 "Namespace": "neighbor", 733 "Datacenters": ["dc5", "dc14"] 734 }, 735 "*": { 736 "Datacenters": ["dc7"] 737 } 738 } 739 }`, 740 expect: &ServiceResolverConfigEntry{ 741 Kind: "service-resolver", 742 Name: "main", 743 Meta: map[string]string{ 744 "foo": "bar", 745 "gir": "zim", 746 }, 747 DefaultSubset: "v1", 748 ConnectTimeout: 15 * time.Second, 749 Subsets: map[string]ServiceResolverSubset{ 750 "v1": { 751 Filter: "Service.Meta.version == v1", 752 }, 753 "v2": { 754 Filter: "Service.Meta.version == v2", 755 OnlyPassing: true, 756 }, 757 }, 758 Failover: map[string]ServiceResolverFailover{ 759 "v2": { 760 Service: "failcopy", 761 ServiceSubset: "sure", 762 Namespace: "neighbor", 763 Datacenters: []string{"dc5", "dc14"}, 764 }, 765 "*": { 766 Datacenters: []string{"dc7"}, 767 }, 768 }, 769 }, 770 }, 771 { 772 name: "service-resolver: redirect", 773 body: ` 774 { 775 "Kind": "service-resolver", 776 "Name": "main", 777 "Redirect": { 778 "Service": "other", 779 "ServiceSubset": "backup", 780 "Namespace": "alt", 781 "Datacenter": "dc9" 782 } 783 } 784 `, 785 expect: &ServiceResolverConfigEntry{ 786 Kind: "service-resolver", 787 Name: "main", 788 Redirect: &ServiceResolverRedirect{ 789 Service: "other", 790 ServiceSubset: "backup", 791 Namespace: "alt", 792 Datacenter: "dc9", 793 }, 794 }, 795 }, 796 { 797 name: "service-resolver: default", 798 body: ` 799 { 800 "Kind": "service-resolver", 801 "Name": "main" 802 } 803 `, 804 expect: &ServiceResolverConfigEntry{ 805 Kind: "service-resolver", 806 Name: "main", 807 }, 808 }, 809 { 810 name: "service-resolver: envoy hash lb kitchen sink", 811 body: ` 812 { 813 "Kind": "service-resolver", 814 "Name": "main", 815 "LoadBalancer": { 816 "Policy": "ring_hash", 817 "RingHashConfig": { 818 "MinimumRingSize": 1, 819 "MaximumRingSize": 2 820 }, 821 "HashPolicies": [ 822 { 823 "Field": "cookie", 824 "FieldValue": "good-cookie", 825 "CookieConfig": { 826 "TTL": "1s", 827 "Path": "/oven" 828 }, 829 "Terminal": true 830 }, 831 { 832 "Field": "cookie", 833 "FieldValue": "less-good-cookie", 834 "CookieConfig": { 835 "Session": true, 836 "Path": "/toaster" 837 }, 838 "Terminal": true 839 }, 840 { 841 "Field": "header", 842 "FieldValue": "x-user-id" 843 }, 844 { 845 "SourceIP": true 846 } 847 ] 848 } 849 } 850 `, 851 expect: &ServiceResolverConfigEntry{ 852 Kind: "service-resolver", 853 Name: "main", 854 LoadBalancer: &LoadBalancer{ 855 Policy: "ring_hash", 856 RingHashConfig: &RingHashConfig{ 857 MinimumRingSize: 1, 858 MaximumRingSize: 2, 859 }, 860 HashPolicies: []HashPolicy{ 861 { 862 Field: "cookie", 863 FieldValue: "good-cookie", 864 CookieConfig: &CookieConfig{ 865 TTL: 1 * time.Second, 866 Path: "/oven", 867 }, 868 Terminal: true, 869 }, 870 { 871 Field: "cookie", 872 FieldValue: "less-good-cookie", 873 CookieConfig: &CookieConfig{ 874 Session: true, 875 Path: "/toaster", 876 }, 877 Terminal: true, 878 }, 879 { 880 Field: "header", 881 FieldValue: "x-user-id", 882 }, 883 { 884 SourceIP: true, 885 }, 886 }, 887 }, 888 }, 889 }, 890 { 891 name: "service-resolver: envoy least request kitchen sink", 892 body: ` 893 { 894 "Kind": "service-resolver", 895 "Name": "main", 896 "LoadBalancer": { 897 "Policy": "least_request", 898 "LeastRequestConfig": { 899 "ChoiceCount": 2 900 } 901 } 902 } 903 `, 904 expect: &ServiceResolverConfigEntry{ 905 Kind: "service-resolver", 906 Name: "main", 907 LoadBalancer: &LoadBalancer{ 908 Policy: "least_request", 909 LeastRequestConfig: &LeastRequestConfig{ 910 ChoiceCount: 2, 911 }, 912 }, 913 }, 914 }, 915 { 916 name: "ingress-gateway", 917 body: ` 918 { 919 "Kind": "ingress-gateway", 920 "Name": "ingress-web", 921 "Meta" : { 922 "foo": "bar", 923 "gir": "zim" 924 }, 925 "Tls": { 926 "Enabled": true 927 }, 928 "Listeners": [ 929 { 930 "Port": 8080, 931 "Protocol": "http", 932 "Services": [ 933 { 934 "Name": "web", 935 "Namespace": "foo" 936 }, 937 { 938 "Name": "db" 939 } 940 ] 941 }, 942 { 943 "Port": 9999, 944 "Protocol": "tcp", 945 "Services": [ 946 { 947 "Name": "mysql" 948 } 949 ] 950 } 951 ] 952 } 953 `, 954 expect: &IngressGatewayConfigEntry{ 955 Kind: "ingress-gateway", 956 Name: "ingress-web", 957 Meta: map[string]string{ 958 "foo": "bar", 959 "gir": "zim", 960 }, 961 TLS: GatewayTLSConfig{ 962 Enabled: true, 963 }, 964 Listeners: []IngressListener{ 965 { 966 Port: 8080, 967 Protocol: "http", 968 Services: []IngressService{ 969 { 970 Name: "web", 971 Namespace: "foo", 972 }, 973 { 974 Name: "db", 975 }, 976 }, 977 }, 978 { 979 Port: 9999, 980 Protocol: "tcp", 981 Services: []IngressService{ 982 { 983 Name: "mysql", 984 }, 985 }, 986 }, 987 }, 988 }, 989 }, 990 { 991 name: "terminating-gateway", 992 body: ` 993 { 994 "Kind": "terminating-gateway", 995 "Name": "terminating-west", 996 "Meta" : { 997 "foo": "bar", 998 "gir": "zim" 999 }, 1000 "Services": [ 1001 { 1002 "Namespace": "foo", 1003 "Name": "web", 1004 "CAFile": "/etc/ca.pem", 1005 "CertFile": "/etc/cert.pem", 1006 "KeyFile": "/etc/tls.key", 1007 "SNI": "mydomain" 1008 }, 1009 { 1010 "Name": "api" 1011 }, 1012 { 1013 "Namespace": "bar", 1014 "Name": "*" 1015 } 1016 ] 1017 }`, 1018 expect: &TerminatingGatewayConfigEntry{ 1019 Kind: "terminating-gateway", 1020 Name: "terminating-west", 1021 Meta: map[string]string{ 1022 "foo": "bar", 1023 "gir": "zim", 1024 }, 1025 Services: []LinkedService{ 1026 { 1027 Namespace: "foo", 1028 Name: "web", 1029 CAFile: "/etc/ca.pem", 1030 CertFile: "/etc/cert.pem", 1031 KeyFile: "/etc/tls.key", 1032 SNI: "mydomain", 1033 }, 1034 { 1035 Name: "api", 1036 }, 1037 { 1038 Namespace: "bar", 1039 Name: "*", 1040 }, 1041 }, 1042 }, 1043 }, 1044 { 1045 name: "service-intentions: kitchen sink", 1046 body: ` 1047 { 1048 "Kind": "service-intentions", 1049 "Name": "web", 1050 "Meta" : { 1051 "foo": "bar", 1052 "gir": "zim" 1053 }, 1054 "Sources": [ 1055 { 1056 "Name": "foo", 1057 "Action": "deny", 1058 "Type": "consul", 1059 "Description": "foo desc" 1060 }, 1061 { 1062 "Name": "bar", 1063 "Action": "allow", 1064 "Description": "bar desc" 1065 }, 1066 { 1067 "Name": "l7", 1068 "Permissions": [ 1069 { 1070 "Action": "deny", 1071 "HTTP": { 1072 "PathExact": "/admin", 1073 "Header": [ 1074 { 1075 "Name": "hdr-present", 1076 "Present": true 1077 }, 1078 { 1079 "Name": "hdr-exact", 1080 "Exact": "exact" 1081 }, 1082 { 1083 "Name": "hdr-prefix", 1084 "Prefix": "prefix" 1085 }, 1086 { 1087 "Name": "hdr-suffix", 1088 "Suffix": "suffix" 1089 }, 1090 { 1091 "Name": "hdr-regex", 1092 "Regex": "regex" 1093 }, 1094 { 1095 "Name": "hdr-absent", 1096 "Present": true, 1097 "Invert": true 1098 } 1099 ] 1100 } 1101 }, 1102 { 1103 "Action": "allow", 1104 "HTTP": { 1105 "PathPrefix": "/v3/" 1106 } 1107 }, 1108 { 1109 "Action": "allow", 1110 "HTTP": { 1111 "PathRegex": "/v[12]/.*", 1112 "Methods": [ 1113 "GET", 1114 "POST" 1115 ] 1116 } 1117 } 1118 ] 1119 }, 1120 { 1121 "Name": "*", 1122 "Action": "deny", 1123 "Description": "wild desc" 1124 } 1125 ] 1126 } 1127 `, 1128 expect: &ServiceIntentionsConfigEntry{ 1129 Kind: "service-intentions", 1130 Name: "web", 1131 Meta: map[string]string{ 1132 "foo": "bar", 1133 "gir": "zim", 1134 }, 1135 Sources: []*SourceIntention{ 1136 { 1137 Name: "foo", 1138 Action: "deny", 1139 Type: "consul", 1140 Description: "foo desc", 1141 }, 1142 { 1143 Name: "bar", 1144 Action: "allow", 1145 Description: "bar desc", 1146 }, 1147 { 1148 Name: "l7", 1149 Permissions: []*IntentionPermission{ 1150 { 1151 Action: "deny", 1152 HTTP: &IntentionHTTPPermission{ 1153 PathExact: "/admin", 1154 Header: []IntentionHTTPHeaderPermission{ 1155 { 1156 Name: "hdr-present", 1157 Present: true, 1158 }, 1159 { 1160 Name: "hdr-exact", 1161 Exact: "exact", 1162 }, 1163 { 1164 Name: "hdr-prefix", 1165 Prefix: "prefix", 1166 }, 1167 { 1168 Name: "hdr-suffix", 1169 Suffix: "suffix", 1170 }, 1171 { 1172 Name: "hdr-regex", 1173 Regex: "regex", 1174 }, 1175 { 1176 Name: "hdr-absent", 1177 Present: true, 1178 Invert: true, 1179 }, 1180 }, 1181 }, 1182 }, 1183 { 1184 Action: "allow", 1185 HTTP: &IntentionHTTPPermission{ 1186 PathPrefix: "/v3/", 1187 }, 1188 }, 1189 { 1190 Action: "allow", 1191 HTTP: &IntentionHTTPPermission{ 1192 PathRegex: "/v[12]/.*", 1193 Methods: []string{"GET", "POST"}, 1194 }, 1195 }, 1196 }, 1197 }, 1198 { 1199 Name: "*", 1200 Action: "deny", 1201 Description: "wild desc", 1202 }, 1203 }, 1204 }, 1205 }, 1206 { 1207 name: "mesh", 1208 body: ` 1209 { 1210 "Kind": "mesh", 1211 "Meta" : { 1212 "foo": "bar", 1213 "gir": "zim" 1214 }, 1215 "TransparentProxy": { 1216 "MeshDestinationsOnly": true 1217 } 1218 } 1219 `, 1220 expect: &MeshConfigEntry{ 1221 Meta: map[string]string{ 1222 "foo": "bar", 1223 "gir": "zim", 1224 }, 1225 TransparentProxy: TransparentProxyMeshConfig{ 1226 MeshDestinationsOnly: true, 1227 }, 1228 }, 1229 }, 1230 } { 1231 tc := tc 1232 1233 t.Run(tc.name+": DecodeConfigEntry", func(t *testing.T) { 1234 var raw map[string]interface{} 1235 require.NoError(t, json.Unmarshal([]byte(tc.body), &raw)) 1236 1237 got, err := DecodeConfigEntry(raw) 1238 require.NoError(t, err) 1239 require.Equal(t, tc.expect, got) 1240 }) 1241 1242 t.Run(tc.name+": DecodeConfigEntryFromJSON", func(t *testing.T) { 1243 got, err := DecodeConfigEntryFromJSON([]byte(tc.body)) 1244 require.NoError(t, err) 1245 require.Equal(t, tc.expect, got) 1246 }) 1247 1248 t.Run(tc.name+": DecodeConfigEntrySlice", func(t *testing.T) { 1249 var raw []map[string]interface{} 1250 require.NoError(t, json.Unmarshal([]byte("["+tc.body+"]"), &raw)) 1251 1252 got, err := decodeConfigEntrySlice(raw) 1253 require.NoError(t, err) 1254 require.Len(t, got, 1) 1255 require.Equal(t, tc.expect, got[0]) 1256 }) 1257 } 1258} 1259 1260func intPointer(v int) *int { 1261 return &v 1262} 1263