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  }