github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/nomad/structs/services_test.go (about)

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