github.com/hernad/nomad@v1.6.112/nomad/structs/services_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package structs
     5  
     6  import (
     7  	"errors"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/hashicorp/go-multierror"
    12  	"github.com/hernad/nomad/ci"
    13  	"github.com/hernad/nomad/helper/pointer"
    14  	"github.com/shoenig/test/must"
    15  	"github.com/stretchr/testify/require"
    16  )
    17  
    18  func TestServiceCheck_Hash(t *testing.T) {
    19  	ci.Parallel(t)
    20  
    21  	original := &ServiceCheck{
    22  		Name:                   "check",
    23  		SuccessBeforePassing:   3,
    24  		FailuresBeforeCritical: 4,
    25  	}
    26  
    27  	type sc = ServiceCheck
    28  	type tweaker = func(check *sc)
    29  
    30  	hash := func(c *sc) string {
    31  		return c.Hash("ServiceID")
    32  	}
    33  
    34  	t.Run("reflexive", func(t *testing.T) {
    35  		require.Equal(t, hash(original), hash(original))
    36  	})
    37  
    38  	// these tests use tweaker to modify 1 field and make the false assertion
    39  	// on comparing the resulting hash output
    40  
    41  	try := func(t *testing.T, tweak tweaker) {
    42  		originalHash := hash(original)
    43  		modifiable := original.Copy()
    44  		tweak(modifiable)
    45  		tweakedHash := hash(modifiable)
    46  		require.NotEqual(t, originalHash, tweakedHash)
    47  	}
    48  
    49  	t.Run("name", func(t *testing.T) {
    50  		try(t, func(s *sc) { s.Name = "newName" })
    51  	})
    52  
    53  	t.Run("success_before_passing", func(t *testing.T) {
    54  		try(t, func(s *sc) { s.SuccessBeforePassing = 99 })
    55  	})
    56  
    57  	t.Run("failures_before_critical", func(t *testing.T) {
    58  		try(t, func(s *sc) { s.FailuresBeforeCritical = 99 })
    59  	})
    60  }
    61  
    62  func TestServiceCheck_Canonicalize(t *testing.T) {
    63  	ci.Parallel(t)
    64  
    65  	t.Run("defaults", func(t *testing.T) {
    66  		sc := &ServiceCheck{
    67  			Args:     []string{},
    68  			Header:   make(map[string][]string),
    69  			Method:   "",
    70  			OnUpdate: "",
    71  		}
    72  		sc.Canonicalize("MyService", "task1")
    73  		must.Nil(t, sc.Args)
    74  		must.Nil(t, sc.Header)
    75  		must.Eq(t, `service: "MyService" check`, sc.Name)
    76  		must.Eq(t, "", sc.Method)
    77  		must.Eq(t, OnUpdateRequireHealthy, sc.OnUpdate)
    78  	})
    79  
    80  	t.Run("check name set", func(t *testing.T) {
    81  		sc := &ServiceCheck{
    82  			Name: "Some Check",
    83  		}
    84  		sc.Canonicalize("MyService", "task1")
    85  		must.Eq(t, "Some Check", sc.Name)
    86  	})
    87  
    88  	t.Run("on_update is set", func(t *testing.T) {
    89  		sc := &ServiceCheck{
    90  			OnUpdate: OnUpdateIgnore,
    91  		}
    92  		sc.Canonicalize("MyService", "task1")
    93  		must.Eq(t, OnUpdateIgnore, sc.OnUpdate)
    94  	})
    95  }
    96  
    97  func TestServiceCheck_validate_PassingTypes(t *testing.T) {
    98  	ci.Parallel(t)
    99  
   100  	t.Run("valid", func(t *testing.T) {
   101  		for _, checkType := range []string{"tcp", "http", "grpc"} {
   102  			err := (&ServiceCheck{
   103  				Name:                 "check",
   104  				Type:                 checkType,
   105  				Path:                 "/path",
   106  				Interval:             1 * time.Second,
   107  				Timeout:              2 * time.Second,
   108  				SuccessBeforePassing: 3,
   109  			}).validateConsul()
   110  			require.NoError(t, err)
   111  		}
   112  	})
   113  
   114  	t.Run("invalid", func(t *testing.T) {
   115  		err := (&ServiceCheck{
   116  			Name:                 "check",
   117  			Type:                 "script",
   118  			Command:              "/nothing",
   119  			Interval:             1 * time.Second,
   120  			Timeout:              2 * time.Second,
   121  			SuccessBeforePassing: 3,
   122  		}).validateConsul()
   123  		require.EqualError(t, err, `success_before_passing not supported for check of type "script"`)
   124  	})
   125  }
   126  
   127  func TestServiceCheck_validate_FailingTypes(t *testing.T) {
   128  	ci.Parallel(t)
   129  
   130  	t.Run("valid", func(t *testing.T) {
   131  		for _, checkType := range []string{"tcp", "http", "grpc"} {
   132  			err := (&ServiceCheck{
   133  				Name:                   "check",
   134  				Type:                   checkType,
   135  				Path:                   "/path",
   136  				Interval:               1 * time.Second,
   137  				Timeout:                2 * time.Second,
   138  				FailuresBeforeCritical: 3,
   139  			}).validateConsul()
   140  			require.NoError(t, err)
   141  		}
   142  	})
   143  
   144  	t.Run("invalid", func(t *testing.T) {
   145  		err := (&ServiceCheck{
   146  			Name:                   "check",
   147  			Type:                   "script",
   148  			Command:                "/nothing",
   149  			Interval:               1 * time.Second,
   150  			Timeout:                2 * time.Second,
   151  			SuccessBeforePassing:   0,
   152  			FailuresBeforeCritical: 3,
   153  		}).validateConsul()
   154  		require.EqualError(t, err, `failures_before_critical not supported for check of type "script"`)
   155  	})
   156  }
   157  
   158  func TestServiceCheck_validate_PassFailZero_on_scripts(t *testing.T) {
   159  	ci.Parallel(t)
   160  
   161  	t.Run("invalid", func(t *testing.T) {
   162  		err := (&ServiceCheck{
   163  			Name:                   "check",
   164  			Type:                   "script",
   165  			Command:                "/nothing",
   166  			Interval:               1 * time.Second,
   167  			Timeout:                2 * time.Second,
   168  			SuccessBeforePassing:   0, // script checks should still pass validation
   169  			FailuresBeforeCritical: 0, // script checks should still pass validation
   170  		}).validateConsul()
   171  		require.NoError(t, err)
   172  	})
   173  }
   174  
   175  func TestServiceCheck_validate_OnUpdate_CheckRestart_Conflict(t *testing.T) {
   176  	ci.Parallel(t)
   177  
   178  	t.Run("invalid", func(t *testing.T) {
   179  		err := (&ServiceCheck{
   180  			Name:     "check",
   181  			Type:     "script",
   182  			Command:  "/nothing",
   183  			Interval: 1 * time.Second,
   184  			Timeout:  2 * time.Second,
   185  			CheckRestart: &CheckRestart{
   186  				IgnoreWarnings: false,
   187  				Limit:          3,
   188  				Grace:          5 * time.Second,
   189  			},
   190  			OnUpdate: OnUpdateIgnoreWarn,
   191  		}).validateConsul()
   192  		require.EqualError(t, err, `on_update value "ignore_warnings" not supported with check_restart ignore_warnings value "false"`)
   193  	})
   194  
   195  	t.Run("invalid", func(t *testing.T) {
   196  		err := (&ServiceCheck{
   197  			Name:     "check",
   198  			Type:     "script",
   199  			Command:  "/nothing",
   200  			Interval: 1 * time.Second,
   201  			Timeout:  2 * time.Second,
   202  			CheckRestart: &CheckRestart{
   203  				IgnoreWarnings: false,
   204  				Limit:          3,
   205  				Grace:          5 * time.Second,
   206  			},
   207  			OnUpdate: OnUpdateIgnore,
   208  		}).validateConsul()
   209  		require.EqualError(t, err, `on_update value "ignore" is not compatible with check_restart`)
   210  	})
   211  
   212  	t.Run("valid", func(t *testing.T) {
   213  		err := (&ServiceCheck{
   214  			Name:     "check",
   215  			Type:     "script",
   216  			Command:  "/nothing",
   217  			Interval: 1 * time.Second,
   218  			Timeout:  2 * time.Second,
   219  			CheckRestart: &CheckRestart{
   220  				IgnoreWarnings: true,
   221  				Limit:          3,
   222  				Grace:          5 * time.Second,
   223  			},
   224  			OnUpdate: OnUpdateIgnoreWarn,
   225  		}).validateConsul()
   226  		require.NoError(t, err)
   227  	})
   228  }
   229  
   230  func TestServiceCheck_validateNomad(t *testing.T) {
   231  	ci.Parallel(t)
   232  
   233  	testCases := []struct {
   234  		name string
   235  		sc   *ServiceCheck
   236  		exp  string
   237  	}{
   238  		{name: "grpc", sc: &ServiceCheck{Type: ServiceCheckGRPC}, exp: `invalid check type ("grpc"), must be one of tcp, http`},
   239  		{name: "script", sc: &ServiceCheck{Type: ServiceCheckScript}, exp: `invalid check type ("script"), must be one of tcp, http`},
   240  		{
   241  			name: "expose",
   242  			sc: &ServiceCheck{
   243  				Type:     ServiceCheckTCP,
   244  				Expose:   true, // consul only
   245  				Interval: 3 * time.Second,
   246  				Timeout:  1 * time.Second,
   247  			},
   248  			exp: `expose may only be set for Consul service checks`,
   249  		}, {
   250  			name: "on_update ignore_warnings",
   251  			sc: &ServiceCheck{
   252  				Type:     ServiceCheckTCP,
   253  				Interval: 3 * time.Second,
   254  				Timeout:  1 * time.Second,
   255  				OnUpdate: OnUpdateIgnoreWarn,
   256  			},
   257  			exp: `on_update may only be set to ignore_warnings for Consul service checks`,
   258  		},
   259  		{
   260  			name: "success_before_passing",
   261  			sc: &ServiceCheck{
   262  				Type:                 ServiceCheckTCP,
   263  				SuccessBeforePassing: 3, // consul only
   264  				Interval:             3 * time.Second,
   265  				Timeout:              1 * time.Second,
   266  			},
   267  			exp: `success_before_passing may only be set for Consul service checks`,
   268  		},
   269  		{
   270  			name: "failures_before_critical",
   271  			sc: &ServiceCheck{
   272  				Type:                   ServiceCheckTCP,
   273  				FailuresBeforeCritical: 3, // consul only
   274  				Interval:               3 * time.Second,
   275  				Timeout:                1 * time.Second,
   276  			},
   277  			exp: `failures_before_critical may only be set for Consul service checks`,
   278  		},
   279  		{
   280  			name: "check_restart",
   281  			sc: &ServiceCheck{
   282  				Type:         ServiceCheckTCP,
   283  				Interval:     3 * time.Second,
   284  				Timeout:      1 * time.Second,
   285  				CheckRestart: new(CheckRestart),
   286  			},
   287  		},
   288  		{
   289  			name: "check_restart ignore_warnings",
   290  			sc: &ServiceCheck{
   291  				Type:     ServiceCheckTCP,
   292  				Interval: 3 * time.Second,
   293  				Timeout:  1 * time.Second,
   294  				CheckRestart: &CheckRestart{
   295  					IgnoreWarnings: true,
   296  				},
   297  			},
   298  			exp: `ignore_warnings on check_restart only supported for Consul service checks`,
   299  		},
   300  		{
   301  			name: "address mode driver",
   302  			sc: &ServiceCheck{
   303  				Type:        ServiceCheckTCP,
   304  				Interval:    3 * time.Second,
   305  				Timeout:     1 * time.Second,
   306  				AddressMode: "driver",
   307  			},
   308  			exp: `address_mode = driver may only be set for Consul service checks`,
   309  		},
   310  		{
   311  			name: "http non GET",
   312  			sc: &ServiceCheck{
   313  				Type:     ServiceCheckHTTP,
   314  				Interval: 3 * time.Second,
   315  				Timeout:  1 * time.Second,
   316  				Path:     "/health",
   317  				Method:   "HEAD",
   318  			},
   319  		},
   320  		{
   321  			name: "http unknown method type",
   322  			sc: &ServiceCheck{
   323  				Type:     ServiceCheckHTTP,
   324  				Interval: 3 * time.Second,
   325  				Timeout:  1 * time.Second,
   326  				Path:     "/health",
   327  				Method:   "Invalid",
   328  			},
   329  			exp: `method type "Invalid" not supported in Nomad http check`,
   330  		},
   331  		{
   332  			name: "http with headers",
   333  			sc: &ServiceCheck{
   334  				Type:     ServiceCheckHTTP,
   335  				Interval: 3 * time.Second,
   336  				Timeout:  1 * time.Second,
   337  				Path:     "/health",
   338  				Method:   "GET",
   339  				Header: map[string][]string{
   340  					"foo": {"bar"},
   341  					"baz": nil,
   342  				},
   343  			},
   344  		},
   345  		{
   346  			name: "http with body",
   347  			sc: &ServiceCheck{
   348  				Type:     ServiceCheckHTTP,
   349  				Interval: 3 * time.Second,
   350  				Timeout:  1 * time.Second,
   351  				Path:     "/health",
   352  				Method:   "POST",
   353  				Body:     "this is a request payload!",
   354  			},
   355  		},
   356  		{
   357  			name: "http with tls_server_name",
   358  			sc: &ServiceCheck{
   359  				Type:          ServiceCheckHTTP,
   360  				Interval:      3 * time.Second,
   361  				Timeout:       1 * time.Second,
   362  				Path:          "/health",
   363  				TLSServerName: "foo",
   364  			},
   365  			exp: `tls_server_name may only be set for Consul service checks`,
   366  		},
   367  	}
   368  
   369  	for _, testCase := range testCases {
   370  		t.Run(testCase.name, func(t *testing.T) {
   371  			err := testCase.sc.validateNomad()
   372  			if testCase.exp == "" {
   373  				must.NoError(t, err)
   374  			} else {
   375  				must.EqError(t, err, testCase.exp)
   376  			}
   377  		})
   378  	}
   379  }
   380  
   381  func TestService_Hash(t *testing.T) {
   382  	ci.Parallel(t)
   383  
   384  	original := &Service{
   385  		Name:      "myService",
   386  		PortLabel: "portLabel",
   387  		// AddressMode: "bridge", // not hashed (used internally by Nomad)
   388  		Tags:       []string{"original", "tags"},
   389  		CanaryTags: []string{"canary", "tags"},
   390  		// Checks:      nil, // not hashed (managed independently)
   391  		Connect: &ConsulConnect{
   392  			// Native: false, // not hashed
   393  			SidecarService: &ConsulSidecarService{
   394  				Tags: []string{"original", "sidecar", "tags"},
   395  				Port: "9000",
   396  				Proxy: &ConsulProxy{
   397  					LocalServiceAddress: "127.0.0.1",
   398  					LocalServicePort:    24000,
   399  					Config:              map[string]any{"foo": "bar"},
   400  					Upstreams: []ConsulUpstream{{
   401  						DestinationName:      "upstream1",
   402  						DestinationNamespace: "ns2",
   403  						LocalBindPort:        29000,
   404  						Config:               map[string]any{"foo": "bar"},
   405  					}},
   406  				},
   407  				Meta: map[string]string{
   408  					"test-key": "test-value",
   409  				},
   410  			},
   411  			// SidecarTask: nil // not hashed
   412  		}}
   413  
   414  	type svc = Service
   415  	type tweaker = func(service *svc)
   416  
   417  	hash := func(s *svc, canary bool) string {
   418  		return s.Hash("AllocID", "TaskName", canary)
   419  	}
   420  
   421  	t.Run("matching and is canary", func(t *testing.T) {
   422  		require.Equal(t, hash(original, true), hash(original, true))
   423  	})
   424  
   425  	t.Run("matching and is not canary", func(t *testing.T) {
   426  		require.Equal(t, hash(original, false), hash(original, false))
   427  	})
   428  
   429  	t.Run("matching mod canary", func(t *testing.T) {
   430  		require.NotEqual(t, hash(original, true), hash(original, false))
   431  	})
   432  
   433  	try := func(t *testing.T, tweak tweaker) {
   434  		originalHash := hash(original, true)
   435  		modifiable := original.Copy()
   436  		tweak(modifiable)
   437  		tweakedHash := hash(modifiable, true)
   438  		require.NotEqual(t, originalHash, tweakedHash)
   439  	}
   440  
   441  	// these tests use tweaker to modify 1 field and make the false assertion
   442  	// on comparing the resulting hash output
   443  
   444  	t.Run("mod address", func(t *testing.T) {
   445  		try(t, func(s *svc) { s.Address = "example.com" })
   446  	})
   447  
   448  	t.Run("mod name", func(t *testing.T) {
   449  		try(t, func(s *svc) { s.Name = "newName" })
   450  	})
   451  
   452  	t.Run("mod port label", func(t *testing.T) {
   453  		try(t, func(s *svc) { s.PortLabel = "newPortLabel" })
   454  	})
   455  
   456  	t.Run("mod tags", func(t *testing.T) {
   457  		try(t, func(s *svc) { s.Tags = []string{"new", "tags"} })
   458  	})
   459  
   460  	t.Run("mod canary tags", func(t *testing.T) {
   461  		try(t, func(s *svc) { s.CanaryTags = []string{"new", "tags"} })
   462  	})
   463  
   464  	t.Run("mod enable tag override", func(t *testing.T) {
   465  		try(t, func(s *svc) { s.EnableTagOverride = true })
   466  	})
   467  
   468  	t.Run("mod connect sidecar tags", func(t *testing.T) {
   469  		try(t, func(s *svc) { s.Connect.SidecarService.Tags = []string{"new", "tags"} })
   470  	})
   471  
   472  	t.Run("mod connect sidecar port", func(t *testing.T) {
   473  		try(t, func(s *svc) { s.Connect.SidecarService.Port = "9090" })
   474  	})
   475  
   476  	t.Run("mod connect sidecar proxy local service address", func(t *testing.T) {
   477  		try(t, func(s *svc) { s.Connect.SidecarService.Proxy.LocalServiceAddress = "1.1.1.1" })
   478  	})
   479  
   480  	t.Run("mod connect sidecar proxy local service port", func(t *testing.T) {
   481  		try(t, func(s *svc) { s.Connect.SidecarService.Proxy.LocalServicePort = 9999 })
   482  	})
   483  
   484  	t.Run("mod connect sidecar proxy config", func(t *testing.T) {
   485  		try(t, func(s *svc) { s.Connect.SidecarService.Proxy.Config = map[string]interface{}{"foo": "baz"} })
   486  	})
   487  
   488  	t.Run("mod connect sidecar proxy upstream destination name", func(t *testing.T) {
   489  		try(t, func(s *svc) { s.Connect.SidecarService.Proxy.Upstreams[0].DestinationName = "dest2" })
   490  	})
   491  
   492  	t.Run("mod connect sidecar proxy upstream destination namespace", func(t *testing.T) {
   493  		try(t, func(s *svc) { s.Connect.SidecarService.Proxy.Upstreams[0].DestinationNamespace = "ns3" })
   494  	})
   495  
   496  	t.Run("mod connect sidecar proxy upstream destination local bind port", func(t *testing.T) {
   497  		try(t, func(s *svc) { s.Connect.SidecarService.Proxy.Upstreams[0].LocalBindPort = 29999 })
   498  	})
   499  
   500  	t.Run("mod connect sidecar proxy upstream config", func(t *testing.T) {
   501  		try(t, func(s *svc) { s.Connect.SidecarService.Proxy.Upstreams[0].Config = map[string]any{"foo": "baz"} })
   502  	})
   503  }
   504  
   505  func TestConsulConnect_Validate(t *testing.T) {
   506  	ci.Parallel(t)
   507  
   508  	c := &ConsulConnect{}
   509  
   510  	// An empty Connect block is invalid
   511  	require.Error(t, c.Validate())
   512  
   513  	c.Native = true
   514  	require.NoError(t, c.Validate())
   515  
   516  	// Native=true + Sidecar!=nil is invalid
   517  	c.SidecarService = &ConsulSidecarService{}
   518  	require.Error(t, c.Validate())
   519  
   520  	c.Native = false
   521  	require.NoError(t, c.Validate())
   522  }
   523  
   524  func TestConsulConnect_CopyEqual(t *testing.T) {
   525  	ci.Parallel(t)
   526  
   527  	c := &ConsulConnect{
   528  		SidecarService: &ConsulSidecarService{
   529  			Tags: []string{"tag1", "tag2"},
   530  			Port: "9001",
   531  			Proxy: &ConsulProxy{
   532  				LocalServiceAddress: "127.0.0.1",
   533  				LocalServicePort:    8080,
   534  				Upstreams: []ConsulUpstream{
   535  					{
   536  						DestinationName:      "up1",
   537  						DestinationNamespace: "ns2",
   538  						LocalBindPort:        9002,
   539  					},
   540  					{
   541  						DestinationName:      "up2",
   542  						DestinationNamespace: "ns2",
   543  						LocalBindPort:        9003,
   544  					},
   545  				},
   546  				Config: map[string]interface{}{
   547  					"foo": 1,
   548  				},
   549  			},
   550  			Meta: map[string]string{
   551  				"test-key": "test-value",
   552  			},
   553  		},
   554  	}
   555  
   556  	require.NoError(t, c.Validate())
   557  
   558  	// Copies should be equivalent
   559  	o := c.Copy()
   560  	require.True(t, c.Equal(o))
   561  
   562  	o.SidecarService.Proxy.Upstreams = nil
   563  	require.False(t, c.Equal(o))
   564  }
   565  
   566  func TestConsulConnect_GatewayProxy_CopyEqual(t *testing.T) {
   567  	ci.Parallel(t)
   568  
   569  	c := &ConsulGatewayProxy{
   570  		ConnectTimeout:                  pointer.Of(1 * time.Second),
   571  		EnvoyGatewayBindTaggedAddresses: false,
   572  		EnvoyGatewayBindAddresses:       make(map[string]*ConsulGatewayBindAddress),
   573  	}
   574  
   575  	require.NoError(t, c.Validate())
   576  
   577  	// Copies should be equivalent
   578  	o := c.Copy()
   579  	require.Equal(t, c, o)
   580  	require.True(t, c.Equal(o))
   581  }
   582  
   583  func TestSidecarTask_MergeIntoTask(t *testing.T) {
   584  	ci.Parallel(t)
   585  
   586  	task := MockJob().TaskGroups[0].Tasks[0]
   587  	sTask := &SidecarTask{
   588  		Name:   "sidecar",
   589  		Driver: "sidecar",
   590  		User:   "test",
   591  		Config: map[string]interface{}{
   592  			"foo": "bar",
   593  		},
   594  		Resources: &Resources{
   595  			CPU:      10000,
   596  			MemoryMB: 10000,
   597  		},
   598  		Env: map[string]string{
   599  			"sidecar": "proxy",
   600  		},
   601  		Meta: map[string]string{
   602  			"abc": "123",
   603  		},
   604  		KillTimeout: pointer.Of(15 * time.Second),
   605  		LogConfig: &LogConfig{
   606  			MaxFiles: 3,
   607  		},
   608  		ShutdownDelay: pointer.Of(5 * time.Second),
   609  		KillSignal:    "SIGABRT",
   610  	}
   611  
   612  	expected := task.Copy()
   613  	expected.Name = "sidecar"
   614  	expected.Driver = "sidecar"
   615  	expected.User = "test"
   616  	expected.Config = map[string]interface{}{
   617  		"foo": "bar",
   618  	}
   619  	expected.Resources.CPU = 10000
   620  	expected.Resources.MemoryMB = 10000
   621  	expected.Env["sidecar"] = "proxy"
   622  	expected.Meta["abc"] = "123"
   623  	expected.KillTimeout = 15 * time.Second
   624  	expected.LogConfig.MaxFiles = 3
   625  	expected.ShutdownDelay = 5 * time.Second
   626  	expected.KillSignal = "SIGABRT"
   627  
   628  	sTask.MergeIntoTask(task)
   629  	require.Exactly(t, expected, task)
   630  
   631  	// Check that changing just driver config doesn't replace map
   632  	sTask.Config["abc"] = 123
   633  	expected.Config["abc"] = 123
   634  
   635  	sTask.MergeIntoTask(task)
   636  	require.Exactly(t, expected, task)
   637  }
   638  
   639  func TestSidecarTask_Equal(t *testing.T) {
   640  	ci.Parallel(t)
   641  
   642  	original := &SidecarTask{
   643  		Name:        "sidecar-task-1",
   644  		Driver:      "docker",
   645  		User:        "nobody",
   646  		Config:      map[string]interface{}{"foo": 1},
   647  		Env:         map[string]string{"color": "blue"},
   648  		Resources:   &Resources{MemoryMB: 300},
   649  		Meta:        map[string]string{"index": "1"},
   650  		KillTimeout: pointer.Of(2 * time.Second),
   651  		LogConfig: &LogConfig{
   652  			MaxFiles:      2,
   653  			MaxFileSizeMB: 300,
   654  		},
   655  		ShutdownDelay: pointer.Of(10 * time.Second),
   656  		KillSignal:    "SIGTERM",
   657  	}
   658  
   659  	t.Run("unmodified", func(t *testing.T) {
   660  		duplicate := original.Copy()
   661  		require.True(t, duplicate.Equal(original))
   662  	})
   663  
   664  	type st = SidecarTask
   665  	type tweaker = func(task *st)
   666  
   667  	try := func(t *testing.T, tweak tweaker) {
   668  		modified := original.Copy()
   669  		tweak(modified)
   670  		require.NotEqual(t, original, modified)
   671  	}
   672  
   673  	t.Run("mod name", func(t *testing.T) {
   674  		try(t, func(s *st) { s.Name = "sidecar-task-2" })
   675  	})
   676  
   677  	t.Run("mod driver", func(t *testing.T) {
   678  		try(t, func(s *st) { s.Driver = "exec" })
   679  	})
   680  
   681  	t.Run("mod user", func(t *testing.T) {
   682  		try(t, func(s *st) { s.User = "root" })
   683  	})
   684  
   685  	t.Run("mod config", func(t *testing.T) {
   686  		try(t, func(s *st) { s.Config = map[string]interface{}{"foo": 2} })
   687  	})
   688  
   689  	t.Run("mod env", func(t *testing.T) {
   690  		try(t, func(s *st) { s.Env = map[string]string{"color": "red"} })
   691  	})
   692  
   693  	t.Run("mod resources", func(t *testing.T) {
   694  		try(t, func(s *st) { s.Resources = &Resources{MemoryMB: 200} })
   695  	})
   696  
   697  	t.Run("mod meta", func(t *testing.T) {
   698  		try(t, func(s *st) { s.Meta = map[string]string{"index": "2"} })
   699  	})
   700  
   701  	t.Run("mod kill timeout", func(t *testing.T) {
   702  		try(t, func(s *st) { s.KillTimeout = pointer.Of(3 * time.Second) })
   703  	})
   704  
   705  	t.Run("mod log config", func(t *testing.T) {
   706  		try(t, func(s *st) { s.LogConfig = &LogConfig{MaxFiles: 3} })
   707  	})
   708  
   709  	t.Run("mod shutdown delay", func(t *testing.T) {
   710  		try(t, func(s *st) { s.ShutdownDelay = pointer.Of(20 * time.Second) })
   711  	})
   712  
   713  	t.Run("mod kill signal", func(t *testing.T) {
   714  		try(t, func(s *st) { s.KillSignal = "SIGHUP" })
   715  	})
   716  }
   717  
   718  func TestConsulUpstream_upstreamEqual(t *testing.T) {
   719  	ci.Parallel(t)
   720  
   721  	up := func(name string, port int) ConsulUpstream {
   722  		return ConsulUpstream{
   723  			DestinationName: name,
   724  			LocalBindPort:   port,
   725  		}
   726  	}
   727  
   728  	t.Run("size mismatch", func(t *testing.T) {
   729  		a := []ConsulUpstream{up("foo", 8000)}
   730  		b := []ConsulUpstream{up("foo", 8000), up("bar", 9000)}
   731  		must.False(t, upstreamsEquals(a, b))
   732  	})
   733  
   734  	t.Run("different", func(t *testing.T) {
   735  		a := []ConsulUpstream{up("bar", 9000)}
   736  		b := []ConsulUpstream{up("foo", 8000)}
   737  		must.False(t, upstreamsEquals(a, b))
   738  	})
   739  
   740  	t.Run("different namespace", func(t *testing.T) {
   741  		a := []ConsulUpstream{up("foo", 8000)}
   742  		a[0].DestinationNamespace = "ns1"
   743  
   744  		b := []ConsulUpstream{up("foo", 8000)}
   745  		b[0].DestinationNamespace = "ns2"
   746  
   747  		must.False(t, upstreamsEquals(a, b))
   748  	})
   749  
   750  	t.Run("different mesh_gateway", func(t *testing.T) {
   751  		a := []ConsulUpstream{{DestinationName: "foo", MeshGateway: ConsulMeshGateway{Mode: "local"}}}
   752  		b := []ConsulUpstream{{DestinationName: "foo", MeshGateway: ConsulMeshGateway{Mode: "remote"}}}
   753  		must.False(t, upstreamsEquals(a, b))
   754  	})
   755  
   756  	t.Run("different opaque config", func(t *testing.T) {
   757  		a := []ConsulUpstream{{Config: map[string]any{"foo": 1}}}
   758  		b := []ConsulUpstream{{Config: map[string]any{"foo": 2}}}
   759  		must.False(t, upstreamsEquals(a, b))
   760  	})
   761  
   762  	t.Run("identical", func(t *testing.T) {
   763  		a := []ConsulUpstream{up("foo", 8000), up("bar", 9000)}
   764  		b := []ConsulUpstream{up("foo", 8000), up("bar", 9000)}
   765  		must.True(t, upstreamsEquals(a, b))
   766  	})
   767  
   768  	t.Run("unsorted", func(t *testing.T) {
   769  		a := []ConsulUpstream{up("foo", 8000), up("bar", 9000)}
   770  		b := []ConsulUpstream{up("bar", 9000), up("foo", 8000)}
   771  		must.True(t, upstreamsEquals(a, b))
   772  	})
   773  }
   774  
   775  func TestConsulExposePath_exposePathsEqual(t *testing.T) {
   776  	ci.Parallel(t)
   777  
   778  	expose := func(path, protocol, listen string, local int) ConsulExposePath {
   779  		return ConsulExposePath{
   780  			Path:          path,
   781  			Protocol:      protocol,
   782  			LocalPathPort: local,
   783  			ListenerPort:  listen,
   784  		}
   785  	}
   786  
   787  	t.Run("size mismatch", func(t *testing.T) {
   788  		a := []ConsulExposePath{expose("/1", "http", "myPort", 8000)}
   789  		b := []ConsulExposePath{expose("/1", "http", "myPort", 8000), expose("/2", "http", "myPort", 8000)}
   790  		require.False(t, exposePathsEqual(a, b))
   791  	})
   792  
   793  	t.Run("different", func(t *testing.T) {
   794  		a := []ConsulExposePath{expose("/1", "http", "myPort", 8000)}
   795  		b := []ConsulExposePath{expose("/2", "http", "myPort", 8000)}
   796  		require.False(t, exposePathsEqual(a, b))
   797  	})
   798  
   799  	t.Run("identical", func(t *testing.T) {
   800  		a := []ConsulExposePath{expose("/1", "http", "myPort", 8000)}
   801  		b := []ConsulExposePath{expose("/1", "http", "myPort", 8000)}
   802  		require.True(t, exposePathsEqual(a, b))
   803  	})
   804  
   805  	t.Run("unsorted", func(t *testing.T) {
   806  		a := []ConsulExposePath{expose("/2", "http", "myPort", 8000), expose("/1", "http", "myPort", 8000)}
   807  		b := []ConsulExposePath{expose("/1", "http", "myPort", 8000), expose("/2", "http", "myPort", 8000)}
   808  		require.True(t, exposePathsEqual(a, b))
   809  	})
   810  }
   811  
   812  func TestConsulExposeConfig_Copy(t *testing.T) {
   813  	ci.Parallel(t)
   814  
   815  	require.Nil(t, (*ConsulExposeConfig)(nil).Copy())
   816  	require.Equal(t, &ConsulExposeConfig{
   817  		Paths: []ConsulExposePath{{
   818  			Path: "/health",
   819  		}},
   820  	}, (&ConsulExposeConfig{
   821  		Paths: []ConsulExposePath{{
   822  			Path: "/health",
   823  		}},
   824  	}).Copy())
   825  }
   826  
   827  func TestConsulExposeConfig_Equal(t *testing.T) {
   828  	ci.Parallel(t)
   829  
   830  	require.True(t, (*ConsulExposeConfig)(nil).Equal(nil))
   831  	require.True(t, (&ConsulExposeConfig{
   832  		Paths: []ConsulExposePath{{
   833  			Path: "/health",
   834  		}},
   835  	}).Equal(&ConsulExposeConfig{
   836  		Paths: []ConsulExposePath{{
   837  			Path: "/health",
   838  		}},
   839  	}))
   840  }
   841  
   842  func TestConsulSidecarService_Copy(t *testing.T) {
   843  	ci.Parallel(t)
   844  
   845  	t.Run("nil", func(t *testing.T) {
   846  		s := (*ConsulSidecarService)(nil)
   847  		result := s.Copy()
   848  		require.Nil(t, result)
   849  	})
   850  
   851  	t.Run("not nil", func(t *testing.T) {
   852  		s := &ConsulSidecarService{
   853  			Tags:  []string{"foo", "bar"},
   854  			Port:  "port1",
   855  			Proxy: &ConsulProxy{LocalServiceAddress: "10.0.0.1"},
   856  			Meta: map[string]string{
   857  				"test-key": "test-value",
   858  			},
   859  		}
   860  		result := s.Copy()
   861  		require.Equal(t, &ConsulSidecarService{
   862  			Tags:  []string{"foo", "bar"},
   863  			Port:  "port1",
   864  			Proxy: &ConsulProxy{LocalServiceAddress: "10.0.0.1"},
   865  			Meta: map[string]string{
   866  				"test-key": "test-value",
   867  			},
   868  		}, result)
   869  	})
   870  }
   871  
   872  var (
   873  	consulIngressGateway1 = &ConsulGateway{
   874  		Proxy: &ConsulGatewayProxy{
   875  			ConnectTimeout:                  pointer.Of(1 * time.Second),
   876  			EnvoyGatewayBindTaggedAddresses: true,
   877  			EnvoyGatewayBindAddresses: map[string]*ConsulGatewayBindAddress{
   878  				"listener1": {Address: "10.0.0.1", Port: 2001},
   879  				"listener2": {Address: "10.0.0.1", Port: 2002},
   880  			},
   881  			EnvoyGatewayNoDefaultBind: true,
   882  			Config: map[string]interface{}{
   883  				"foo": 1,
   884  			},
   885  		},
   886  		Ingress: &ConsulIngressConfigEntry{
   887  			TLS: &ConsulGatewayTLSConfig{
   888  				Enabled: true,
   889  			},
   890  			Listeners: []*ConsulIngressListener{{
   891  				Port:     3000,
   892  				Protocol: "http",
   893  				Services: []*ConsulIngressService{{
   894  					Name:  "service1",
   895  					Hosts: []string{"10.0.0.1", "10.0.0.1:3000"},
   896  				}, {
   897  					Name:  "service2",
   898  					Hosts: []string{"10.0.0.2", "10.0.0.2:3000"},
   899  				}},
   900  			}, {
   901  				Port:     3001,
   902  				Protocol: "tcp",
   903  				Services: []*ConsulIngressService{{
   904  					Name: "service3",
   905  				}},
   906  			}},
   907  		},
   908  	}
   909  
   910  	consulTerminatingGateway1 = &ConsulGateway{
   911  		Proxy: &ConsulGatewayProxy{
   912  			ConnectTimeout:            pointer.Of(1 * time.Second),
   913  			EnvoyDNSDiscoveryType:     "STRICT_DNS",
   914  			EnvoyGatewayBindAddresses: nil,
   915  		},
   916  		Terminating: &ConsulTerminatingConfigEntry{
   917  			Services: []*ConsulLinkedService{{
   918  				Name:     "linked-service1",
   919  				CAFile:   "ca.pem",
   920  				CertFile: "cert.pem",
   921  				KeyFile:  "key.pem",
   922  				SNI:      "service1.consul",
   923  			}, {
   924  				Name: "linked-service2",
   925  			}},
   926  		},
   927  	}
   928  
   929  	consulMeshGateway1 = &ConsulGateway{
   930  		Proxy: &ConsulGatewayProxy{
   931  			ConnectTimeout: pointer.Of(1 * time.Second),
   932  		},
   933  		Mesh: &ConsulMeshConfigEntry{
   934  			// nothing
   935  		},
   936  	}
   937  )
   938  
   939  func TestConsulGateway_Prefix(t *testing.T) {
   940  	ci.Parallel(t)
   941  
   942  	t.Run("ingress", func(t *testing.T) {
   943  		result := (&ConsulGateway{Ingress: new(ConsulIngressConfigEntry)}).Prefix()
   944  		require.Equal(t, ConnectIngressPrefix, result)
   945  	})
   946  
   947  	t.Run("terminating", func(t *testing.T) {
   948  		result := (&ConsulGateway{Terminating: new(ConsulTerminatingConfigEntry)}).Prefix()
   949  		require.Equal(t, ConnectTerminatingPrefix, result)
   950  	})
   951  
   952  	t.Run("mesh", func(t *testing.T) {
   953  		result := (&ConsulGateway{Mesh: new(ConsulMeshConfigEntry)}).Prefix()
   954  		require.Equal(t, ConnectMeshPrefix, result)
   955  	})
   956  }
   957  
   958  func TestConsulGateway_Copy(t *testing.T) {
   959  	ci.Parallel(t)
   960  
   961  	t.Run("nil", func(t *testing.T) {
   962  		g := (*ConsulGateway)(nil)
   963  		result := g.Copy()
   964  		require.Nil(t, result)
   965  	})
   966  
   967  	t.Run("as ingress", func(t *testing.T) {
   968  		result := consulIngressGateway1.Copy()
   969  		require.Equal(t, consulIngressGateway1, result)
   970  		require.True(t, result.Equal(consulIngressGateway1))
   971  		require.True(t, consulIngressGateway1.Equal(result))
   972  	})
   973  
   974  	t.Run("as terminating", func(t *testing.T) {
   975  		result := consulTerminatingGateway1.Copy()
   976  		require.Equal(t, consulTerminatingGateway1, result)
   977  		require.True(t, result.Equal(consulTerminatingGateway1))
   978  		require.True(t, consulTerminatingGateway1.Equal(result))
   979  	})
   980  
   981  	t.Run("as mesh", func(t *testing.T) {
   982  		result := consulMeshGateway1.Copy()
   983  		require.Equal(t, consulMeshGateway1, result)
   984  		require.True(t, result.Equal(consulMeshGateway1))
   985  		require.True(t, consulMeshGateway1.Equal(result))
   986  	})
   987  }
   988  
   989  func TestConsulGateway_Equal_mesh(t *testing.T) {
   990  	ci.Parallel(t)
   991  
   992  	t.Run("nil", func(t *testing.T) {
   993  		a := (*ConsulGateway)(nil)
   994  		b := (*ConsulGateway)(nil)
   995  		require.True(t, a.Equal(b))
   996  		require.False(t, a.Equal(consulMeshGateway1))
   997  		require.False(t, consulMeshGateway1.Equal(a))
   998  	})
   999  
  1000  	t.Run("reflexive", func(t *testing.T) {
  1001  		require.True(t, consulMeshGateway1.Equal(consulMeshGateway1))
  1002  	})
  1003  }
  1004  
  1005  func TestConsulGateway_Equal_ingress(t *testing.T) {
  1006  	ci.Parallel(t)
  1007  
  1008  	t.Run("nil", func(t *testing.T) {
  1009  		a := (*ConsulGateway)(nil)
  1010  		b := (*ConsulGateway)(nil)
  1011  		require.True(t, a.Equal(b))
  1012  		require.False(t, a.Equal(consulIngressGateway1))
  1013  		require.False(t, consulIngressGateway1.Equal(a))
  1014  	})
  1015  
  1016  	original := consulIngressGateway1.Copy()
  1017  
  1018  	type cg = ConsulGateway
  1019  	type tweaker = func(g *cg)
  1020  
  1021  	t.Run("reflexive", func(t *testing.T) {
  1022  		require.True(t, original.Equal(original))
  1023  	})
  1024  
  1025  	try := func(t *testing.T, tweak tweaker) {
  1026  		modifiable := original.Copy()
  1027  		tweak(modifiable)
  1028  		require.False(t, original.Equal(modifiable))
  1029  		require.False(t, modifiable.Equal(original))
  1030  		require.True(t, modifiable.Equal(modifiable))
  1031  	}
  1032  
  1033  	// proxy block equality checks
  1034  
  1035  	t.Run("mod gateway timeout", func(t *testing.T) {
  1036  		try(t, func(g *cg) { g.Proxy.ConnectTimeout = pointer.Of(9 * time.Second) })
  1037  	})
  1038  
  1039  	t.Run("mod gateway envoy_gateway_bind_tagged_addresses", func(t *testing.T) {
  1040  		try(t, func(g *cg) { g.Proxy.EnvoyGatewayBindTaggedAddresses = false })
  1041  	})
  1042  
  1043  	t.Run("mod gateway envoy_gateway_bind_addresses", func(t *testing.T) {
  1044  		try(t, func(g *cg) {
  1045  			g.Proxy.EnvoyGatewayBindAddresses = map[string]*ConsulGatewayBindAddress{
  1046  				"listener3": {Address: "9.9.9.9", Port: 9999},
  1047  			}
  1048  		})
  1049  	})
  1050  
  1051  	t.Run("mod gateway envoy_gateway_no_default_bind", func(t *testing.T) {
  1052  		try(t, func(g *cg) { g.Proxy.EnvoyGatewayNoDefaultBind = false })
  1053  	})
  1054  
  1055  	t.Run("mod gateway config", func(t *testing.T) {
  1056  		try(t, func(g *cg) {
  1057  			g.Proxy.Config = map[string]interface{}{
  1058  				"foo": 2,
  1059  			}
  1060  		})
  1061  	})
  1062  
  1063  	// ingress config entry equality checks
  1064  
  1065  	t.Run("mod ingress tls", func(t *testing.T) {
  1066  		try(t, func(g *cg) { g.Ingress.TLS = nil })
  1067  		try(t, func(g *cg) { g.Ingress.TLS.Enabled = false })
  1068  	})
  1069  
  1070  	t.Run("mod ingress listeners count", func(t *testing.T) {
  1071  		try(t, func(g *cg) { g.Ingress.Listeners = g.Ingress.Listeners[:1] })
  1072  	})
  1073  
  1074  	t.Run("mod ingress listeners port", func(t *testing.T) {
  1075  		try(t, func(g *cg) { g.Ingress.Listeners[0].Port = 7777 })
  1076  	})
  1077  
  1078  	t.Run("mod ingress listeners protocol", func(t *testing.T) {
  1079  		try(t, func(g *cg) { g.Ingress.Listeners[0].Protocol = "tcp" })
  1080  	})
  1081  
  1082  	t.Run("mod ingress listeners services count", func(t *testing.T) {
  1083  		try(t, func(g *cg) { g.Ingress.Listeners[0].Services = g.Ingress.Listeners[0].Services[:1] })
  1084  	})
  1085  
  1086  	t.Run("mod ingress listeners services name", func(t *testing.T) {
  1087  		try(t, func(g *cg) { g.Ingress.Listeners[0].Services[0].Name = "serviceX" })
  1088  	})
  1089  
  1090  	t.Run("mod ingress listeners services hosts count", func(t *testing.T) {
  1091  		try(t, func(g *cg) { g.Ingress.Listeners[0].Services[0].Hosts = g.Ingress.Listeners[0].Services[0].Hosts[:1] })
  1092  	})
  1093  
  1094  	t.Run("mod ingress listeners services hosts content", func(t *testing.T) {
  1095  		try(t, func(g *cg) { g.Ingress.Listeners[0].Services[0].Hosts[0] = "255.255.255.255" })
  1096  	})
  1097  }
  1098  
  1099  func TestConsulGateway_Equal_terminating(t *testing.T) {
  1100  	ci.Parallel(t)
  1101  
  1102  	original := consulTerminatingGateway1.Copy()
  1103  
  1104  	type cg = ConsulGateway
  1105  	type tweaker = func(c *cg)
  1106  
  1107  	t.Run("reflexive", func(t *testing.T) {
  1108  		require.True(t, original.Equal(original))
  1109  	})
  1110  
  1111  	try := func(t *testing.T, tweak tweaker) {
  1112  		modifiable := original.Copy()
  1113  		tweak(modifiable)
  1114  		require.False(t, original.Equal(modifiable))
  1115  		require.False(t, modifiable.Equal(original))
  1116  		require.True(t, modifiable.Equal(modifiable))
  1117  	}
  1118  
  1119  	// proxy block equality checks
  1120  
  1121  	t.Run("mod dns discovery type", func(t *testing.T) {
  1122  		try(t, func(g *cg) { g.Proxy.EnvoyDNSDiscoveryType = "LOGICAL_DNS" })
  1123  	})
  1124  
  1125  	// terminating config entry equality checks
  1126  
  1127  	t.Run("mod terminating services count", func(t *testing.T) {
  1128  		try(t, func(g *cg) { g.Terminating.Services = g.Terminating.Services[:1] })
  1129  	})
  1130  
  1131  	t.Run("mod terminating services name", func(t *testing.T) {
  1132  		try(t, func(g *cg) { g.Terminating.Services[0].Name = "foo" })
  1133  	})
  1134  
  1135  	t.Run("mod terminating services ca_file", func(t *testing.T) {
  1136  		try(t, func(g *cg) { g.Terminating.Services[0].CAFile = "foo.pem" })
  1137  	})
  1138  
  1139  	t.Run("mod terminating services cert_file", func(t *testing.T) {
  1140  		try(t, func(g *cg) { g.Terminating.Services[0].CertFile = "foo.pem" })
  1141  	})
  1142  
  1143  	t.Run("mod terminating services key_file", func(t *testing.T) {
  1144  		try(t, func(g *cg) { g.Terminating.Services[0].KeyFile = "foo.pem" })
  1145  	})
  1146  
  1147  	t.Run("mod terminating services sni", func(t *testing.T) {
  1148  		try(t, func(g *cg) { g.Terminating.Services[0].SNI = "foo.consul" })
  1149  	})
  1150  }
  1151  
  1152  func TestConsulGateway_ingressServicesEqual(t *testing.T) {
  1153  	ci.Parallel(t)
  1154  
  1155  	igs1 := []*ConsulIngressService{{
  1156  		Name:  "service1",
  1157  		Hosts: []string{"host1", "host2"},
  1158  	}, {
  1159  		Name:  "service2",
  1160  		Hosts: []string{"host3"},
  1161  	}}
  1162  
  1163  	require.False(t, ingressServicesEqual(igs1, nil))
  1164  	require.True(t, ingressServicesEqual(igs1, igs1))
  1165  
  1166  	reversed := []*ConsulIngressService{
  1167  		igs1[1], igs1[0], // services reversed
  1168  	}
  1169  
  1170  	require.True(t, ingressServicesEqual(igs1, reversed))
  1171  
  1172  	hostOrder := []*ConsulIngressService{{
  1173  		Name:  "service1",
  1174  		Hosts: []string{"host2", "host1"}, // hosts reversed
  1175  	}, {
  1176  		Name:  "service2",
  1177  		Hosts: []string{"host3"},
  1178  	}}
  1179  
  1180  	require.True(t, ingressServicesEqual(igs1, hostOrder))
  1181  }
  1182  
  1183  func TestConsulGateway_ingressListenersEqual(t *testing.T) {
  1184  	ci.Parallel(t)
  1185  
  1186  	ils1 := []*ConsulIngressListener{{
  1187  		Port:     2000,
  1188  		Protocol: "http",
  1189  		Services: []*ConsulIngressService{{
  1190  			Name:  "service1",
  1191  			Hosts: []string{"host1", "host2"},
  1192  		}},
  1193  	}, {
  1194  		Port:     2001,
  1195  		Protocol: "tcp",
  1196  		Services: []*ConsulIngressService{{
  1197  			Name: "service2",
  1198  		}},
  1199  	}}
  1200  
  1201  	require.False(t, ingressListenersEqual(ils1, nil))
  1202  
  1203  	reversed := []*ConsulIngressListener{
  1204  		ils1[1], ils1[0],
  1205  	}
  1206  
  1207  	require.True(t, ingressListenersEqual(ils1, reversed))
  1208  }
  1209  
  1210  func TestConsulGateway_Validate(t *testing.T) {
  1211  	ci.Parallel(t)
  1212  
  1213  	t.Run("bad proxy", func(t *testing.T) {
  1214  		err := (&ConsulGateway{
  1215  			Proxy: &ConsulGatewayProxy{
  1216  				ConnectTimeout: nil,
  1217  			},
  1218  			Ingress: nil,
  1219  		}).Validate()
  1220  		require.EqualError(t, err, "Consul Gateway Proxy connection_timeout must be set")
  1221  	})
  1222  
  1223  	t.Run("bad ingress config entry", func(t *testing.T) {
  1224  		err := (&ConsulGateway{
  1225  			Ingress: &ConsulIngressConfigEntry{
  1226  				Listeners: nil,
  1227  			},
  1228  		}).Validate()
  1229  		require.EqualError(t, err, "Consul Ingress Gateway requires at least one listener")
  1230  	})
  1231  
  1232  	t.Run("bad terminating config entry", func(t *testing.T) {
  1233  		err := (&ConsulGateway{
  1234  			Terminating: &ConsulTerminatingConfigEntry{
  1235  				Services: nil,
  1236  			},
  1237  		}).Validate()
  1238  		require.EqualError(t, err, "Consul Terminating Gateway requires at least one service")
  1239  	})
  1240  
  1241  	t.Run("no config entry set", func(t *testing.T) {
  1242  		err := (&ConsulGateway{
  1243  			Ingress:     nil,
  1244  			Terminating: nil,
  1245  			Mesh:        nil,
  1246  		}).Validate()
  1247  		require.EqualError(t, err, "One Consul Gateway Configuration must be set")
  1248  	})
  1249  
  1250  	t.Run("multiple config entries set", func(t *testing.T) {
  1251  		err := (&ConsulGateway{
  1252  			Ingress: &ConsulIngressConfigEntry{
  1253  				Listeners: []*ConsulIngressListener{{
  1254  					Port:     1111,
  1255  					Protocol: "tcp",
  1256  					Services: []*ConsulIngressService{{
  1257  						Name: "service1",
  1258  					}},
  1259  				}},
  1260  			},
  1261  			Terminating: &ConsulTerminatingConfigEntry{
  1262  				Services: []*ConsulLinkedService{{
  1263  					Name: "linked-service1",
  1264  				}},
  1265  			},
  1266  		}).Validate()
  1267  		require.EqualError(t, err, "One Consul Gateway Configuration must be set")
  1268  	})
  1269  
  1270  	t.Run("ok mesh", func(t *testing.T) {
  1271  		err := (&ConsulGateway{
  1272  			Mesh: new(ConsulMeshConfigEntry),
  1273  		}).Validate()
  1274  		require.NoError(t, err)
  1275  	})
  1276  }
  1277  
  1278  func TestConsulGatewayBindAddress_Validate(t *testing.T) {
  1279  	ci.Parallel(t)
  1280  
  1281  	t.Run("no address", func(t *testing.T) {
  1282  		err := (&ConsulGatewayBindAddress{
  1283  			Address: "",
  1284  			Port:    2000,
  1285  		}).Validate()
  1286  		require.EqualError(t, err, "Consul Gateway Bind Address must be set")
  1287  	})
  1288  
  1289  	t.Run("invalid port", func(t *testing.T) {
  1290  		err := (&ConsulGatewayBindAddress{
  1291  			Address: "10.0.0.1",
  1292  			Port:    0,
  1293  		}).Validate()
  1294  		require.EqualError(t, err, "Consul Gateway Bind Address must set valid Port")
  1295  	})
  1296  
  1297  	t.Run("ok", func(t *testing.T) {
  1298  		err := (&ConsulGatewayBindAddress{
  1299  			Address: "10.0.0.1",
  1300  			Port:    2000,
  1301  		}).Validate()
  1302  		require.NoError(t, err)
  1303  	})
  1304  }
  1305  
  1306  func TestConsulGatewayProxy_Validate(t *testing.T) {
  1307  	ci.Parallel(t)
  1308  
  1309  	t.Run("no timeout", func(t *testing.T) {
  1310  		err := (&ConsulGatewayProxy{
  1311  			ConnectTimeout: nil,
  1312  		}).Validate()
  1313  		require.EqualError(t, err, "Consul Gateway Proxy connection_timeout must be set")
  1314  	})
  1315  
  1316  	t.Run("invalid bind address", func(t *testing.T) {
  1317  		err := (&ConsulGatewayProxy{
  1318  			ConnectTimeout: pointer.Of(1 * time.Second),
  1319  			EnvoyGatewayBindAddresses: map[string]*ConsulGatewayBindAddress{
  1320  				"service1": {
  1321  					Address: "10.0.0.1",
  1322  					Port:    0,
  1323  				}},
  1324  		}).Validate()
  1325  		require.EqualError(t, err, "Consul Gateway Bind Address must set valid Port")
  1326  	})
  1327  
  1328  	t.Run("invalid dns discovery type", func(t *testing.T) {
  1329  		err := (&ConsulGatewayProxy{
  1330  			ConnectTimeout:        pointer.Of(1 * time.Second),
  1331  			EnvoyDNSDiscoveryType: "RANDOM_DNS",
  1332  		}).Validate()
  1333  		require.EqualError(t, err, "Consul Gateway Proxy Envoy DNS Discovery type must be STRICT_DNS or LOGICAL_DNS")
  1334  	})
  1335  
  1336  	t.Run("ok with nothing set", func(t *testing.T) {
  1337  		err := (&ConsulGatewayProxy{
  1338  			ConnectTimeout: pointer.Of(1 * time.Second),
  1339  		}).Validate()
  1340  		require.NoError(t, err)
  1341  	})
  1342  
  1343  	t.Run("ok with everything set", func(t *testing.T) {
  1344  		err := (&ConsulGatewayProxy{
  1345  			ConnectTimeout: pointer.Of(1 * time.Second),
  1346  			EnvoyGatewayBindAddresses: map[string]*ConsulGatewayBindAddress{
  1347  				"service1": {
  1348  					Address: "10.0.0.1",
  1349  					Port:    2000,
  1350  				}},
  1351  			EnvoyGatewayBindTaggedAddresses: true,
  1352  			EnvoyGatewayNoDefaultBind:       true,
  1353  		}).Validate()
  1354  		require.NoError(t, err)
  1355  	})
  1356  }
  1357  
  1358  func TestConsulIngressService_Validate(t *testing.T) {
  1359  	ci.Parallel(t)
  1360  
  1361  	t.Run("invalid name", func(t *testing.T) {
  1362  		err := (&ConsulIngressService{
  1363  			Name: "",
  1364  		}).Validate("http")
  1365  		must.EqError(t, err, "Consul Ingress Service requires a name")
  1366  	})
  1367  
  1368  	t.Run("tcp extraneous hosts", func(t *testing.T) {
  1369  		err := (&ConsulIngressService{
  1370  			Name:  "service1",
  1371  			Hosts: []string{"host1"},
  1372  		}).Validate("tcp")
  1373  		must.EqError(t, err, `Consul Ingress Service doesn't support associating hosts to a service for the "tcp" protocol`)
  1374  	})
  1375  
  1376  	t.Run("tcp ok", func(t *testing.T) {
  1377  		err := (&ConsulIngressService{
  1378  			Name: "service1",
  1379  		}).Validate("tcp")
  1380  		must.NoError(t, err)
  1381  	})
  1382  
  1383  	t.Run("tcp with wildcard service", func(t *testing.T) {
  1384  		err := (&ConsulIngressService{
  1385  			Name: "*",
  1386  		}).Validate("tcp")
  1387  		must.EqError(t, err, `Consul Ingress Service doesn't support wildcard name for "tcp" protocol`)
  1388  	})
  1389  
  1390  	// non-"tcp" protocols should be all treated the same.
  1391  	for _, proto := range []string{"http", "http2", "grpc"} {
  1392  		t.Run(proto+" ok", func(t *testing.T) {
  1393  			err := (&ConsulIngressService{
  1394  				Name:  "service1",
  1395  				Hosts: []string{"host1"},
  1396  			}).Validate(proto)
  1397  			must.NoError(t, err)
  1398  		})
  1399  
  1400  		t.Run(proto+" without hosts", func(t *testing.T) {
  1401  			err := (&ConsulIngressService{
  1402  				Name: "service1",
  1403  			}).Validate(proto)
  1404  			must.NoError(t, err, must.Sprintf(`"%s" protocol should not require hosts`, proto))
  1405  		})
  1406  
  1407  		t.Run(proto+" wildcard service", func(t *testing.T) {
  1408  			err := (&ConsulIngressService{
  1409  				Name: "*",
  1410  			}).Validate(proto)
  1411  			must.NoError(t, err, must.Sprintf(`"%s" protocol should allow wildcard service`, proto))
  1412  		})
  1413  
  1414  		t.Run(proto+" wildcard service and host", func(t *testing.T) {
  1415  			err := (&ConsulIngressService{
  1416  				Name:  "*",
  1417  				Hosts: []string{"any"},
  1418  			}).Validate(proto)
  1419  			must.EqError(t, err, `Consul Ingress Service with a wildcard "*" service name can not also specify hosts`)
  1420  		})
  1421  	}
  1422  }
  1423  
  1424  func TestConsulIngressListener_Validate(t *testing.T) {
  1425  	ci.Parallel(t)
  1426  
  1427  	t.Run("invalid port", func(t *testing.T) {
  1428  		err := (&ConsulIngressListener{
  1429  			Port:     0,
  1430  			Protocol: "tcp",
  1431  			Services: []*ConsulIngressService{{
  1432  				Name: "service1",
  1433  			}},
  1434  		}).Validate()
  1435  		require.EqualError(t, err, "Consul Ingress Listener requires valid Port")
  1436  	})
  1437  
  1438  	t.Run("invalid protocol", func(t *testing.T) {
  1439  		err := (&ConsulIngressListener{
  1440  			Port:     2000,
  1441  			Protocol: "gopher",
  1442  			Services: []*ConsulIngressService{{
  1443  				Name: "service1",
  1444  			}},
  1445  		}).Validate()
  1446  		require.EqualError(t, err, `Consul Ingress Listener requires protocol of tcp, http, http2, grpc, got "gopher"`)
  1447  	})
  1448  
  1449  	t.Run("no services", func(t *testing.T) {
  1450  		err := (&ConsulIngressListener{
  1451  			Port:     2000,
  1452  			Protocol: "tcp",
  1453  			Services: nil,
  1454  		}).Validate()
  1455  		require.EqualError(t, err, "Consul Ingress Listener requires one or more services")
  1456  	})
  1457  
  1458  	t.Run("invalid service", func(t *testing.T) {
  1459  		err := (&ConsulIngressListener{
  1460  			Port:     2000,
  1461  			Protocol: "tcp",
  1462  			Services: []*ConsulIngressService{{
  1463  				Name: "",
  1464  			}},
  1465  		}).Validate()
  1466  		require.EqualError(t, err, "Consul Ingress Service requires a name")
  1467  	})
  1468  
  1469  	t.Run("ok", func(t *testing.T) {
  1470  		err := (&ConsulIngressListener{
  1471  			Port:     2000,
  1472  			Protocol: "tcp",
  1473  			Services: []*ConsulIngressService{{
  1474  				Name: "service1",
  1475  			}},
  1476  		}).Validate()
  1477  		require.NoError(t, err)
  1478  	})
  1479  }
  1480  
  1481  func TestConsulIngressConfigEntry_Validate(t *testing.T) {
  1482  	ci.Parallel(t)
  1483  
  1484  	t.Run("no listeners", func(t *testing.T) {
  1485  		err := (&ConsulIngressConfigEntry{}).Validate()
  1486  		require.EqualError(t, err, "Consul Ingress Gateway requires at least one listener")
  1487  	})
  1488  
  1489  	t.Run("invalid listener", func(t *testing.T) {
  1490  		err := (&ConsulIngressConfigEntry{
  1491  			Listeners: []*ConsulIngressListener{{
  1492  				Port:     9000,
  1493  				Protocol: "tcp",
  1494  			}},
  1495  		}).Validate()
  1496  		require.EqualError(t, err, "Consul Ingress Listener requires one or more services")
  1497  	})
  1498  
  1499  	t.Run("full", func(t *testing.T) {
  1500  		err := (&ConsulIngressConfigEntry{
  1501  			TLS: &ConsulGatewayTLSConfig{
  1502  				Enabled: true,
  1503  			},
  1504  			Listeners: []*ConsulIngressListener{{
  1505  				Port:     9000,
  1506  				Protocol: "tcp",
  1507  				Services: []*ConsulIngressService{{
  1508  					Name: "service1",
  1509  				}},
  1510  			}},
  1511  		}).Validate()
  1512  		require.NoError(t, err)
  1513  	})
  1514  }
  1515  
  1516  func TestConsulLinkedService_Validate(t *testing.T) {
  1517  	ci.Parallel(t)
  1518  
  1519  	t.Run("nil", func(t *testing.T) {
  1520  		err := (*ConsulLinkedService)(nil).Validate()
  1521  		require.Nil(t, err)
  1522  	})
  1523  
  1524  	t.Run("missing name", func(t *testing.T) {
  1525  		err := (&ConsulLinkedService{}).Validate()
  1526  		require.EqualError(t, err, "Consul Linked Service requires Name")
  1527  	})
  1528  
  1529  	t.Run("missing ca_file", func(t *testing.T) {
  1530  		err := (&ConsulLinkedService{
  1531  			Name:     "linked-service1",
  1532  			CertFile: "cert_file.pem",
  1533  			KeyFile:  "key_file.pem",
  1534  		}).Validate()
  1535  		require.EqualError(t, err, "Consul Linked Service TLS requires CAFile")
  1536  	})
  1537  
  1538  	t.Run("mutual cert key", func(t *testing.T) {
  1539  		err := (&ConsulLinkedService{
  1540  			Name:     "linked-service1",
  1541  			CAFile:   "ca_file.pem",
  1542  			CertFile: "cert_file.pem",
  1543  		}).Validate()
  1544  		require.EqualError(t, err, "Consul Linked Service TLS Cert and Key must both be set")
  1545  	})
  1546  
  1547  	t.Run("sni without ca_file", func(t *testing.T) {
  1548  		err := (&ConsulLinkedService{
  1549  			Name: "linked-service1",
  1550  			SNI:  "service.consul",
  1551  		}).Validate()
  1552  		require.EqualError(t, err, "Consul Linked Service TLS SNI requires CAFile")
  1553  	})
  1554  
  1555  	t.Run("minimal", func(t *testing.T) {
  1556  		err := (&ConsulLinkedService{
  1557  			Name: "linked-service1",
  1558  		}).Validate()
  1559  		require.NoError(t, err)
  1560  	})
  1561  
  1562  	t.Run("tls minimal", func(t *testing.T) {
  1563  		err := (&ConsulLinkedService{
  1564  			Name:   "linked-service1",
  1565  			CAFile: "ca_file.pem",
  1566  		}).Validate()
  1567  		require.NoError(t, err)
  1568  	})
  1569  
  1570  	t.Run("tls mutual", func(t *testing.T) {
  1571  		err := (&ConsulLinkedService{
  1572  			Name:     "linked-service1",
  1573  			CAFile:   "ca_file.pem",
  1574  			CertFile: "cert_file.pem",
  1575  			KeyFile:  "key_file.pem",
  1576  		}).Validate()
  1577  		require.NoError(t, err)
  1578  	})
  1579  
  1580  	t.Run("tls sni", func(t *testing.T) {
  1581  		err := (&ConsulLinkedService{
  1582  			Name:   "linked-service1",
  1583  			CAFile: "ca_file.pem",
  1584  			SNI:    "linked-service.consul",
  1585  		}).Validate()
  1586  		require.NoError(t, err)
  1587  	})
  1588  
  1589  	t.Run("tls complete", func(t *testing.T) {
  1590  		err := (&ConsulLinkedService{
  1591  			Name:     "linked-service1",
  1592  			CAFile:   "ca_file.pem",
  1593  			CertFile: "cert_file.pem",
  1594  			KeyFile:  "key_file.pem",
  1595  			SNI:      "linked-service.consul",
  1596  		}).Validate()
  1597  		require.NoError(t, err)
  1598  	})
  1599  }
  1600  
  1601  func TestConsulLinkedService_Copy(t *testing.T) {
  1602  	ci.Parallel(t)
  1603  
  1604  	require.Nil(t, (*ConsulLinkedService)(nil).Copy())
  1605  	require.Equal(t, &ConsulLinkedService{
  1606  		Name:     "service1",
  1607  		CAFile:   "ca.pem",
  1608  		CertFile: "cert.pem",
  1609  		KeyFile:  "key.pem",
  1610  		SNI:      "service1.consul",
  1611  	}, (&ConsulLinkedService{
  1612  		Name:     "service1",
  1613  		CAFile:   "ca.pem",
  1614  		CertFile: "cert.pem",
  1615  		KeyFile:  "key.pem",
  1616  		SNI:      "service1.consul",
  1617  	}).Copy())
  1618  }
  1619  
  1620  func TestConsulLinkedService_linkedServicesEqual(t *testing.T) {
  1621  	ci.Parallel(t)
  1622  
  1623  	services := []*ConsulLinkedService{{
  1624  		Name:   "service1",
  1625  		CAFile: "ca.pem",
  1626  	}, {
  1627  		Name:   "service2",
  1628  		CAFile: "ca.pem",
  1629  	}}
  1630  
  1631  	require.False(t, linkedServicesEqual(services, nil))
  1632  	require.True(t, linkedServicesEqual(services, services))
  1633  
  1634  	reversed := []*ConsulLinkedService{
  1635  		services[1], services[0], // reversed
  1636  	}
  1637  
  1638  	require.True(t, linkedServicesEqual(services, reversed))
  1639  
  1640  	different := []*ConsulLinkedService{
  1641  		services[0], {
  1642  			Name:   "service2",
  1643  			CAFile: "ca.pem",
  1644  			SNI:    "service2.consul",
  1645  		},
  1646  	}
  1647  
  1648  	require.False(t, linkedServicesEqual(services, different))
  1649  }
  1650  
  1651  func TestConsulTerminatingConfigEntry_Validate(t *testing.T) {
  1652  	ci.Parallel(t)
  1653  
  1654  	t.Run("nil", func(t *testing.T) {
  1655  		err := (*ConsulTerminatingConfigEntry)(nil).Validate()
  1656  		require.NoError(t, err)
  1657  	})
  1658  
  1659  	t.Run("no services", func(t *testing.T) {
  1660  		err := (&ConsulTerminatingConfigEntry{
  1661  			Services: make([]*ConsulLinkedService, 0),
  1662  		}).Validate()
  1663  		require.EqualError(t, err, "Consul Terminating Gateway requires at least one service")
  1664  	})
  1665  
  1666  	t.Run("service invalid", func(t *testing.T) {
  1667  		err := (&ConsulTerminatingConfigEntry{
  1668  			Services: []*ConsulLinkedService{{
  1669  				Name: "",
  1670  			}},
  1671  		}).Validate()
  1672  		require.EqualError(t, err, "Consul Linked Service requires Name")
  1673  	})
  1674  
  1675  	t.Run("ok", func(t *testing.T) {
  1676  		err := (&ConsulTerminatingConfigEntry{
  1677  			Services: []*ConsulLinkedService{{
  1678  				Name: "service1",
  1679  			}},
  1680  		}).Validate()
  1681  		require.NoError(t, err)
  1682  	})
  1683  }
  1684  
  1685  func TestConsulMeshGateway_Copy(t *testing.T) {
  1686  	ci.Parallel(t)
  1687  
  1688  	require.Nil(t, (*ConsulMeshGateway)(nil))
  1689  	require.Equal(t, &ConsulMeshGateway{
  1690  		Mode: "remote",
  1691  	}, &ConsulMeshGateway{
  1692  		Mode: "remote",
  1693  	})
  1694  }
  1695  
  1696  func TestConsulMeshGateway_Equal(t *testing.T) {
  1697  	ci.Parallel(t)
  1698  
  1699  	c := ConsulMeshGateway{Mode: "local"}
  1700  	require.False(t, c.Equal(ConsulMeshGateway{}))
  1701  	require.True(t, c.Equal(c))
  1702  
  1703  	o := ConsulMeshGateway{Mode: "remote"}
  1704  	require.False(t, c.Equal(o))
  1705  }
  1706  
  1707  func TestConsulMeshGateway_Validate(t *testing.T) {
  1708  	ci.Parallel(t)
  1709  
  1710  	t.Run("nil", func(t *testing.T) {
  1711  		err := (*ConsulMeshGateway)(nil).Validate()
  1712  		require.NoError(t, err)
  1713  	})
  1714  
  1715  	t.Run("mode invalid", func(t *testing.T) {
  1716  		err := (&ConsulMeshGateway{Mode: "banana"}).Validate()
  1717  		require.EqualError(t, err, `Connect mesh_gateway mode "banana" not supported`)
  1718  	})
  1719  
  1720  	t.Run("ok", func(t *testing.T) {
  1721  		err := (&ConsulMeshGateway{Mode: "local"}).Validate()
  1722  		require.NoError(t, err)
  1723  	})
  1724  }
  1725  
  1726  func TestService_Validate(t *testing.T) {
  1727  	ci.Parallel(t)
  1728  
  1729  	testCases := []struct {
  1730  		input     *Service
  1731  		expErr    bool
  1732  		expErrStr string
  1733  		name      string
  1734  	}{
  1735  		{
  1736  			name: "base service",
  1737  			input: &Service{
  1738  				Name: "testservice",
  1739  			},
  1740  			expErr: false,
  1741  		},
  1742  		{
  1743  			name: "Native Connect without task name",
  1744  			input: &Service{
  1745  				Name: "testservice",
  1746  				Connect: &ConsulConnect{
  1747  					Native: true,
  1748  				},
  1749  			},
  1750  			expErr: false, // gets set automatically
  1751  		},
  1752  		{
  1753  			name: "Native Connect with task name",
  1754  			input: &Service{
  1755  				Name:     "testservice",
  1756  				TaskName: "testtask",
  1757  				Connect: &ConsulConnect{
  1758  					Native: true,
  1759  				},
  1760  			},
  1761  			expErr: false,
  1762  		},
  1763  		{
  1764  			name: "Native Connect with Sidecar",
  1765  			input: &Service{
  1766  				Name:     "testservice",
  1767  				TaskName: "testtask",
  1768  				Connect: &ConsulConnect{
  1769  					Native:         true,
  1770  					SidecarService: &ConsulSidecarService{},
  1771  				},
  1772  			},
  1773  			expErr:    true,
  1774  			expErrStr: "Consul Connect must be exclusively native",
  1775  		},
  1776  		{
  1777  			name: "provider nomad with checks",
  1778  			input: &Service{
  1779  				Name:      "testservice",
  1780  				Provider:  "nomad",
  1781  				PortLabel: "port",
  1782  				Checks: []*ServiceCheck{
  1783  					{
  1784  						Name:     "servicecheck",
  1785  						Type:     "http",
  1786  						Path:     "/",
  1787  						Interval: 1 * time.Second,
  1788  						Timeout:  3 * time.Second,
  1789  					},
  1790  					{
  1791  						Name:     "servicecheck",
  1792  						Type:     "tcp",
  1793  						Interval: 1 * time.Second,
  1794  						Timeout:  3 * time.Second,
  1795  					},
  1796  				},
  1797  			},
  1798  			expErr: false,
  1799  		},
  1800  		{
  1801  			name: "provider nomad with invalid check type",
  1802  			input: &Service{
  1803  				Name:     "testservice",
  1804  				Provider: "nomad",
  1805  				Checks: []*ServiceCheck{
  1806  					{
  1807  						Name: "servicecheck",
  1808  						Type: "script",
  1809  					},
  1810  				},
  1811  			},
  1812  			expErr: true,
  1813  		},
  1814  		{
  1815  			name: "provider nomad with connect",
  1816  			input: &Service{
  1817  				Name:     "testservice",
  1818  				Provider: "nomad",
  1819  				Connect: &ConsulConnect{
  1820  					Native: true,
  1821  				},
  1822  			},
  1823  			expErr:    true,
  1824  			expErrStr: "Service with provider nomad cannot include Connect blocks",
  1825  		},
  1826  		{
  1827  			name: "provider nomad valid",
  1828  			input: &Service{
  1829  				Name:     "testservice",
  1830  				Provider: "nomad",
  1831  			},
  1832  			expErr: false,
  1833  		},
  1834  	}
  1835  
  1836  	for _, tc := range testCases {
  1837  		t.Run(tc.name, func(t *testing.T) {
  1838  			tc.input.Canonicalize("testjob", "testgroup", "testtask", "testnamespace")
  1839  			err := tc.input.Validate()
  1840  			if tc.expErr {
  1841  				require.Error(t, err)
  1842  				require.Contains(t, err.Error(), tc.expErrStr)
  1843  			} else {
  1844  				require.NoError(t, err)
  1845  			}
  1846  		})
  1847  	}
  1848  }
  1849  
  1850  func TestService_Validate_Address(t *testing.T) {
  1851  	ci.Parallel(t)
  1852  
  1853  	try := func(mode, advertise string, exp error) {
  1854  		s := &Service{Name: "s1", Provider: "consul", AddressMode: mode, Address: advertise}
  1855  		result := s.Validate()
  1856  		if exp == nil {
  1857  			require.NoError(t, result)
  1858  		} else {
  1859  			// would be nice if multierror worked with errors.Is
  1860  			require.Contains(t, result.Error(), exp.Error())
  1861  		}
  1862  	}
  1863  
  1864  	// advertise not set
  1865  	try("", "", nil)
  1866  	try("auto", "", nil)
  1867  	try("host", "", nil)
  1868  	try("alloc", "", nil)
  1869  	try("driver", "", nil)
  1870  
  1871  	// advertise is set
  1872  	try("", "example.com", nil)
  1873  	try("auto", "example.com", nil)
  1874  	try("host", "example.com", errors.New(`Service address_mode must be "auto" if address is set`))
  1875  	try("alloc", "example.com", errors.New(`Service address_mode must be "auto" if address is set`))
  1876  	try("driver", "example.com", errors.New(`Service address_mode must be "auto" if address is set`))
  1877  }
  1878  
  1879  func TestService_Equal(t *testing.T) {
  1880  	ci.Parallel(t)
  1881  
  1882  	s := Service{
  1883  		Name:            "testservice",
  1884  		TaggedAddresses: make(map[string]string),
  1885  	}
  1886  
  1887  	s.Canonicalize("testjob", "testgroup", "testtask", "default")
  1888  
  1889  	o := s.Copy()
  1890  
  1891  	// Base service should be equal to copy of itself
  1892  	require.True(t, s.Equal(o))
  1893  
  1894  	// create a helper to assert a diff and reset the struct
  1895  	assertDiff := func() {
  1896  		require.False(t, s.Equal(o))
  1897  		o = s.Copy()
  1898  		require.True(t, s.Equal(o), "bug in copy")
  1899  	}
  1900  
  1901  	// Changing any field should cause inequality
  1902  	o.Name = "diff"
  1903  	assertDiff()
  1904  
  1905  	o.Address = "diff"
  1906  	assertDiff()
  1907  
  1908  	o.PortLabel = "diff"
  1909  	assertDiff()
  1910  
  1911  	o.AddressMode = AddressModeDriver
  1912  	assertDiff()
  1913  
  1914  	o.Tags = []string{"diff"}
  1915  	assertDiff()
  1916  
  1917  	o.CanaryTags = []string{"diff"}
  1918  	assertDiff()
  1919  
  1920  	o.Checks = []*ServiceCheck{{Name: "diff"}}
  1921  	assertDiff()
  1922  
  1923  	o.Connect = &ConsulConnect{Native: true}
  1924  	assertDiff()
  1925  
  1926  	o.EnableTagOverride = true
  1927  	assertDiff()
  1928  
  1929  	o.Provider = "nomad"
  1930  	assertDiff()
  1931  
  1932  	o.TaggedAddresses = map[string]string{"foo": "bar"}
  1933  	assertDiff()
  1934  }
  1935  
  1936  func TestService_validateNomadService(t *testing.T) {
  1937  	ci.Parallel(t)
  1938  
  1939  	testCases := []struct {
  1940  		inputService         *Service
  1941  		inputErr             *multierror.Error
  1942  		expectedOutputErrors []error
  1943  		name                 string
  1944  	}{
  1945  		{
  1946  			inputService: &Service{
  1947  				Name:      "webapp",
  1948  				PortLabel: "http",
  1949  				Namespace: "default",
  1950  				Provider:  "nomad",
  1951  			},
  1952  			inputErr:             &multierror.Error{},
  1953  			expectedOutputErrors: nil,
  1954  			name:                 "valid service",
  1955  		},
  1956  		{
  1957  			inputService: &Service{
  1958  				Name:      "webapp",
  1959  				PortLabel: "http",
  1960  				Namespace: "default",
  1961  				Provider:  "nomad",
  1962  				Checks: []*ServiceCheck{{
  1963  					Name:     "webapp",
  1964  					Type:     ServiceCheckHTTP,
  1965  					Method:   "GET",
  1966  					Path:     "/health",
  1967  					Interval: 3 * time.Second,
  1968  					Timeout:  1 * time.Second,
  1969  				}},
  1970  			},
  1971  			inputErr:             &multierror.Error{},
  1972  			expectedOutputErrors: nil,
  1973  			name:                 "valid service with checks",
  1974  		},
  1975  		{
  1976  			inputService: &Service{
  1977  				Name:      "webapp",
  1978  				PortLabel: "http",
  1979  				Namespace: "default",
  1980  				Provider:  "nomad",
  1981  				Connect: &ConsulConnect{
  1982  					Native: true,
  1983  				},
  1984  			},
  1985  			inputErr:             &multierror.Error{},
  1986  			expectedOutputErrors: []error{errors.New("Service with provider nomad cannot include Connect blocks")},
  1987  			name:                 "invalid service due to connect",
  1988  		},
  1989  		{
  1990  			inputService: &Service{
  1991  				Name:      "webapp",
  1992  				PortLabel: "http",
  1993  				Namespace: "default",
  1994  				Provider:  "nomad",
  1995  				Checks: []*ServiceCheck{
  1996  					{Name: "some-check"},
  1997  				},
  1998  			},
  1999  			inputErr: &multierror.Error{},
  2000  			expectedOutputErrors: []error{
  2001  				errors.New(`invalid check type (""), must be one of tcp, http`),
  2002  			},
  2003  			name: "bad nomad check",
  2004  		},
  2005  	}
  2006  
  2007  	for _, tc := range testCases {
  2008  		t.Run(tc.name, func(t *testing.T) {
  2009  			tc.inputService.validateNomadService(tc.inputErr)
  2010  			must.Eq(t, tc.expectedOutputErrors, tc.inputErr.Errors)
  2011  		})
  2012  	}
  2013  }