github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/nomad/structs/services_test.go (about) 1 package structs 2 3 import ( 4 "testing" 5 "time" 6 7 "github.com/hashicorp/nomad/helper" 8 "github.com/stretchr/testify/require" 9 ) 10 11 func TestServiceCheck_Hash(t *testing.T) { 12 t.Parallel() 13 14 original := &ServiceCheck{ 15 Name: "check", 16 SuccessBeforePassing: 3, 17 FailuresBeforeCritical: 4, 18 } 19 20 type sc = ServiceCheck 21 type tweaker = func(check *sc) 22 23 hash := func(c *sc) string { 24 return c.Hash("ServiceID") 25 } 26 27 t.Run("reflexive", func(t *testing.T) { 28 require.Equal(t, hash(original), hash(original)) 29 }) 30 31 // these tests use tweaker to modify 1 field and make the false assertion 32 // on comparing the resulting hash output 33 34 try := func(t *testing.T, tweak tweaker) { 35 originalHash := hash(original) 36 modifiable := original.Copy() 37 tweak(modifiable) 38 tweakedHash := hash(modifiable) 39 require.NotEqual(t, originalHash, tweakedHash) 40 } 41 42 t.Run("name", func(t *testing.T) { 43 try(t, func(s *sc) { s.Name = "newName" }) 44 }) 45 46 t.Run("success_before_passing", func(t *testing.T) { 47 try(t, func(s *sc) { s.SuccessBeforePassing = 99 }) 48 }) 49 50 t.Run("failures_before_critical", func(t *testing.T) { 51 try(t, func(s *sc) { s.FailuresBeforeCritical = 99 }) 52 }) 53 } 54 55 func TestServiceCheck_validate_PassingTypes(t *testing.T) { 56 t.Parallel() 57 58 t.Run("valid", func(t *testing.T) { 59 for _, checkType := range []string{"tcp", "http", "grpc"} { 60 err := (&ServiceCheck{ 61 Name: "check", 62 Type: checkType, 63 Path: "/path", 64 Interval: 1 * time.Second, 65 Timeout: 2 * time.Second, 66 SuccessBeforePassing: 3, 67 }).validate() 68 require.NoError(t, err) 69 } 70 }) 71 72 t.Run("invalid", func(t *testing.T) { 73 err := (&ServiceCheck{ 74 Name: "check", 75 Type: "script", 76 Command: "/nothing", 77 Interval: 1 * time.Second, 78 Timeout: 2 * time.Second, 79 SuccessBeforePassing: 3, 80 }).validate() 81 require.EqualError(t, err, `success_before_passing not supported for check of type "script"`) 82 }) 83 } 84 85 func TestServiceCheck_validate_FailingTypes(t *testing.T) { 86 t.Parallel() 87 88 t.Run("valid", func(t *testing.T) { 89 for _, checkType := range []string{"tcp", "http", "grpc"} { 90 err := (&ServiceCheck{ 91 Name: "check", 92 Type: checkType, 93 Path: "/path", 94 Interval: 1 * time.Second, 95 Timeout: 2 * time.Second, 96 FailuresBeforeCritical: 3, 97 }).validate() 98 require.NoError(t, err) 99 } 100 }) 101 102 t.Run("invalid", func(t *testing.T) { 103 err := (&ServiceCheck{ 104 Name: "check", 105 Type: "script", 106 Command: "/nothing", 107 Interval: 1 * time.Second, 108 Timeout: 2 * time.Second, 109 SuccessBeforePassing: 0, 110 FailuresBeforeCritical: 3, 111 }).validate() 112 require.EqualError(t, err, `failures_before_critical not supported for check of type "script"`) 113 }) 114 } 115 116 func TestServiceCheck_validate_PassFailZero_on_scripts(t *testing.T) { 117 t.Parallel() 118 119 t.Run("invalid", func(t *testing.T) { 120 err := (&ServiceCheck{ 121 Name: "check", 122 Type: "script", 123 Command: "/nothing", 124 Interval: 1 * time.Second, 125 Timeout: 2 * time.Second, 126 SuccessBeforePassing: 0, // script checks should still pass validation 127 FailuresBeforeCritical: 0, // script checks should still pass validation 128 }).validate() 129 require.NoError(t, err) 130 }) 131 } 132 133 func TestServiceCheck_validate_OnUpdate_CheckRestart_Conflict(t *testing.T) { 134 t.Parallel() 135 136 t.Run("invalid", func(t *testing.T) { 137 err := (&ServiceCheck{ 138 Name: "check", 139 Type: "script", 140 Command: "/nothing", 141 Interval: 1 * time.Second, 142 Timeout: 2 * time.Second, 143 CheckRestart: &CheckRestart{ 144 IgnoreWarnings: false, 145 Limit: 3, 146 Grace: 5 * time.Second, 147 }, 148 OnUpdate: "ignore_warnings", 149 }).validate() 150 require.EqualError(t, err, `on_update value "ignore_warnings" not supported with check_restart ignore_warnings value "false"`) 151 }) 152 153 t.Run("invalid", func(t *testing.T) { 154 err := (&ServiceCheck{ 155 Name: "check", 156 Type: "script", 157 Command: "/nothing", 158 Interval: 1 * time.Second, 159 Timeout: 2 * time.Second, 160 CheckRestart: &CheckRestart{ 161 IgnoreWarnings: false, 162 Limit: 3, 163 Grace: 5 * time.Second, 164 }, 165 OnUpdate: "ignore", 166 }).validate() 167 require.EqualError(t, err, `on_update value "ignore" is not compatible with check_restart`) 168 }) 169 170 t.Run("valid", func(t *testing.T) { 171 err := (&ServiceCheck{ 172 Name: "check", 173 Type: "script", 174 Command: "/nothing", 175 Interval: 1 * time.Second, 176 Timeout: 2 * time.Second, 177 CheckRestart: &CheckRestart{ 178 IgnoreWarnings: true, 179 Limit: 3, 180 Grace: 5 * time.Second, 181 }, 182 OnUpdate: "ignore_warnings", 183 }).validate() 184 require.NoError(t, err) 185 }) 186 } 187 188 func TestService_Hash(t *testing.T) { 189 t.Parallel() 190 191 original := &Service{ 192 Name: "myService", 193 PortLabel: "portLabel", 194 // AddressMode: "bridge", // not hashed (used internally by Nomad) 195 Tags: []string{"original", "tags"}, 196 CanaryTags: []string{"canary", "tags"}, 197 // Checks: nil, // not hashed (managed independently) 198 Connect: &ConsulConnect{ 199 // Native: false, // not hashed 200 SidecarService: &ConsulSidecarService{ 201 Tags: []string{"original", "sidecar", "tags"}, 202 Port: "9000", 203 Proxy: &ConsulProxy{ 204 LocalServiceAddress: "127.0.0.1", 205 LocalServicePort: 24000, 206 Config: map[string]interface{}{"foo": "bar"}, 207 Upstreams: []ConsulUpstream{{ 208 DestinationName: "upstream1", 209 LocalBindPort: 29000, 210 }}, 211 }, 212 }, 213 // SidecarTask: nil // not hashed 214 }} 215 216 type svc = Service 217 type tweaker = func(service *svc) 218 219 hash := func(s *svc, canary bool) string { 220 return s.Hash("AllocID", "TaskName", canary) 221 } 222 223 t.Run("matching and is canary", func(t *testing.T) { 224 require.Equal(t, hash(original, true), hash(original, true)) 225 }) 226 227 t.Run("matching and is not canary", func(t *testing.T) { 228 require.Equal(t, hash(original, false), hash(original, false)) 229 }) 230 231 t.Run("matching mod canary", func(t *testing.T) { 232 require.NotEqual(t, hash(original, true), hash(original, false)) 233 }) 234 235 try := func(t *testing.T, tweak tweaker) { 236 originalHash := hash(original, true) 237 modifiable := original.Copy() 238 tweak(modifiable) 239 tweakedHash := hash(modifiable, true) 240 require.NotEqual(t, originalHash, tweakedHash) 241 } 242 243 // these tests use tweaker to modify 1 field and make the false assertion 244 // on comparing the resulting hash output 245 246 t.Run("mod name", func(t *testing.T) { 247 try(t, func(s *svc) { s.Name = "newName" }) 248 }) 249 250 t.Run("mod port label", func(t *testing.T) { 251 try(t, func(s *svc) { s.PortLabel = "newPortLabel" }) 252 }) 253 254 t.Run("mod tags", func(t *testing.T) { 255 try(t, func(s *svc) { s.Tags = []string{"new", "tags"} }) 256 }) 257 258 t.Run("mod canary tags", func(t *testing.T) { 259 try(t, func(s *svc) { s.CanaryTags = []string{"new", "tags"} }) 260 }) 261 262 t.Run("mod enable tag override", func(t *testing.T) { 263 try(t, func(s *svc) { s.EnableTagOverride = true }) 264 }) 265 266 t.Run("mod connect sidecar tags", func(t *testing.T) { 267 try(t, func(s *svc) { s.Connect.SidecarService.Tags = []string{"new", "tags"} }) 268 }) 269 270 t.Run("mod connect sidecar port", func(t *testing.T) { 271 try(t, func(s *svc) { s.Connect.SidecarService.Port = "9090" }) 272 }) 273 274 t.Run("mod connect sidecar proxy local service address", func(t *testing.T) { 275 try(t, func(s *svc) { s.Connect.SidecarService.Proxy.LocalServiceAddress = "1.1.1.1" }) 276 }) 277 278 t.Run("mod connect sidecar proxy local service port", func(t *testing.T) { 279 try(t, func(s *svc) { s.Connect.SidecarService.Proxy.LocalServicePort = 9999 }) 280 }) 281 282 t.Run("mod connect sidecar proxy config", func(t *testing.T) { 283 try(t, func(s *svc) { s.Connect.SidecarService.Proxy.Config = map[string]interface{}{"foo": "baz"} }) 284 }) 285 286 t.Run("mod connect sidecar proxy upstream dest name", func(t *testing.T) { 287 try(t, func(s *svc) { s.Connect.SidecarService.Proxy.Upstreams[0].DestinationName = "dest2" }) 288 }) 289 290 t.Run("mod connect sidecar proxy upstream dest local bind port", func(t *testing.T) { 291 try(t, func(s *svc) { s.Connect.SidecarService.Proxy.Upstreams[0].LocalBindPort = 29999 }) 292 }) 293 } 294 295 func TestConsulConnect_Validate(t *testing.T) { 296 t.Parallel() 297 298 c := &ConsulConnect{} 299 300 // An empty Connect stanza is invalid 301 require.Error(t, c.Validate()) 302 303 c.Native = true 304 require.NoError(t, c.Validate()) 305 306 // Native=true + Sidecar!=nil is invalid 307 c.SidecarService = &ConsulSidecarService{} 308 require.Error(t, c.Validate()) 309 310 c.Native = false 311 require.NoError(t, c.Validate()) 312 } 313 314 func TestConsulConnect_CopyEquals(t *testing.T) { 315 t.Parallel() 316 317 c := &ConsulConnect{ 318 SidecarService: &ConsulSidecarService{ 319 Tags: []string{"tag1", "tag2"}, 320 Port: "9001", 321 Proxy: &ConsulProxy{ 322 LocalServiceAddress: "127.0.0.1", 323 LocalServicePort: 8080, 324 Upstreams: []ConsulUpstream{ 325 { 326 DestinationName: "up1", 327 LocalBindPort: 9002, 328 }, 329 { 330 DestinationName: "up2", 331 LocalBindPort: 9003, 332 }, 333 }, 334 Config: map[string]interface{}{ 335 "foo": 1, 336 }, 337 }, 338 }, 339 } 340 341 require.NoError(t, c.Validate()) 342 343 // Copies should be equivalent 344 o := c.Copy() 345 require.True(t, c.Equals(o)) 346 347 o.SidecarService.Proxy.Upstreams = nil 348 require.False(t, c.Equals(o)) 349 } 350 351 func TestConsulConnect_GatewayProxy_CopyEquals(t *testing.T) { 352 t.Parallel() 353 354 c := &ConsulGatewayProxy{ 355 ConnectTimeout: helper.TimeToPtr(1 * time.Second), 356 EnvoyGatewayBindTaggedAddresses: false, 357 EnvoyGatewayBindAddresses: make(map[string]*ConsulGatewayBindAddress), 358 } 359 360 require.NoError(t, c.Validate()) 361 362 // Copies should be equivalent 363 o := c.Copy() 364 require.Equal(t, c, o) 365 require.True(t, c.Equals(o)) 366 } 367 368 func TestSidecarTask_MergeIntoTask(t *testing.T) { 369 t.Parallel() 370 371 task := MockJob().TaskGroups[0].Tasks[0] 372 sTask := &SidecarTask{ 373 Name: "sidecar", 374 Driver: "sidecar", 375 User: "test", 376 Config: map[string]interface{}{ 377 "foo": "bar", 378 }, 379 Resources: &Resources{ 380 CPU: 10000, 381 MemoryMB: 10000, 382 }, 383 Env: map[string]string{ 384 "sidecar": "proxy", 385 }, 386 Meta: map[string]string{ 387 "abc": "123", 388 }, 389 KillTimeout: helper.TimeToPtr(15 * time.Second), 390 LogConfig: &LogConfig{ 391 MaxFiles: 3, 392 }, 393 ShutdownDelay: helper.TimeToPtr(5 * time.Second), 394 KillSignal: "SIGABRT", 395 } 396 397 expected := task.Copy() 398 expected.Name = "sidecar" 399 expected.Driver = "sidecar" 400 expected.User = "test" 401 expected.Config = map[string]interface{}{ 402 "foo": "bar", 403 } 404 expected.Resources.CPU = 10000 405 expected.Resources.MemoryMB = 10000 406 expected.Env["sidecar"] = "proxy" 407 expected.Meta["abc"] = "123" 408 expected.KillTimeout = 15 * time.Second 409 expected.LogConfig.MaxFiles = 3 410 expected.ShutdownDelay = 5 * time.Second 411 expected.KillSignal = "SIGABRT" 412 413 sTask.MergeIntoTask(task) 414 require.Exactly(t, expected, task) 415 416 // Check that changing just driver config doesn't replace map 417 sTask.Config["abc"] = 123 418 expected.Config["abc"] = 123 419 420 sTask.MergeIntoTask(task) 421 require.Exactly(t, expected, task) 422 } 423 424 func TestSidecarTask_Equals(t *testing.T) { 425 t.Parallel() 426 427 original := &SidecarTask{ 428 Name: "sidecar-task-1", 429 Driver: "docker", 430 User: "nobody", 431 Config: map[string]interface{}{"foo": 1}, 432 Env: map[string]string{"color": "blue"}, 433 Resources: &Resources{MemoryMB: 300}, 434 Meta: map[string]string{"index": "1"}, 435 KillTimeout: helper.TimeToPtr(2 * time.Second), 436 LogConfig: &LogConfig{ 437 MaxFiles: 2, 438 MaxFileSizeMB: 300, 439 }, 440 ShutdownDelay: helper.TimeToPtr(10 * time.Second), 441 KillSignal: "SIGTERM", 442 } 443 444 t.Run("unmodified", func(t *testing.T) { 445 duplicate := original.Copy() 446 require.True(t, duplicate.Equals(original)) 447 }) 448 449 type st = SidecarTask 450 type tweaker = func(task *st) 451 452 try := func(t *testing.T, tweak tweaker) { 453 modified := original.Copy() 454 tweak(modified) 455 require.NotEqual(t, original, modified) 456 } 457 458 t.Run("mod name", func(t *testing.T) { 459 try(t, func(s *st) { s.Name = "sidecar-task-2" }) 460 }) 461 462 t.Run("mod driver", func(t *testing.T) { 463 try(t, func(s *st) { s.Driver = "exec" }) 464 }) 465 466 t.Run("mod user", func(t *testing.T) { 467 try(t, func(s *st) { s.User = "root" }) 468 }) 469 470 t.Run("mod config", func(t *testing.T) { 471 try(t, func(s *st) { s.Config = map[string]interface{}{"foo": 2} }) 472 }) 473 474 t.Run("mod env", func(t *testing.T) { 475 try(t, func(s *st) { s.Env = map[string]string{"color": "red"} }) 476 }) 477 478 t.Run("mod resources", func(t *testing.T) { 479 try(t, func(s *st) { s.Resources = &Resources{MemoryMB: 200} }) 480 }) 481 482 t.Run("mod meta", func(t *testing.T) { 483 try(t, func(s *st) { s.Meta = map[string]string{"index": "2"} }) 484 }) 485 486 t.Run("mod kill timeout", func(t *testing.T) { 487 try(t, func(s *st) { s.KillTimeout = helper.TimeToPtr(3 * time.Second) }) 488 }) 489 490 t.Run("mod log config", func(t *testing.T) { 491 try(t, func(s *st) { s.LogConfig = &LogConfig{MaxFiles: 3} }) 492 }) 493 494 t.Run("mod shutdown delay", func(t *testing.T) { 495 try(t, func(s *st) { s.ShutdownDelay = helper.TimeToPtr(20 * time.Second) }) 496 }) 497 498 t.Run("mod kill signal", func(t *testing.T) { 499 try(t, func(s *st) { s.KillSignal = "SIGHUP" }) 500 }) 501 } 502 503 func TestConsulUpstream_upstreamEquals(t *testing.T) { 504 t.Parallel() 505 506 up := func(name string, port int) ConsulUpstream { 507 return ConsulUpstream{ 508 DestinationName: name, 509 LocalBindPort: port, 510 } 511 } 512 513 t.Run("size mismatch", func(t *testing.T) { 514 a := []ConsulUpstream{up("foo", 8000)} 515 b := []ConsulUpstream{up("foo", 8000), up("bar", 9000)} 516 require.False(t, upstreamsEquals(a, b)) 517 }) 518 519 t.Run("different", func(t *testing.T) { 520 a := []ConsulUpstream{up("bar", 9000)} 521 b := []ConsulUpstream{up("foo", 8000)} 522 require.False(t, upstreamsEquals(a, b)) 523 }) 524 525 t.Run("identical", func(t *testing.T) { 526 a := []ConsulUpstream{up("foo", 8000), up("bar", 9000)} 527 b := []ConsulUpstream{up("foo", 8000), up("bar", 9000)} 528 require.True(t, upstreamsEquals(a, b)) 529 }) 530 531 t.Run("unsorted", func(t *testing.T) { 532 a := []ConsulUpstream{up("foo", 8000), up("bar", 9000)} 533 b := []ConsulUpstream{up("bar", 9000), up("foo", 8000)} 534 require.True(t, upstreamsEquals(a, b)) 535 }) 536 } 537 538 func TestConsulExposePath_exposePathsEqual(t *testing.T) { 539 t.Parallel() 540 541 expose := func(path, protocol, listen string, local int) ConsulExposePath { 542 return ConsulExposePath{ 543 Path: path, 544 Protocol: protocol, 545 LocalPathPort: local, 546 ListenerPort: listen, 547 } 548 } 549 550 t.Run("size mismatch", func(t *testing.T) { 551 a := []ConsulExposePath{expose("/1", "http", "myPort", 8000)} 552 b := []ConsulExposePath{expose("/1", "http", "myPort", 8000), expose("/2", "http", "myPort", 8000)} 553 require.False(t, exposePathsEqual(a, b)) 554 }) 555 556 t.Run("different", func(t *testing.T) { 557 a := []ConsulExposePath{expose("/1", "http", "myPort", 8000)} 558 b := []ConsulExposePath{expose("/2", "http", "myPort", 8000)} 559 require.False(t, exposePathsEqual(a, b)) 560 }) 561 562 t.Run("identical", func(t *testing.T) { 563 a := []ConsulExposePath{expose("/1", "http", "myPort", 8000)} 564 b := []ConsulExposePath{expose("/1", "http", "myPort", 8000)} 565 require.True(t, exposePathsEqual(a, b)) 566 }) 567 568 t.Run("unsorted", func(t *testing.T) { 569 a := []ConsulExposePath{expose("/2", "http", "myPort", 8000), expose("/1", "http", "myPort", 8000)} 570 b := []ConsulExposePath{expose("/1", "http", "myPort", 8000), expose("/2", "http", "myPort", 8000)} 571 require.True(t, exposePathsEqual(a, b)) 572 }) 573 } 574 575 func TestConsulExposeConfig_Copy(t *testing.T) { 576 t.Parallel() 577 578 require.Nil(t, (*ConsulExposeConfig)(nil).Copy()) 579 require.Equal(t, &ConsulExposeConfig{ 580 Paths: []ConsulExposePath{{ 581 Path: "/health", 582 }}, 583 }, (&ConsulExposeConfig{ 584 Paths: []ConsulExposePath{{ 585 Path: "/health", 586 }}, 587 }).Copy()) 588 } 589 590 func TestConsulExposeConfig_Equals(t *testing.T) { 591 t.Parallel() 592 593 require.True(t, (*ConsulExposeConfig)(nil).Equals(nil)) 594 require.True(t, (&ConsulExposeConfig{ 595 Paths: []ConsulExposePath{{ 596 Path: "/health", 597 }}, 598 }).Equals(&ConsulExposeConfig{ 599 Paths: []ConsulExposePath{{ 600 Path: "/health", 601 }}, 602 })) 603 } 604 605 func TestConsulSidecarService_Copy(t *testing.T) { 606 t.Parallel() 607 608 t.Run("nil", func(t *testing.T) { 609 s := (*ConsulSidecarService)(nil) 610 result := s.Copy() 611 require.Nil(t, result) 612 }) 613 614 t.Run("not nil", func(t *testing.T) { 615 s := &ConsulSidecarService{ 616 Tags: []string{"foo", "bar"}, 617 Port: "port1", 618 Proxy: &ConsulProxy{LocalServiceAddress: "10.0.0.1"}, 619 } 620 result := s.Copy() 621 require.Equal(t, &ConsulSidecarService{ 622 Tags: []string{"foo", "bar"}, 623 Port: "port1", 624 Proxy: &ConsulProxy{LocalServiceAddress: "10.0.0.1"}, 625 }, result) 626 }) 627 } 628 629 var ( 630 consulIngressGateway1 = &ConsulGateway{ 631 Proxy: &ConsulGatewayProxy{ 632 ConnectTimeout: helper.TimeToPtr(1 * time.Second), 633 EnvoyGatewayBindTaggedAddresses: true, 634 EnvoyGatewayBindAddresses: map[string]*ConsulGatewayBindAddress{ 635 "listener1": {Address: "10.0.0.1", Port: 2001}, 636 "listener2": {Address: "10.0.0.1", Port: 2002}, 637 }, 638 EnvoyGatewayNoDefaultBind: true, 639 Config: map[string]interface{}{ 640 "foo": 1, 641 }, 642 }, 643 Ingress: &ConsulIngressConfigEntry{ 644 TLS: &ConsulGatewayTLSConfig{ 645 Enabled: true, 646 }, 647 Listeners: []*ConsulIngressListener{{ 648 Port: 3000, 649 Protocol: "http", 650 Services: []*ConsulIngressService{{ 651 Name: "service1", 652 Hosts: []string{"10.0.0.1", "10.0.0.1:3000"}, 653 }, { 654 Name: "service2", 655 Hosts: []string{"10.0.0.2", "10.0.0.2:3000"}, 656 }}, 657 }, { 658 Port: 3001, 659 Protocol: "tcp", 660 Services: []*ConsulIngressService{{ 661 Name: "service3", 662 }}, 663 }}, 664 }, 665 } 666 667 consulTerminatingGateway1 = &ConsulGateway{ 668 Proxy: &ConsulGatewayProxy{ 669 ConnectTimeout: helper.TimeToPtr(1 * time.Second), 670 EnvoyDNSDiscoveryType: "STRICT_DNS", 671 EnvoyGatewayBindAddresses: nil, 672 }, 673 Terminating: &ConsulTerminatingConfigEntry{ 674 Services: []*ConsulLinkedService{{ 675 Name: "linked-service1", 676 CAFile: "ca.pem", 677 CertFile: "cert.pem", 678 KeyFile: "key.pem", 679 SNI: "service1.consul", 680 }, { 681 Name: "linked-service2", 682 }}, 683 }, 684 } 685 ) 686 687 func TestConsulGateway_Prefix(t *testing.T) { 688 t.Run("ingress", func(t *testing.T) { 689 result := (&ConsulGateway{Ingress: new(ConsulIngressConfigEntry)}).Prefix() 690 require.Equal(t, ConnectIngressPrefix, result) 691 }) 692 693 t.Run("terminating", func(t *testing.T) { 694 result := (&ConsulGateway{Terminating: new(ConsulTerminatingConfigEntry)}).Prefix() 695 require.Equal(t, ConnectTerminatingPrefix, result) 696 }) 697 698 // also mesh 699 } 700 701 func TestConsulGateway_Copy(t *testing.T) { 702 t.Parallel() 703 704 t.Run("nil", func(t *testing.T) { 705 g := (*ConsulGateway)(nil) 706 result := g.Copy() 707 require.Nil(t, result) 708 }) 709 710 t.Run("as ingress", func(t *testing.T) { 711 result := consulIngressGateway1.Copy() 712 require.Equal(t, consulIngressGateway1, result) 713 require.True(t, result.Equals(consulIngressGateway1)) 714 require.True(t, consulIngressGateway1.Equals(result)) 715 }) 716 717 t.Run("as terminating", func(t *testing.T) { 718 result := consulTerminatingGateway1.Copy() 719 require.Equal(t, consulTerminatingGateway1, result) 720 require.True(t, result.Equals(consulTerminatingGateway1)) 721 require.True(t, consulTerminatingGateway1.Equals(result)) 722 }) 723 } 724 725 func TestConsulGateway_Equals_ingress(t *testing.T) { 726 t.Parallel() 727 728 t.Run("nil", func(t *testing.T) { 729 a := (*ConsulGateway)(nil) 730 b := (*ConsulGateway)(nil) 731 require.True(t, a.Equals(b)) 732 require.False(t, a.Equals(consulIngressGateway1)) 733 require.False(t, consulIngressGateway1.Equals(a)) 734 }) 735 736 original := consulIngressGateway1.Copy() 737 738 type cg = ConsulGateway 739 type tweaker = func(g *cg) 740 741 t.Run("reflexive", func(t *testing.T) { 742 require.True(t, original.Equals(original)) 743 }) 744 745 try := func(t *testing.T, tweak tweaker) { 746 modifiable := original.Copy() 747 tweak(modifiable) 748 require.False(t, original.Equals(modifiable)) 749 require.False(t, modifiable.Equals(original)) 750 require.True(t, modifiable.Equals(modifiable)) 751 } 752 753 // proxy stanza equality checks 754 755 t.Run("mod gateway timeout", func(t *testing.T) { 756 try(t, func(g *cg) { g.Proxy.ConnectTimeout = helper.TimeToPtr(9 * time.Second) }) 757 }) 758 759 t.Run("mod gateway envoy_gateway_bind_tagged_addresses", func(t *testing.T) { 760 try(t, func(g *cg) { g.Proxy.EnvoyGatewayBindTaggedAddresses = false }) 761 }) 762 763 t.Run("mod gateway envoy_gateway_bind_addresses", func(t *testing.T) { 764 try(t, func(g *cg) { 765 g.Proxy.EnvoyGatewayBindAddresses = map[string]*ConsulGatewayBindAddress{ 766 "listener3": {Address: "9.9.9.9", Port: 9999}, 767 } 768 }) 769 }) 770 771 t.Run("mod gateway envoy_gateway_no_default_bind", func(t *testing.T) { 772 try(t, func(g *cg) { g.Proxy.EnvoyGatewayNoDefaultBind = false }) 773 }) 774 775 t.Run("mod gateway config", func(t *testing.T) { 776 try(t, func(g *cg) { 777 g.Proxy.Config = map[string]interface{}{ 778 "foo": 2, 779 } 780 }) 781 }) 782 783 // ingress config entry equality checks 784 785 t.Run("mod ingress tls", func(t *testing.T) { 786 try(t, func(g *cg) { g.Ingress.TLS = nil }) 787 try(t, func(g *cg) { g.Ingress.TLS.Enabled = false }) 788 }) 789 790 t.Run("mod ingress listeners count", func(t *testing.T) { 791 try(t, func(g *cg) { g.Ingress.Listeners = g.Ingress.Listeners[:1] }) 792 }) 793 794 t.Run("mod ingress listeners port", func(t *testing.T) { 795 try(t, func(g *cg) { g.Ingress.Listeners[0].Port = 7777 }) 796 }) 797 798 t.Run("mod ingress listeners protocol", func(t *testing.T) { 799 try(t, func(g *cg) { g.Ingress.Listeners[0].Protocol = "tcp" }) 800 }) 801 802 t.Run("mod ingress listeners services count", func(t *testing.T) { 803 try(t, func(g *cg) { g.Ingress.Listeners[0].Services = g.Ingress.Listeners[0].Services[:1] }) 804 }) 805 806 t.Run("mod ingress listeners services name", func(t *testing.T) { 807 try(t, func(g *cg) { g.Ingress.Listeners[0].Services[0].Name = "serviceX" }) 808 }) 809 810 t.Run("mod ingress listeners services hosts count", func(t *testing.T) { 811 try(t, func(g *cg) { g.Ingress.Listeners[0].Services[0].Hosts = g.Ingress.Listeners[0].Services[0].Hosts[:1] }) 812 }) 813 814 t.Run("mod ingress listeners services hosts content", func(t *testing.T) { 815 try(t, func(g *cg) { g.Ingress.Listeners[0].Services[0].Hosts[0] = "255.255.255.255" }) 816 }) 817 } 818 819 func TestConsulGateway_Equals_terminating(t *testing.T) { 820 t.Parallel() 821 822 original := consulTerminatingGateway1.Copy() 823 824 type cg = ConsulGateway 825 type tweaker = func(c *cg) 826 827 t.Run("reflexive", func(t *testing.T) { 828 require.True(t, original.Equals(original)) 829 }) 830 831 try := func(t *testing.T, tweak tweaker) { 832 modifiable := original.Copy() 833 tweak(modifiable) 834 require.False(t, original.Equals(modifiable)) 835 require.False(t, modifiable.Equals(original)) 836 require.True(t, modifiable.Equals(modifiable)) 837 } 838 839 // proxy stanza equality checks 840 841 t.Run("mod dns discovery type", func(t *testing.T) { 842 try(t, func(g *cg) { g.Proxy.EnvoyDNSDiscoveryType = "LOGICAL_DNS" }) 843 }) 844 845 // terminating config entry equality checks 846 847 t.Run("mod terminating services count", func(t *testing.T) { 848 try(t, func(g *cg) { g.Terminating.Services = g.Terminating.Services[:1] }) 849 }) 850 851 t.Run("mod terminating services name", func(t *testing.T) { 852 try(t, func(g *cg) { g.Terminating.Services[0].Name = "foo" }) 853 }) 854 855 t.Run("mod terminating services ca_file", func(t *testing.T) { 856 try(t, func(g *cg) { g.Terminating.Services[0].CAFile = "foo.pem" }) 857 }) 858 859 t.Run("mod terminating services cert_file", func(t *testing.T) { 860 try(t, func(g *cg) { g.Terminating.Services[0].CertFile = "foo.pem" }) 861 }) 862 863 t.Run("mod terminating services key_file", func(t *testing.T) { 864 try(t, func(g *cg) { g.Terminating.Services[0].KeyFile = "foo.pem" }) 865 }) 866 867 t.Run("mod terminating services sni", func(t *testing.T) { 868 try(t, func(g *cg) { g.Terminating.Services[0].SNI = "foo.consul" }) 869 }) 870 } 871 872 func TestConsulGateway_ingressServicesEqual(t *testing.T) { 873 t.Parallel() 874 875 igs1 := []*ConsulIngressService{{ 876 Name: "service1", 877 Hosts: []string{"host1", "host2"}, 878 }, { 879 Name: "service2", 880 Hosts: []string{"host3"}, 881 }} 882 883 require.False(t, ingressServicesEqual(igs1, nil)) 884 require.True(t, ingressServicesEqual(igs1, igs1)) 885 886 reversed := []*ConsulIngressService{ 887 igs1[1], igs1[0], // services reversed 888 } 889 890 require.True(t, ingressServicesEqual(igs1, reversed)) 891 892 hostOrder := []*ConsulIngressService{{ 893 Name: "service1", 894 Hosts: []string{"host2", "host1"}, // hosts reversed 895 }, { 896 Name: "service2", 897 Hosts: []string{"host3"}, 898 }} 899 900 require.True(t, ingressServicesEqual(igs1, hostOrder)) 901 } 902 903 func TestConsulGateway_ingressListenersEqual(t *testing.T) { 904 t.Parallel() 905 906 ils1 := []*ConsulIngressListener{{ 907 Port: 2000, 908 Protocol: "http", 909 Services: []*ConsulIngressService{{ 910 Name: "service1", 911 Hosts: []string{"host1", "host2"}, 912 }}, 913 }, { 914 Port: 2001, 915 Protocol: "tcp", 916 Services: []*ConsulIngressService{{ 917 Name: "service2", 918 }}, 919 }} 920 921 require.False(t, ingressListenersEqual(ils1, nil)) 922 923 reversed := []*ConsulIngressListener{ 924 ils1[1], ils1[0], 925 } 926 927 require.True(t, ingressListenersEqual(ils1, reversed)) 928 } 929 930 func TestConsulGateway_Validate(t *testing.T) { 931 t.Parallel() 932 933 t.Run("bad proxy", func(t *testing.T) { 934 err := (&ConsulGateway{ 935 Proxy: &ConsulGatewayProxy{ 936 ConnectTimeout: nil, 937 }, 938 Ingress: nil, 939 }).Validate() 940 require.EqualError(t, err, "Consul Gateway Proxy connection_timeout must be set") 941 }) 942 943 t.Run("bad ingress config entry", func(t *testing.T) { 944 err := (&ConsulGateway{ 945 Ingress: &ConsulIngressConfigEntry{ 946 Listeners: nil, 947 }, 948 }).Validate() 949 require.EqualError(t, err, "Consul Ingress Gateway requires at least one listener") 950 }) 951 952 t.Run("bad terminating config entry", func(t *testing.T) { 953 err := (&ConsulGateway{ 954 Terminating: &ConsulTerminatingConfigEntry{ 955 Services: nil, 956 }, 957 }).Validate() 958 require.EqualError(t, err, "Consul Terminating Gateway requires at least one service") 959 }) 960 961 t.Run("no config entry set", func(t *testing.T) { 962 err := (&ConsulGateway{ 963 Ingress: nil, 964 Terminating: nil, 965 }).Validate() 966 require.EqualError(t, err, "One Consul Gateway Configuration Entry must be set") 967 }) 968 969 t.Run("multiple config entries set", func(t *testing.T) { 970 err := (&ConsulGateway{ 971 Ingress: &ConsulIngressConfigEntry{ 972 Listeners: []*ConsulIngressListener{{ 973 Port: 1111, 974 Protocol: "tcp", 975 Services: []*ConsulIngressService{{ 976 Name: "service1", 977 }}, 978 }}, 979 }, 980 Terminating: &ConsulTerminatingConfigEntry{ 981 Services: []*ConsulLinkedService{{ 982 Name: "linked-service1", 983 }}, 984 }, 985 }).Validate() 986 require.EqualError(t, err, "One Consul Gateway Configuration Entry must be set") 987 }) 988 } 989 990 func TestConsulGatewayBindAddress_Validate(t *testing.T) { 991 t.Parallel() 992 993 t.Run("no address", func(t *testing.T) { 994 err := (&ConsulGatewayBindAddress{ 995 Address: "", 996 Port: 2000, 997 }).Validate() 998 require.EqualError(t, err, "Consul Gateway Bind Address must be set") 999 }) 1000 1001 t.Run("invalid port", func(t *testing.T) { 1002 err := (&ConsulGatewayBindAddress{ 1003 Address: "10.0.0.1", 1004 Port: 0, 1005 }).Validate() 1006 require.EqualError(t, err, "Consul Gateway Bind Address must set valid Port") 1007 }) 1008 1009 t.Run("ok", func(t *testing.T) { 1010 err := (&ConsulGatewayBindAddress{ 1011 Address: "10.0.0.1", 1012 Port: 2000, 1013 }).Validate() 1014 require.NoError(t, err) 1015 }) 1016 } 1017 1018 func TestConsulGatewayProxy_Validate(t *testing.T) { 1019 t.Parallel() 1020 1021 t.Run("no timeout", func(t *testing.T) { 1022 err := (&ConsulGatewayProxy{ 1023 ConnectTimeout: nil, 1024 }).Validate() 1025 require.EqualError(t, err, "Consul Gateway Proxy connection_timeout must be set") 1026 }) 1027 1028 t.Run("invalid bind address", func(t *testing.T) { 1029 err := (&ConsulGatewayProxy{ 1030 ConnectTimeout: helper.TimeToPtr(1 * time.Second), 1031 EnvoyGatewayBindAddresses: map[string]*ConsulGatewayBindAddress{ 1032 "service1": { 1033 Address: "10.0.0.1", 1034 Port: 0, 1035 }}, 1036 }).Validate() 1037 require.EqualError(t, err, "Consul Gateway Bind Address must set valid Port") 1038 }) 1039 1040 t.Run("invalid dns discovery type", func(t *testing.T) { 1041 err := (&ConsulGatewayProxy{ 1042 ConnectTimeout: helper.TimeToPtr(1 * time.Second), 1043 EnvoyDNSDiscoveryType: "RANDOM_DNS", 1044 }).Validate() 1045 require.EqualError(t, err, "Consul Gateway Proxy Envoy DNS Discovery type must be STRICT_DNS or LOGICAL_DNS") 1046 }) 1047 1048 t.Run("ok with nothing set", func(t *testing.T) { 1049 err := (&ConsulGatewayProxy{ 1050 ConnectTimeout: helper.TimeToPtr(1 * time.Second), 1051 }).Validate() 1052 require.NoError(t, err) 1053 }) 1054 1055 t.Run("ok with everything set", func(t *testing.T) { 1056 err := (&ConsulGatewayProxy{ 1057 ConnectTimeout: helper.TimeToPtr(1 * time.Second), 1058 EnvoyGatewayBindAddresses: map[string]*ConsulGatewayBindAddress{ 1059 "service1": { 1060 Address: "10.0.0.1", 1061 Port: 2000, 1062 }}, 1063 EnvoyGatewayBindTaggedAddresses: true, 1064 EnvoyGatewayNoDefaultBind: true, 1065 }).Validate() 1066 require.NoError(t, err) 1067 }) 1068 } 1069 1070 func TestConsulIngressService_Validate(t *testing.T) { 1071 t.Parallel() 1072 1073 t.Run("invalid name", func(t *testing.T) { 1074 err := (&ConsulIngressService{ 1075 Name: "", 1076 }).Validate(true) 1077 require.EqualError(t, err, "Consul Ingress Service requires a name") 1078 }) 1079 1080 t.Run("http missing hosts", func(t *testing.T) { 1081 err := (&ConsulIngressService{ 1082 Name: "service1", 1083 }).Validate(true) 1084 require.EqualError(t, err, "Consul Ingress Service requires one or more hosts when using HTTP protocol") 1085 }) 1086 1087 t.Run("tcp extraneous hosts", func(t *testing.T) { 1088 err := (&ConsulIngressService{ 1089 Name: "service1", 1090 Hosts: []string{"host1"}, 1091 }).Validate(false) 1092 require.EqualError(t, err, "Consul Ingress Service supports hosts only when using HTTP protocol") 1093 }) 1094 1095 t.Run("ok tcp", func(t *testing.T) { 1096 err := (&ConsulIngressService{ 1097 Name: "service1", 1098 }).Validate(false) 1099 require.NoError(t, err) 1100 }) 1101 1102 t.Run("ok http", func(t *testing.T) { 1103 err := (&ConsulIngressService{ 1104 Name: "service1", 1105 Hosts: []string{"host1"}, 1106 }).Validate(true) 1107 require.NoError(t, err) 1108 }) 1109 } 1110 1111 func TestConsulIngressListener_Validate(t *testing.T) { 1112 t.Parallel() 1113 1114 t.Run("invalid port", func(t *testing.T) { 1115 err := (&ConsulIngressListener{ 1116 Port: 0, 1117 Protocol: "tcp", 1118 Services: []*ConsulIngressService{{ 1119 Name: "service1", 1120 }}, 1121 }).Validate() 1122 require.EqualError(t, err, "Consul Ingress Listener requires valid Port") 1123 }) 1124 1125 t.Run("invalid protocol", func(t *testing.T) { 1126 err := (&ConsulIngressListener{ 1127 Port: 2000, 1128 Protocol: "gopher", 1129 Services: []*ConsulIngressService{{ 1130 Name: "service1", 1131 }}, 1132 }).Validate() 1133 require.EqualError(t, err, `Consul Ingress Listener requires protocol of "http" or "tcp", got "gopher"`) 1134 }) 1135 1136 t.Run("no services", func(t *testing.T) { 1137 err := (&ConsulIngressListener{ 1138 Port: 2000, 1139 Protocol: "tcp", 1140 Services: nil, 1141 }).Validate() 1142 require.EqualError(t, err, "Consul Ingress Listener requires one or more services") 1143 }) 1144 1145 t.Run("invalid service", func(t *testing.T) { 1146 err := (&ConsulIngressListener{ 1147 Port: 2000, 1148 Protocol: "tcp", 1149 Services: []*ConsulIngressService{{ 1150 Name: "", 1151 }}, 1152 }).Validate() 1153 require.EqualError(t, err, "Consul Ingress Service requires a name") 1154 }) 1155 1156 t.Run("ok", func(t *testing.T) { 1157 err := (&ConsulIngressListener{ 1158 Port: 2000, 1159 Protocol: "tcp", 1160 Services: []*ConsulIngressService{{ 1161 Name: "service1", 1162 }}, 1163 }).Validate() 1164 require.NoError(t, err) 1165 }) 1166 } 1167 1168 func TestConsulIngressConfigEntry_Validate(t *testing.T) { 1169 t.Parallel() 1170 1171 t.Run("no listeners", func(t *testing.T) { 1172 err := (&ConsulIngressConfigEntry{}).Validate() 1173 require.EqualError(t, err, "Consul Ingress Gateway requires at least one listener") 1174 }) 1175 1176 t.Run("invalid listener", func(t *testing.T) { 1177 err := (&ConsulIngressConfigEntry{ 1178 Listeners: []*ConsulIngressListener{{ 1179 Port: 9000, 1180 Protocol: "tcp", 1181 }}, 1182 }).Validate() 1183 require.EqualError(t, err, "Consul Ingress Listener requires one or more services") 1184 }) 1185 1186 t.Run("full", func(t *testing.T) { 1187 err := (&ConsulIngressConfigEntry{ 1188 TLS: &ConsulGatewayTLSConfig{ 1189 Enabled: true, 1190 }, 1191 Listeners: []*ConsulIngressListener{{ 1192 Port: 9000, 1193 Protocol: "tcp", 1194 Services: []*ConsulIngressService{{ 1195 Name: "service1", 1196 }}, 1197 }}, 1198 }).Validate() 1199 require.NoError(t, err) 1200 }) 1201 } 1202 1203 func TestConsulLinkedService_Validate(t *testing.T) { 1204 t.Parallel() 1205 1206 t.Run("nil", func(t *testing.T) { 1207 err := (*ConsulLinkedService)(nil).Validate() 1208 require.Nil(t, err) 1209 }) 1210 1211 t.Run("missing name", func(t *testing.T) { 1212 err := (&ConsulLinkedService{}).Validate() 1213 require.EqualError(t, err, "Consul Linked Service requires Name") 1214 }) 1215 1216 t.Run("missing cafile", func(t *testing.T) { 1217 err := (&ConsulLinkedService{ 1218 Name: "linked-service1", 1219 CertFile: "cert_file.pem", 1220 KeyFile: "key_file.pem", 1221 }).Validate() 1222 require.EqualError(t, err, "Consul Linked Service TLS requires CAFile") 1223 }) 1224 1225 t.Run("mutual cert key", func(t *testing.T) { 1226 err := (&ConsulLinkedService{ 1227 Name: "linked-service1", 1228 CAFile: "ca_file.pem", 1229 CertFile: "cert_file.pem", 1230 }).Validate() 1231 require.EqualError(t, err, "Consul Linked Service TLS Cert and Key must both be set") 1232 }) 1233 1234 t.Run("sni without cafile", func(t *testing.T) { 1235 err := (&ConsulLinkedService{ 1236 Name: "linked-service1", 1237 SNI: "service.consul", 1238 }).Validate() 1239 require.EqualError(t, err, "Consul Linked Service TLS SNI requires CAFile") 1240 }) 1241 1242 t.Run("minimal", func(t *testing.T) { 1243 err := (&ConsulLinkedService{ 1244 Name: "linked-service1", 1245 }).Validate() 1246 require.NoError(t, err) 1247 }) 1248 1249 t.Run("tls minimal", func(t *testing.T) { 1250 err := (&ConsulLinkedService{ 1251 Name: "linked-service1", 1252 CAFile: "ca_file.pem", 1253 }).Validate() 1254 require.NoError(t, err) 1255 }) 1256 1257 t.Run("tls mutual", func(t *testing.T) { 1258 err := (&ConsulLinkedService{ 1259 Name: "linked-service1", 1260 CAFile: "ca_file.pem", 1261 CertFile: "cert_file.pem", 1262 KeyFile: "key_file.pem", 1263 }).Validate() 1264 require.NoError(t, err) 1265 }) 1266 1267 t.Run("tls sni", func(t *testing.T) { 1268 err := (&ConsulLinkedService{ 1269 Name: "linked-service1", 1270 CAFile: "ca_file.pem", 1271 SNI: "linked-service.consul", 1272 }).Validate() 1273 require.NoError(t, err) 1274 }) 1275 1276 t.Run("tls complete", func(t *testing.T) { 1277 err := (&ConsulLinkedService{ 1278 Name: "linked-service1", 1279 CAFile: "ca_file.pem", 1280 CertFile: "cert_file.pem", 1281 KeyFile: "key_file.pem", 1282 SNI: "linked-service.consul", 1283 }).Validate() 1284 require.NoError(t, err) 1285 }) 1286 } 1287 1288 func TestConsulLinkedService_Copy(t *testing.T) { 1289 t.Parallel() 1290 1291 require.Nil(t, (*ConsulLinkedService)(nil).Copy()) 1292 require.Equal(t, &ConsulLinkedService{ 1293 Name: "service1", 1294 CAFile: "ca.pem", 1295 CertFile: "cert.pem", 1296 KeyFile: "key.pem", 1297 SNI: "service1.consul", 1298 }, (&ConsulLinkedService{ 1299 Name: "service1", 1300 CAFile: "ca.pem", 1301 CertFile: "cert.pem", 1302 KeyFile: "key.pem", 1303 SNI: "service1.consul", 1304 }).Copy()) 1305 } 1306 1307 func TestConsulLinkedService_linkedServicesEqual(t *testing.T) { 1308 t.Parallel() 1309 1310 services := []*ConsulLinkedService{{ 1311 Name: "service1", 1312 CAFile: "ca.pem", 1313 }, { 1314 Name: "service2", 1315 CAFile: "ca.pem", 1316 }} 1317 1318 require.False(t, linkedServicesEqual(services, nil)) 1319 require.True(t, linkedServicesEqual(services, services)) 1320 1321 reversed := []*ConsulLinkedService{ 1322 services[1], services[0], // reversed 1323 } 1324 1325 require.True(t, linkedServicesEqual(services, reversed)) 1326 1327 different := []*ConsulLinkedService{ 1328 services[0], &ConsulLinkedService{ 1329 Name: "service2", 1330 CAFile: "ca.pem", 1331 SNI: "service2.consul", 1332 }, 1333 } 1334 1335 require.False(t, linkedServicesEqual(services, different)) 1336 } 1337 1338 func TestConsulTerminatingConfigEntry_Validate(t *testing.T) { 1339 t.Parallel() 1340 1341 t.Run("nil", func(t *testing.T) { 1342 err := (*ConsulTerminatingConfigEntry)(nil).Validate() 1343 require.NoError(t, err) 1344 }) 1345 1346 t.Run("no services", func(t *testing.T) { 1347 err := (&ConsulTerminatingConfigEntry{ 1348 Services: make([]*ConsulLinkedService, 0), 1349 }).Validate() 1350 require.EqualError(t, err, "Consul Terminating Gateway requires at least one service") 1351 }) 1352 1353 t.Run("service invalid", func(t *testing.T) { 1354 err := (&ConsulTerminatingConfigEntry{ 1355 Services: []*ConsulLinkedService{{ 1356 Name: "", 1357 }}, 1358 }).Validate() 1359 require.EqualError(t, err, "Consul Linked Service requires Name") 1360 }) 1361 1362 t.Run("ok", func(t *testing.T) { 1363 err := (&ConsulTerminatingConfigEntry{ 1364 Services: []*ConsulLinkedService{{ 1365 Name: "service1", 1366 }}, 1367 }).Validate() 1368 require.NoError(t, err) 1369 }) 1370 }