github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/nomad/structs/services_test.go (about)

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