github.com/outbrain/consul@v1.4.5/agent/structs/structs_test.go (about) 1 package structs 2 3 import ( 4 "fmt" 5 "reflect" 6 "strings" 7 "testing" 8 9 "github.com/hashicorp/consul/agent/cache" 10 "github.com/hashicorp/consul/api" 11 "github.com/hashicorp/consul/types" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 ) 15 16 func TestEncodeDecode(t *testing.T) { 17 arg := &RegisterRequest{ 18 Datacenter: "foo", 19 Node: "bar", 20 Address: "baz", 21 Service: &NodeService{ 22 Service: "test", 23 Address: "127.0.0.2", 24 }, 25 } 26 buf, err := Encode(RegisterRequestType, arg) 27 if err != nil { 28 t.Fatalf("err: %v", err) 29 } 30 31 var out RegisterRequest 32 err = Decode(buf[1:], &out) 33 if err != nil { 34 t.Fatalf("err: %v", err) 35 } 36 37 if !reflect.DeepEqual(arg.Service, out.Service) { 38 t.Fatalf("bad: %#v %#v", arg.Service, out.Service) 39 } 40 if !reflect.DeepEqual(arg, &out) { 41 t.Fatalf("bad: %#v %#v", arg, out) 42 } 43 } 44 45 func TestStructs_Implements(t *testing.T) { 46 var ( 47 _ RPCInfo = &RegisterRequest{} 48 _ RPCInfo = &DeregisterRequest{} 49 _ RPCInfo = &DCSpecificRequest{} 50 _ RPCInfo = &ServiceSpecificRequest{} 51 _ RPCInfo = &NodeSpecificRequest{} 52 _ RPCInfo = &ChecksInStateRequest{} 53 _ RPCInfo = &KVSRequest{} 54 _ RPCInfo = &KeyRequest{} 55 _ RPCInfo = &KeyListRequest{} 56 _ RPCInfo = &SessionRequest{} 57 _ RPCInfo = &SessionSpecificRequest{} 58 _ RPCInfo = &EventFireRequest{} 59 _ RPCInfo = &ACLPolicyResolveLegacyRequest{} 60 _ RPCInfo = &ACLPolicyBatchGetRequest{} 61 _ RPCInfo = &ACLPolicyGetRequest{} 62 _ RPCInfo = &ACLTokenGetRequest{} 63 _ RPCInfo = &KeyringRequest{} 64 _ CompoundResponse = &KeyringResponses{} 65 ) 66 } 67 68 func TestStructs_RegisterRequest_ChangesNode(t *testing.T) { 69 req := &RegisterRequest{ 70 ID: types.NodeID("40e4a748-2192-161a-0510-9bf59fe950b5"), 71 Node: "test", 72 Address: "127.0.0.1", 73 Datacenter: "dc1", 74 TaggedAddresses: make(map[string]string), 75 NodeMeta: map[string]string{ 76 "role": "server", 77 }, 78 } 79 80 node := &Node{ 81 ID: types.NodeID("40e4a748-2192-161a-0510-9bf59fe950b5"), 82 Node: "test", 83 Address: "127.0.0.1", 84 Datacenter: "dc1", 85 TaggedAddresses: make(map[string]string), 86 Meta: map[string]string{ 87 "role": "server", 88 }, 89 } 90 91 check := func(twiddle, restore func()) { 92 if req.ChangesNode(node) { 93 t.Fatalf("should not change") 94 } 95 96 twiddle() 97 if !req.ChangesNode(node) { 98 t.Fatalf("should change") 99 } 100 101 req.SkipNodeUpdate = true 102 if req.ChangesNode(node) { 103 t.Fatalf("should skip") 104 } 105 106 req.SkipNodeUpdate = false 107 if !req.ChangesNode(node) { 108 t.Fatalf("should change") 109 } 110 111 restore() 112 if req.ChangesNode(node) { 113 t.Fatalf("should not change") 114 } 115 } 116 117 check(func() { req.ID = "nope" }, func() { req.ID = types.NodeID("40e4a748-2192-161a-0510-9bf59fe950b5") }) 118 check(func() { req.Node = "nope" }, func() { req.Node = "test" }) 119 check(func() { req.Address = "127.0.0.2" }, func() { req.Address = "127.0.0.1" }) 120 check(func() { req.Datacenter = "dc2" }, func() { req.Datacenter = "dc1" }) 121 check(func() { req.TaggedAddresses["wan"] = "nope" }, func() { delete(req.TaggedAddresses, "wan") }) 122 check(func() { req.NodeMeta["invalid"] = "nope" }, func() { delete(req.NodeMeta, "invalid") }) 123 124 if !req.ChangesNode(nil) { 125 t.Fatalf("should change") 126 } 127 } 128 129 // testServiceNode gives a fully filled out ServiceNode instance. 130 func testServiceNode(t *testing.T) *ServiceNode { 131 return &ServiceNode{ 132 ID: types.NodeID("40e4a748-2192-161a-0510-9bf59fe950b5"), 133 Node: "node1", 134 Address: "127.0.0.1", 135 Datacenter: "dc1", 136 TaggedAddresses: map[string]string{ 137 "hello": "world", 138 }, 139 NodeMeta: map[string]string{ 140 "tag": "value", 141 }, 142 ServiceKind: ServiceKindTypical, 143 ServiceID: "service1", 144 ServiceName: "dogs", 145 ServiceTags: []string{"prod", "v1"}, 146 ServiceAddress: "127.0.0.2", 147 ServicePort: 8080, 148 ServiceMeta: map[string]string{ 149 "service": "metadata", 150 }, 151 ServiceEnableTagOverride: true, 152 RaftIndex: RaftIndex{ 153 CreateIndex: 1, 154 ModifyIndex: 2, 155 }, 156 ServiceProxy: TestConnectProxyConfig(t), 157 // DEPRECATED (ProxyDestination) - remove this when removing ProxyDestination 158 // ServiceProxyDestination is deprecated bit must be set consistently with 159 // the value of ServiceProxy.DestinationServiceName otherwise a round-trip 160 // through ServiceNode -> NodeService and back will not match and fail 161 // tests. 162 ServiceProxyDestination: "web", 163 ServiceConnect: ServiceConnect{ 164 Native: true, 165 }, 166 } 167 } 168 169 func TestNode_IsSame(t *testing.T) { 170 id := types.NodeID("e62f3b31-9284-4e26-ab14-2a59dea85b55") 171 node := "mynode1" 172 address := "" 173 datacenter := "dc1" 174 n := &Node{ 175 ID: id, 176 Node: node, 177 Datacenter: datacenter, 178 Address: address, 179 TaggedAddresses: make(map[string]string), 180 Meta: make(map[string]string), 181 RaftIndex: RaftIndex{ 182 CreateIndex: 1, 183 ModifyIndex: 2, 184 }, 185 } 186 other := &Node{ 187 ID: id, 188 Node: node, 189 Datacenter: datacenter, 190 Address: address, 191 TaggedAddresses: make(map[string]string), 192 Meta: make(map[string]string), 193 RaftIndex: RaftIndex{ 194 CreateIndex: 1, 195 ModifyIndex: 3, 196 }, 197 } 198 check := func(twiddle, restore func()) { 199 t.Helper() 200 if !n.IsSame(other) || !other.IsSame(n) { 201 t.Fatalf("should be the same") 202 } 203 204 twiddle() 205 if n.IsSame(other) || other.IsSame(n) { 206 t.Fatalf("should be different, was %#v VS %#v", n, other) 207 } 208 209 restore() 210 if !n.IsSame(other) || !other.IsSame(n) { 211 t.Fatalf("should be the same") 212 } 213 } 214 check(func() { other.ID = types.NodeID("") }, func() { other.ID = id }) 215 check(func() { other.Node = "other" }, func() { other.Node = node }) 216 check(func() { other.Datacenter = "dcX" }, func() { other.Datacenter = datacenter }) 217 check(func() { other.Address = "127.0.0.1" }, func() { other.Address = address }) 218 check(func() { other.TaggedAddresses = map[string]string{"my": "address"} }, func() { other.TaggedAddresses = map[string]string{} }) 219 check(func() { other.Meta = map[string]string{"my": "meta"} }, func() { other.Meta = map[string]string{} }) 220 221 if !n.IsSame(other) { 222 t.Fatalf("should be equal, was %#v VS %#v", n, other) 223 } 224 } 225 226 func TestStructs_ServiceNode_IsSameService(t *testing.T) { 227 sn := testServiceNode(t) 228 node := "node1" 229 serviceID := sn.ServiceID 230 serviceAddress := sn.ServiceAddress 231 serviceEnableTagOverride := sn.ServiceEnableTagOverride 232 serviceMeta := make(map[string]string) 233 for k, v := range sn.ServiceMeta { 234 serviceMeta[k] = v 235 } 236 serviceName := sn.ServiceName 237 servicePort := sn.ServicePort 238 serviceTags := sn.ServiceTags 239 serviceWeights := Weights{Passing: 2, Warning: 1} 240 sn.ServiceWeights = serviceWeights 241 serviceProxyDestination := sn.ServiceProxyDestination 242 serviceProxy := sn.ServiceProxy 243 serviceConnect := sn.ServiceConnect 244 245 n := sn.ToNodeService().ToServiceNode(node) 246 other := sn.ToNodeService().ToServiceNode(node) 247 248 check := func(twiddle, restore func()) { 249 t.Helper() 250 if !n.IsSameService(other) || !other.IsSameService(n) { 251 t.Fatalf("should be the same") 252 } 253 254 twiddle() 255 if n.IsSameService(other) || other.IsSameService(n) { 256 t.Fatalf("should be different, was %#v VS %#v", n, other) 257 } 258 259 restore() 260 if !n.IsSameService(other) || !other.IsSameService(n) { 261 t.Fatalf("should be the same after restore, was:\n %#v VS\n %#v", n, other) 262 } 263 } 264 265 check(func() { other.ServiceID = "66fb695a-c782-472f-8d36-4f3edd754b37" }, func() { other.ServiceID = serviceID }) 266 check(func() { other.Node = "other" }, func() { other.Node = node }) 267 check(func() { other.ServiceAddress = "1.2.3.4" }, func() { other.ServiceAddress = serviceAddress }) 268 check(func() { other.ServiceEnableTagOverride = !serviceEnableTagOverride }, func() { other.ServiceEnableTagOverride = serviceEnableTagOverride }) 269 check(func() { other.ServiceKind = "newKind" }, func() { other.ServiceKind = "" }) 270 check(func() { other.ServiceMeta = map[string]string{"my": "meta"} }, func() { other.ServiceMeta = serviceMeta }) 271 check(func() { other.ServiceName = "duck" }, func() { other.ServiceName = serviceName }) 272 check(func() { other.ServicePort = 65534 }, func() { other.ServicePort = servicePort }) 273 check(func() { other.ServiceProxyDestination = "duck" }, func() { other.ServiceProxyDestination = serviceProxyDestination }) 274 check(func() { other.ServiceTags = []string{"new", "tags"} }, func() { other.ServiceTags = serviceTags }) 275 check(func() { other.ServiceWeights = Weights{Passing: 42, Warning: 41} }, func() { other.ServiceWeights = serviceWeights }) 276 check(func() { other.ServiceProxy = ConnectProxyConfig{} }, func() { other.ServiceProxy = serviceProxy }) 277 check(func() { other.ServiceConnect = ServiceConnect{} }, func() { other.ServiceConnect = serviceConnect }) 278 } 279 280 func TestStructs_ServiceNode_PartialClone(t *testing.T) { 281 sn := testServiceNode(t) 282 283 clone := sn.PartialClone() 284 285 // Make sure the parts that weren't supposed to be cloned didn't get 286 // copied over, then zero-value them out so we can do a DeepEqual() on 287 // the rest of the contents. 288 if clone.ID != "" || 289 clone.Address != "" || 290 clone.Datacenter != "" || 291 len(clone.TaggedAddresses) != 0 || 292 len(clone.NodeMeta) != 0 { 293 t.Fatalf("bad: %v", clone) 294 } 295 296 sn.ID = "" 297 sn.Address = "" 298 sn.Datacenter = "" 299 sn.TaggedAddresses = nil 300 sn.NodeMeta = nil 301 require.Equal(t, sn, clone) 302 303 sn.ServiceTags = append(sn.ServiceTags, "hello") 304 if reflect.DeepEqual(sn, clone) { 305 t.Fatalf("clone wasn't independent of the original") 306 } 307 308 revert := make([]string, len(sn.ServiceTags)-1) 309 copy(revert, sn.ServiceTags[0:len(sn.ServiceTags)-1]) 310 sn.ServiceTags = revert 311 if !reflect.DeepEqual(sn, clone) { 312 t.Fatalf("bad: %v VS %v", clone, sn) 313 } 314 oldPassingWeight := clone.ServiceWeights.Passing 315 sn.ServiceWeights.Passing = 1000 316 if reflect.DeepEqual(sn, clone) { 317 t.Fatalf("clone wasn't independent of the original for Meta") 318 } 319 sn.ServiceWeights.Passing = oldPassingWeight 320 sn.ServiceMeta["new_meta"] = "new_value" 321 if reflect.DeepEqual(sn, clone) { 322 t.Fatalf("clone wasn't independent of the original for Meta") 323 } 324 } 325 326 func TestStructs_ServiceNode_Conversions(t *testing.T) { 327 sn := testServiceNode(t) 328 329 sn2 := sn.ToNodeService().ToServiceNode("node1") 330 331 // These two fields get lost in the conversion, so we have to zero-value 332 // them out before we do the compare. 333 sn.ID = "" 334 sn.Address = "" 335 sn.Datacenter = "" 336 sn.TaggedAddresses = nil 337 sn.NodeMeta = nil 338 sn.ServiceWeights = Weights{Passing: 1, Warning: 1} 339 require.Equal(t, sn, sn2) 340 if !sn.IsSameService(sn2) || !sn2.IsSameService(sn) { 341 t.Fatalf("bad: %#v, should be the same %#v", sn2, sn) 342 } 343 // Those fields are lost in conversion, so IsSameService() should not take them into account 344 sn.Address = "y" 345 sn.Datacenter = "z" 346 sn.TaggedAddresses = map[string]string{"one": "1", "two": "2"} 347 sn.NodeMeta = map[string]string{"meta": "data"} 348 if !sn.IsSameService(sn2) || !sn2.IsSameService(sn) { 349 t.Fatalf("bad: %#v, should be the same %#v", sn2, sn) 350 } 351 } 352 353 func TestStructs_NodeService_ValidateConnectProxy(t *testing.T) { 354 cases := []struct { 355 Name string 356 Modify func(*NodeService) 357 Err string 358 }{ 359 { 360 "valid", 361 func(x *NodeService) {}, 362 "", 363 }, 364 365 { 366 "connect-proxy: no ProxyDestination", 367 func(x *NodeService) { x.Proxy.DestinationServiceName = "" }, 368 "Proxy.DestinationServiceName must be", 369 }, 370 371 { 372 "connect-proxy: whitespace ProxyDestination", 373 func(x *NodeService) { x.Proxy.DestinationServiceName = " " }, 374 "Proxy.DestinationServiceName must be", 375 }, 376 377 { 378 "connect-proxy: valid ProxyDestination", 379 func(x *NodeService) { x.Proxy.DestinationServiceName = "hello" }, 380 "", 381 }, 382 383 { 384 "connect-proxy: no port set", 385 func(x *NodeService) { x.Port = 0 }, 386 "Port must", 387 }, 388 389 { 390 "connect-proxy: ConnectNative set", 391 func(x *NodeService) { x.Connect.Native = true }, 392 "cannot also be", 393 }, 394 } 395 396 for _, tc := range cases { 397 t.Run(tc.Name, func(t *testing.T) { 398 assert := assert.New(t) 399 ns := TestNodeServiceProxy(t) 400 tc.Modify(ns) 401 402 err := ns.Validate() 403 assert.Equal(err != nil, tc.Err != "", err) 404 if err == nil { 405 return 406 } 407 408 assert.Contains(strings.ToLower(err.Error()), strings.ToLower(tc.Err)) 409 }) 410 } 411 } 412 413 func TestStructs_NodeService_ValidateSidecarService(t *testing.T) { 414 cases := []struct { 415 Name string 416 Modify func(*NodeService) 417 Err string 418 }{ 419 { 420 "valid", 421 func(x *NodeService) {}, 422 "", 423 }, 424 425 { 426 "ID can't be set", 427 func(x *NodeService) { x.Connect.SidecarService.ID = "foo" }, 428 "SidecarService cannot specify an ID", 429 }, 430 431 { 432 "Nested sidecar can't be set", 433 func(x *NodeService) { 434 x.Connect.SidecarService.Connect = &ServiceConnect{ 435 SidecarService: &ServiceDefinition{}, 436 } 437 }, 438 "SidecarService cannot have a nested SidecarService", 439 }, 440 441 { 442 "Sidecar can't have managed proxy", 443 func(x *NodeService) { 444 x.Connect.SidecarService.Connect = &ServiceConnect{ 445 Proxy: &ServiceDefinitionConnectProxy{}, 446 } 447 }, 448 "SidecarService cannot have a managed proxy", 449 }, 450 } 451 452 for _, tc := range cases { 453 t.Run(tc.Name, func(t *testing.T) { 454 assert := assert.New(t) 455 ns := TestNodeServiceSidecar(t) 456 tc.Modify(ns) 457 458 err := ns.Validate() 459 assert.Equal(err != nil, tc.Err != "", err) 460 if err == nil { 461 return 462 } 463 464 assert.Contains(strings.ToLower(err.Error()), strings.ToLower(tc.Err)) 465 }) 466 } 467 } 468 469 func TestStructs_NodeService_IsSame(t *testing.T) { 470 ns := &NodeService{ 471 ID: "node1", 472 Service: "theservice", 473 Tags: []string{"foo", "bar"}, 474 Address: "127.0.0.1", 475 Meta: map[string]string{ 476 "meta1": "value1", 477 "meta2": "value2", 478 }, 479 Port: 1234, 480 EnableTagOverride: true, 481 Proxy: ConnectProxyConfig{ 482 DestinationServiceName: "db", 483 Config: map[string]interface{}{ 484 "foo": "bar", 485 }, 486 }, 487 Weights: &Weights{Passing: 1, Warning: 1}, 488 } 489 if !ns.IsSame(ns) { 490 t.Fatalf("should be equal to itself") 491 } 492 493 other := &NodeService{ 494 ID: "node1", 495 Service: "theservice", 496 Tags: []string{"foo", "bar"}, 497 Address: "127.0.0.1", 498 Port: 1234, 499 EnableTagOverride: true, 500 Meta: map[string]string{ 501 // We don't care about order 502 "meta2": "value2", 503 "meta1": "value1", 504 }, 505 Proxy: ConnectProxyConfig{ 506 DestinationServiceName: "db", 507 Config: map[string]interface{}{ 508 "foo": "bar", 509 }, 510 }, 511 Weights: &Weights{Passing: 1, Warning: 1}, 512 RaftIndex: RaftIndex{ 513 CreateIndex: 1, 514 ModifyIndex: 2, 515 }, 516 } 517 if !ns.IsSame(other) || !other.IsSame(ns) { 518 t.Fatalf("should not care about Raft fields") 519 } 520 521 check := func(twiddle, restore func()) { 522 t.Helper() 523 if !ns.IsSame(other) || !other.IsSame(ns) { 524 t.Fatalf("should be the same") 525 } 526 527 twiddle() 528 if ns.IsSame(other) || other.IsSame(ns) { 529 t.Fatalf("should not be the same") 530 } 531 532 restore() 533 if !ns.IsSame(other) || !other.IsSame(ns) { 534 t.Fatalf("should be the same again") 535 } 536 } 537 538 check(func() { other.ID = "XXX" }, func() { other.ID = "node1" }) 539 check(func() { other.Service = "XXX" }, func() { other.Service = "theservice" }) 540 check(func() { other.Tags = nil }, func() { other.Tags = []string{"foo", "bar"} }) 541 check(func() { other.Tags = []string{"foo"} }, func() { other.Tags = []string{"foo", "bar"} }) 542 check(func() { other.Address = "XXX" }, func() { other.Address = "127.0.0.1" }) 543 check(func() { other.Port = 9999 }, func() { other.Port = 1234 }) 544 check(func() { other.Meta["meta2"] = "wrongValue" }, func() { other.Meta["meta2"] = "value2" }) 545 check(func() { other.EnableTagOverride = false }, func() { other.EnableTagOverride = true }) 546 check(func() { other.Kind = ServiceKindConnectProxy }, func() { other.Kind = "" }) 547 check(func() { other.Proxy.DestinationServiceName = "" }, func() { other.Proxy.DestinationServiceName = "db" }) 548 check(func() { other.Proxy.DestinationServiceID = "XXX" }, func() { other.Proxy.DestinationServiceID = "" }) 549 check(func() { other.Proxy.LocalServiceAddress = "XXX" }, func() { other.Proxy.LocalServiceAddress = "" }) 550 check(func() { other.Proxy.LocalServicePort = 9999 }, func() { other.Proxy.LocalServicePort = 0 }) 551 check(func() { other.Proxy.Config["baz"] = "XXX" }, func() { delete(other.Proxy.Config, "baz") }) 552 check(func() { other.Connect.Native = true }, func() { other.Connect.Native = false }) 553 otherServiceNode := other.ToServiceNode("node1") 554 copyNodeService := otherServiceNode.ToNodeService() 555 if !copyNodeService.IsSame(other) { 556 t.Fatalf("copy should be the same, but was\n %#v\nVS\n %#v", copyNodeService, other) 557 } 558 otherServiceNodeCopy2 := copyNodeService.ToServiceNode("node1") 559 if !otherServiceNode.IsSameService(otherServiceNodeCopy2) { 560 t.Fatalf("copy should be the same, but was\n %#v\nVS\n %#v", otherServiceNode, otherServiceNodeCopy2) 561 } 562 } 563 564 func TestStructs_HealthCheck_IsSame(t *testing.T) { 565 hc := &HealthCheck{ 566 Node: "node1", 567 CheckID: "check1", 568 Name: "thecheck", 569 Status: api.HealthPassing, 570 Notes: "it's all good", 571 Output: "lgtm", 572 ServiceID: "service1", 573 ServiceName: "theservice", 574 ServiceTags: []string{"foo"}, 575 } 576 if !hc.IsSame(hc) { 577 t.Fatalf("should be equal to itself") 578 } 579 580 other := &HealthCheck{ 581 Node: "node1", 582 CheckID: "check1", 583 Name: "thecheck", 584 Status: api.HealthPassing, 585 Notes: "it's all good", 586 Output: "lgtm", 587 ServiceID: "service1", 588 ServiceName: "theservice", 589 ServiceTags: []string{"foo"}, 590 RaftIndex: RaftIndex{ 591 CreateIndex: 1, 592 ModifyIndex: 2, 593 }, 594 } 595 if !hc.IsSame(other) || !other.IsSame(hc) { 596 t.Fatalf("should not care about Raft fields") 597 } 598 599 checkCheckIDField := func(field *types.CheckID) { 600 if !hc.IsSame(other) || !other.IsSame(hc) { 601 t.Fatalf("should be the same") 602 } 603 604 old := *field 605 *field = "XXX" 606 if hc.IsSame(other) || other.IsSame(hc) { 607 t.Fatalf("should not be the same") 608 } 609 *field = old 610 611 if !hc.IsSame(other) || !other.IsSame(hc) { 612 t.Fatalf("should be the same") 613 } 614 } 615 616 checkStringField := func(field *string) { 617 if !hc.IsSame(other) || !other.IsSame(hc) { 618 t.Fatalf("should be the same") 619 } 620 621 old := *field 622 *field = "XXX" 623 if hc.IsSame(other) || other.IsSame(hc) { 624 t.Fatalf("should not be the same") 625 } 626 *field = old 627 628 if !hc.IsSame(other) || !other.IsSame(hc) { 629 t.Fatalf("should be the same") 630 } 631 } 632 633 checkStringField(&other.Node) 634 checkCheckIDField(&other.CheckID) 635 checkStringField(&other.Name) 636 checkStringField(&other.Status) 637 checkStringField(&other.Notes) 638 checkStringField(&other.Output) 639 checkStringField(&other.ServiceID) 640 checkStringField(&other.ServiceName) 641 } 642 643 func TestStructs_HealthCheck_Marshalling(t *testing.T) { 644 d := &HealthCheckDefinition{} 645 buf, err := d.MarshalJSON() 646 require.NoError(t, err) 647 require.NotContains(t, string(buf), `"Interval":""`) 648 require.NotContains(t, string(buf), `"Timeout":""`) 649 require.NotContains(t, string(buf), `"DeregisterCriticalServiceAfter":""`) 650 } 651 652 func TestStructs_HealthCheck_Clone(t *testing.T) { 653 hc := &HealthCheck{ 654 Node: "node1", 655 CheckID: "check1", 656 Name: "thecheck", 657 Status: api.HealthPassing, 658 Notes: "it's all good", 659 Output: "lgtm", 660 ServiceID: "service1", 661 ServiceName: "theservice", 662 } 663 clone := hc.Clone() 664 if !hc.IsSame(clone) { 665 t.Fatalf("should be equal to its clone") 666 } 667 668 clone.Output = "different" 669 if hc.IsSame(clone) { 670 t.Fatalf("should not longer be equal to its clone") 671 } 672 } 673 674 func TestStructs_CheckServiceNodes_Shuffle(t *testing.T) { 675 // Make a huge list of nodes. 676 var nodes CheckServiceNodes 677 for i := 0; i < 100; i++ { 678 nodes = append(nodes, CheckServiceNode{ 679 Node: &Node{ 680 Node: fmt.Sprintf("node%d", i), 681 Address: fmt.Sprintf("127.0.0.%d", i+1), 682 }, 683 }) 684 } 685 686 // Keep track of how many unique shuffles we get. 687 uniques := make(map[string]struct{}) 688 for i := 0; i < 100; i++ { 689 nodes.Shuffle() 690 691 var names []string 692 for _, node := range nodes { 693 names = append(names, node.Node.Node) 694 } 695 key := strings.Join(names, "|") 696 uniques[key] = struct{}{} 697 } 698 699 // We have to allow for the fact that there won't always be a unique 700 // shuffle each pass, so we just look for smell here without the test 701 // being flaky. 702 if len(uniques) < 50 { 703 t.Fatalf("unique shuffle ratio too low: %d/100", len(uniques)) 704 } 705 } 706 707 func TestStructs_CheckServiceNodes_Filter(t *testing.T) { 708 nodes := CheckServiceNodes{ 709 CheckServiceNode{ 710 Node: &Node{ 711 Node: "node1", 712 Address: "127.0.0.1", 713 }, 714 Checks: HealthChecks{ 715 &HealthCheck{ 716 Status: api.HealthWarning, 717 }, 718 }, 719 }, 720 CheckServiceNode{ 721 Node: &Node{ 722 Node: "node2", 723 Address: "127.0.0.2", 724 }, 725 Checks: HealthChecks{ 726 &HealthCheck{ 727 Status: api.HealthPassing, 728 }, 729 }, 730 }, 731 CheckServiceNode{ 732 Node: &Node{ 733 Node: "node3", 734 Address: "127.0.0.3", 735 }, 736 Checks: HealthChecks{ 737 &HealthCheck{ 738 Status: api.HealthCritical, 739 }, 740 }, 741 }, 742 CheckServiceNode{ 743 Node: &Node{ 744 Node: "node4", 745 Address: "127.0.0.4", 746 }, 747 Checks: HealthChecks{ 748 // This check has a different ID to the others to ensure it is not 749 // ignored by accident 750 &HealthCheck{ 751 CheckID: "failing2", 752 Status: api.HealthCritical, 753 }, 754 }, 755 }, 756 } 757 758 // Test the case where warnings are allowed. 759 { 760 twiddle := make(CheckServiceNodes, len(nodes)) 761 if n := copy(twiddle, nodes); n != len(nodes) { 762 t.Fatalf("bad: %d", n) 763 } 764 filtered := twiddle.Filter(false) 765 expected := CheckServiceNodes{ 766 nodes[0], 767 nodes[1], 768 } 769 if !reflect.DeepEqual(filtered, expected) { 770 t.Fatalf("bad: %v", filtered) 771 } 772 } 773 774 // Limit to only passing checks. 775 { 776 twiddle := make(CheckServiceNodes, len(nodes)) 777 if n := copy(twiddle, nodes); n != len(nodes) { 778 t.Fatalf("bad: %d", n) 779 } 780 filtered := twiddle.Filter(true) 781 expected := CheckServiceNodes{ 782 nodes[1], 783 } 784 if !reflect.DeepEqual(filtered, expected) { 785 t.Fatalf("bad: %v", filtered) 786 } 787 } 788 789 // Allow failing checks to be ignored (note that the test checks have empty 790 // CheckID which is valid). 791 { 792 twiddle := make(CheckServiceNodes, len(nodes)) 793 if n := copy(twiddle, nodes); n != len(nodes) { 794 t.Fatalf("bad: %d", n) 795 } 796 filtered := twiddle.FilterIgnore(true, []types.CheckID{""}) 797 expected := CheckServiceNodes{ 798 nodes[0], 799 nodes[1], 800 nodes[2], // Node 3's critical check should be ignored. 801 // Node 4 should still be failing since it's got a critical check with a 802 // non-ignored ID. 803 } 804 if !reflect.DeepEqual(filtered, expected) { 805 t.Fatalf("bad: %v", filtered) 806 } 807 } 808 } 809 810 func TestStructs_DirEntry_Clone(t *testing.T) { 811 e := &DirEntry{ 812 LockIndex: 5, 813 Key: "hello", 814 Flags: 23, 815 Value: []byte("this is a test"), 816 Session: "session1", 817 RaftIndex: RaftIndex{ 818 CreateIndex: 1, 819 ModifyIndex: 2, 820 }, 821 } 822 823 clone := e.Clone() 824 if !reflect.DeepEqual(e, clone) { 825 t.Fatalf("bad: %v", clone) 826 } 827 828 e.Value = []byte("a new value") 829 if reflect.DeepEqual(e, clone) { 830 t.Fatalf("clone wasn't independent of the original") 831 } 832 } 833 834 func TestStructs_ValidateMetadata(t *testing.T) { 835 // Load a valid set of key/value pairs 836 meta := map[string]string{ 837 "key1": "value1", 838 "key2": "value2", 839 } 840 // Should succeed 841 if err := ValidateMetadata(meta, false); err != nil { 842 t.Fatalf("err: %s", err) 843 } 844 845 // Should get error 846 meta = map[string]string{ 847 "": "value1", 848 } 849 if err := ValidateMetadata(meta, false); !strings.Contains(err.Error(), "Couldn't load metadata pair") { 850 t.Fatalf("should have failed") 851 } 852 853 // Should get error 854 meta = make(map[string]string) 855 for i := 0; i < metaMaxKeyPairs+1; i++ { 856 meta[string(i)] = "value" 857 } 858 if err := ValidateMetadata(meta, false); !strings.Contains(err.Error(), "cannot contain more than") { 859 t.Fatalf("should have failed") 860 } 861 862 // Should not error 863 meta = map[string]string{ 864 metaKeyReservedPrefix + "key": "value1", 865 } 866 // Should fail 867 if err := ValidateMetadata(meta, false); err == nil || !strings.Contains(err.Error(), "reserved for internal use") { 868 t.Fatalf("err: %s", err) 869 } 870 // Should succeed 871 if err := ValidateMetadata(meta, true); err != nil { 872 t.Fatalf("err: %s", err) 873 } 874 } 875 876 func TestStructs_validateMetaPair(t *testing.T) { 877 longKey := strings.Repeat("a", metaKeyMaxLength+1) 878 longValue := strings.Repeat("b", metaValueMaxLength+1) 879 pairs := []struct { 880 Key string 881 Value string 882 Error string 883 AllowConsulPrefix bool 884 }{ 885 // valid pair 886 {"key", "value", "", false}, 887 // invalid, blank key 888 {"", "value", "cannot be blank", false}, 889 // allowed special chars in key name 890 {"k_e-y", "value", "", false}, 891 // disallowed special chars in key name 892 {"(%key&)", "value", "invalid characters", false}, 893 // key too long 894 {longKey, "value", "Key is too long", false}, 895 // reserved prefix 896 {metaKeyReservedPrefix + "key", "value", "reserved for internal use", false}, 897 // reserved prefix, allowed 898 {metaKeyReservedPrefix + "key", "value", "", true}, 899 // value too long 900 {"key", longValue, "Value is too long", false}, 901 } 902 903 for _, pair := range pairs { 904 err := validateMetaPair(pair.Key, pair.Value, pair.AllowConsulPrefix) 905 if pair.Error == "" && err != nil { 906 t.Fatalf("should have succeeded: %v, %v", pair, err) 907 } else if pair.Error != "" && !strings.Contains(err.Error(), pair.Error) { 908 t.Fatalf("should have failed: %v, %v", pair, err) 909 } 910 } 911 } 912 913 func TestSpecificServiceRequest_CacheInfo(t *testing.T) { 914 tests := []struct { 915 name string 916 req ServiceSpecificRequest 917 mutate func(req *ServiceSpecificRequest) 918 want *cache.RequestInfo 919 wantSame bool 920 }{ 921 { 922 name: "basic params", 923 req: ServiceSpecificRequest{ 924 QueryOptions: QueryOptions{Token: "foo"}, 925 Datacenter: "dc1", 926 }, 927 want: &cache.RequestInfo{ 928 Token: "foo", 929 Datacenter: "dc1", 930 }, 931 wantSame: true, 932 }, 933 { 934 name: "name should be considered", 935 req: ServiceSpecificRequest{ 936 ServiceName: "web", 937 }, 938 mutate: func(req *ServiceSpecificRequest) { 939 req.ServiceName = "db" 940 }, 941 wantSame: false, 942 }, 943 { 944 name: "node meta should be considered", 945 req: ServiceSpecificRequest{ 946 NodeMetaFilters: map[string]string{ 947 "foo": "bar", 948 }, 949 }, 950 mutate: func(req *ServiceSpecificRequest) { 951 req.NodeMetaFilters = map[string]string{ 952 "foo": "qux", 953 } 954 }, 955 wantSame: false, 956 }, 957 { 958 name: "address should be considered", 959 req: ServiceSpecificRequest{ 960 ServiceAddress: "1.2.3.4", 961 }, 962 mutate: func(req *ServiceSpecificRequest) { 963 req.ServiceAddress = "4.3.2.1" 964 }, 965 wantSame: false, 966 }, 967 { 968 name: "tag filter should be considered", 969 req: ServiceSpecificRequest{ 970 TagFilter: true, 971 }, 972 mutate: func(req *ServiceSpecificRequest) { 973 req.TagFilter = false 974 }, 975 wantSame: false, 976 }, 977 { 978 name: "connect should be considered", 979 req: ServiceSpecificRequest{ 980 Connect: true, 981 }, 982 mutate: func(req *ServiceSpecificRequest) { 983 req.Connect = false 984 }, 985 wantSame: false, 986 }, 987 { 988 name: "tags should be different", 989 req: ServiceSpecificRequest{ 990 ServiceName: "web", 991 ServiceTags: []string{"foo"}, 992 }, 993 mutate: func(req *ServiceSpecificRequest) { 994 req.ServiceTags = []string{"foo", "bar"} 995 }, 996 wantSame: false, 997 }, 998 { 999 name: "tags should not depend on order", 1000 req: ServiceSpecificRequest{ 1001 ServiceName: "web", 1002 ServiceTags: []string{"bar", "foo"}, 1003 }, 1004 mutate: func(req *ServiceSpecificRequest) { 1005 req.ServiceTags = []string{"foo", "bar"} 1006 }, 1007 wantSame: true, 1008 }, 1009 // DEPRECATED (singular-service-tag) - remove this when upgrade RPC compat 1010 // with 1.2.x is not required. 1011 { 1012 name: "legacy requests with singular tag should be different", 1013 req: ServiceSpecificRequest{ 1014 ServiceName: "web", 1015 ServiceTag: "foo", 1016 }, 1017 mutate: func(req *ServiceSpecificRequest) { 1018 req.ServiceTag = "bar" 1019 }, 1020 wantSame: false, 1021 }, 1022 } 1023 1024 for _, tc := range tests { 1025 t.Run(tc.name, func(t *testing.T) { 1026 info := tc.req.CacheInfo() 1027 if tc.mutate != nil { 1028 tc.mutate(&tc.req) 1029 } 1030 afterInfo := tc.req.CacheInfo() 1031 1032 // Check key matches or not 1033 if tc.wantSame { 1034 require.Equal(t, info, afterInfo) 1035 } else { 1036 require.NotEqual(t, info, afterInfo) 1037 } 1038 1039 if tc.want != nil { 1040 // Reset key since we don't care about the actual hash value as long as 1041 // it does/doesn't change appropriately (asserted with wantSame above). 1042 info.Key = "" 1043 require.Equal(t, *tc.want, info) 1044 } 1045 }) 1046 } 1047 }