github.com/hernad/nomad@v1.6.112/command/agent/consul/connect_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package consul
     5  
     6  import (
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/hashicorp/consul/api"
    11  	"github.com/hernad/nomad/ci"
    12  	"github.com/hernad/nomad/helper/pointer"
    13  	"github.com/hernad/nomad/helper/uuid"
    14  	"github.com/hernad/nomad/nomad/structs"
    15  	"github.com/shoenig/test/must"
    16  	"github.com/stretchr/testify/require"
    17  )
    18  
    19  var (
    20  	testConnectNetwork = structs.Networks{{
    21  		Mode:   "bridge",
    22  		Device: "eth0",
    23  		IP:     "192.168.30.1",
    24  		DynamicPorts: []structs.Port{
    25  			{Label: "healthPort", Value: 23100, To: 23100},
    26  			{Label: "metricsPort", Value: 23200, To: 23200},
    27  			{Label: "connect-proxy-redis", Value: 3000, To: 3000},
    28  		},
    29  	}}
    30  	testConnectPorts = structs.AllocatedPorts{{
    31  		Label:  "connect-proxy-redis",
    32  		Value:  3000,
    33  		To:     3000,
    34  		HostIP: "192.168.30.1",
    35  	}}
    36  )
    37  
    38  func TestConnect_newConnect(t *testing.T) {
    39  	ci.Parallel(t)
    40  
    41  	service := "redis"
    42  	redisID := uuid.Generate()
    43  	allocID := uuid.Generate()
    44  	info := structs.AllocInfo{
    45  		AllocID: allocID,
    46  	}
    47  
    48  	t.Run("nil", func(t *testing.T) {
    49  		asr, err := newConnect("", structs.AllocInfo{}, "", nil, nil, nil)
    50  		require.NoError(t, err)
    51  		require.Nil(t, asr)
    52  	})
    53  
    54  	t.Run("native", func(t *testing.T) {
    55  		asr, err := newConnect(redisID, info, service, &structs.ConsulConnect{
    56  			Native: true,
    57  		}, nil, nil)
    58  		require.NoError(t, err)
    59  		require.True(t, asr.Native)
    60  		require.Nil(t, asr.SidecarService)
    61  	})
    62  
    63  	t.Run("with sidecar", func(t *testing.T) {
    64  		asr, err := newConnect(redisID, info, service, &structs.ConsulConnect{
    65  			Native: false,
    66  			SidecarService: &structs.ConsulSidecarService{
    67  				Tags: []string{"foo", "bar"},
    68  				Port: "connect-proxy-redis",
    69  			},
    70  		}, testConnectNetwork, testConnectPorts)
    71  		require.NoError(t, err)
    72  		require.Equal(t, &api.AgentServiceRegistration{
    73  			Tags:    []string{"foo", "bar"},
    74  			Port:    3000,
    75  			Address: "192.168.30.1",
    76  			Proxy: &api.AgentServiceConnectProxyConfig{
    77  				Config: map[string]interface{}{
    78  					"bind_address":     "0.0.0.0",
    79  					"bind_port":        3000,
    80  					"envoy_stats_tags": []string{"nomad.alloc_id=" + allocID},
    81  				},
    82  			},
    83  			Checks: api.AgentServiceChecks{
    84  				{
    85  					Name:         "Connect Sidecar Aliasing " + redisID,
    86  					AliasService: redisID,
    87  				},
    88  				{
    89  					Name:     "Connect Sidecar Listening",
    90  					TCP:      "192.168.30.1:3000",
    91  					Interval: "10s",
    92  				},
    93  			},
    94  		}, asr.SidecarService)
    95  	})
    96  
    97  	t.Run("with sidecar without TCP checks", func(t *testing.T) {
    98  		asr, err := newConnect(redisID, info, service, &structs.ConsulConnect{
    99  			Native: false,
   100  			SidecarService: &structs.ConsulSidecarService{
   101  				Tags:                   []string{"foo", "bar"},
   102  				Port:                   "connect-proxy-redis",
   103  				DisableDefaultTCPCheck: true,
   104  			},
   105  		}, testConnectNetwork, testConnectPorts)
   106  		require.NoError(t, err)
   107  		require.Equal(t, &api.AgentServiceRegistration{
   108  			Tags:    []string{"foo", "bar"},
   109  			Port:    3000,
   110  			Address: "192.168.30.1",
   111  			Proxy: &api.AgentServiceConnectProxyConfig{
   112  				Config: map[string]interface{}{
   113  					"bind_address":     "0.0.0.0",
   114  					"bind_port":        3000,
   115  					"envoy_stats_tags": []string{"nomad.alloc_id=" + allocID},
   116  				},
   117  			},
   118  			Checks: api.AgentServiceChecks{
   119  				{
   120  					Name:         "Connect Sidecar Aliasing " + redisID,
   121  					AliasService: redisID,
   122  				},
   123  			},
   124  		}, asr.SidecarService)
   125  	})
   126  }
   127  
   128  func TestConnect_connectSidecarRegistration(t *testing.T) {
   129  	ci.Parallel(t)
   130  
   131  	redisID := uuid.Generate()
   132  	allocID := uuid.Generate()
   133  	info := structs.AllocInfo{
   134  		AllocID: allocID,
   135  	}
   136  
   137  	t.Run("nil", func(t *testing.T) {
   138  		sidecarReg, err := connectSidecarRegistration(redisID, info, nil, testConnectNetwork, testConnectPorts)
   139  		require.NoError(t, err)
   140  		require.Nil(t, sidecarReg)
   141  	})
   142  
   143  	t.Run("no service port", func(t *testing.T) {
   144  		_, err := connectSidecarRegistration("unknown-id", info, &structs.ConsulSidecarService{
   145  			Port: "unknown-label",
   146  		}, testConnectNetwork, testConnectPorts)
   147  		require.EqualError(t, err, `No port of label "unknown-label" defined`)
   148  	})
   149  
   150  	t.Run("bad proxy", func(t *testing.T) {
   151  		_, err := connectSidecarRegistration(redisID, info, &structs.ConsulSidecarService{
   152  			Port: "connect-proxy-redis",
   153  			Proxy: &structs.ConsulProxy{
   154  				Expose: &structs.ConsulExposeConfig{
   155  					Paths: []structs.ConsulExposePath{{
   156  						ListenerPort: "badPort",
   157  					}},
   158  				},
   159  			},
   160  		}, testConnectNetwork, testConnectPorts)
   161  		require.EqualError(t, err, `No port of label "badPort" defined`)
   162  	})
   163  
   164  	t.Run("normal", func(t *testing.T) {
   165  		proxy, err := connectSidecarRegistration(redisID, info, &structs.ConsulSidecarService{
   166  			Tags: []string{"foo", "bar"},
   167  			Port: "connect-proxy-redis",
   168  		}, testConnectNetwork, testConnectPorts)
   169  		require.NoError(t, err)
   170  		require.Equal(t, &api.AgentServiceRegistration{
   171  			Tags:    []string{"foo", "bar"},
   172  			Port:    3000,
   173  			Address: "192.168.30.1",
   174  			Proxy: &api.AgentServiceConnectProxyConfig{
   175  				Config: map[string]interface{}{
   176  					"bind_address":     "0.0.0.0",
   177  					"bind_port":        3000,
   178  					"envoy_stats_tags": []string{"nomad.alloc_id=" + allocID},
   179  				},
   180  			},
   181  			Checks: api.AgentServiceChecks{
   182  				{
   183  					Name:         "Connect Sidecar Aliasing " + redisID,
   184  					AliasService: redisID,
   185  				},
   186  				{
   187  					Name:     "Connect Sidecar Listening",
   188  					TCP:      "192.168.30.1:3000",
   189  					Interval: "10s",
   190  				},
   191  			},
   192  		}, proxy)
   193  	})
   194  }
   195  
   196  func TestConnect_connectProxy(t *testing.T) {
   197  	ci.Parallel(t)
   198  
   199  	allocID := uuid.Generate()
   200  	info := structs.AllocInfo{
   201  		AllocID: allocID,
   202  	}
   203  
   204  	// If the input proxy is nil, we expect the output to be a proxy with its
   205  	// config set to default values.
   206  	t.Run("nil proxy", func(t *testing.T) {
   207  		proxy, err := connectSidecarProxy(info, nil, 2000, testConnectNetwork)
   208  		require.NoError(t, err)
   209  		require.Equal(t, &api.AgentServiceConnectProxyConfig{
   210  			LocalServiceAddress: "",
   211  			LocalServicePort:    0,
   212  			Upstreams:           nil,
   213  			Expose:              api.ExposeConfig{},
   214  			Config: map[string]interface{}{
   215  				"bind_address":     "0.0.0.0",
   216  				"bind_port":        2000,
   217  				"envoy_stats_tags": []string{"nomad.alloc_id=" + allocID},
   218  			},
   219  		}, proxy)
   220  	})
   221  
   222  	t.Run("bad proxy", func(t *testing.T) {
   223  		_, err := connectSidecarProxy(info, &structs.ConsulProxy{
   224  			LocalServiceAddress: "0.0.0.0",
   225  			LocalServicePort:    2000,
   226  			Upstreams:           nil,
   227  			Expose: &structs.ConsulExposeConfig{
   228  				Paths: []structs.ConsulExposePath{{
   229  					ListenerPort: "badPort",
   230  				}},
   231  			},
   232  			Config: nil,
   233  		}, 2000, testConnectNetwork)
   234  		require.EqualError(t, err, `No port of label "badPort" defined`)
   235  	})
   236  
   237  	t.Run("normal", func(t *testing.T) {
   238  		proxy, err := connectSidecarProxy(info, &structs.ConsulProxy{
   239  			LocalServiceAddress: "0.0.0.0",
   240  			LocalServicePort:    2000,
   241  			Upstreams:           nil,
   242  			Expose: &structs.ConsulExposeConfig{
   243  				Paths: []structs.ConsulExposePath{{
   244  					Path:          "/health",
   245  					Protocol:      "http",
   246  					LocalPathPort: 8000,
   247  					ListenerPort:  "healthPort",
   248  				}},
   249  			},
   250  			Config: nil,
   251  		}, 2000, testConnectNetwork)
   252  		require.NoError(t, err)
   253  		require.Equal(t, &api.AgentServiceConnectProxyConfig{
   254  			LocalServiceAddress: "0.0.0.0",
   255  			LocalServicePort:    2000,
   256  			Upstreams:           nil,
   257  			Expose: api.ExposeConfig{
   258  				Paths: []api.ExposePath{{
   259  					Path:          "/health",
   260  					Protocol:      "http",
   261  					LocalPathPort: 8000,
   262  					ListenerPort:  23100,
   263  				}},
   264  			},
   265  			Config: map[string]interface{}{
   266  				"bind_address":     "0.0.0.0",
   267  				"bind_port":        2000,
   268  				"envoy_stats_tags": []string{"nomad.alloc_id=" + allocID},
   269  			},
   270  		}, proxy)
   271  	})
   272  }
   273  
   274  func TestConnect_connectProxyExpose(t *testing.T) {
   275  	ci.Parallel(t)
   276  
   277  	t.Run("nil", func(t *testing.T) {
   278  		exposeConfig, err := connectProxyExpose(nil, nil)
   279  		require.NoError(t, err)
   280  		require.Equal(t, api.ExposeConfig{}, exposeConfig)
   281  	})
   282  
   283  	t.Run("bad port", func(t *testing.T) {
   284  		_, err := connectProxyExpose(&structs.ConsulExposeConfig{
   285  			Paths: []structs.ConsulExposePath{{
   286  				ListenerPort: "badPort",
   287  			}},
   288  		}, testConnectNetwork)
   289  		require.EqualError(t, err, `No port of label "badPort" defined`)
   290  	})
   291  
   292  	t.Run("normal", func(t *testing.T) {
   293  		expose, err := connectProxyExpose(&structs.ConsulExposeConfig{
   294  			Paths: []structs.ConsulExposePath{{
   295  				Path:          "/health",
   296  				Protocol:      "http",
   297  				LocalPathPort: 8000,
   298  				ListenerPort:  "healthPort",
   299  			}},
   300  		}, testConnectNetwork)
   301  		require.NoError(t, err)
   302  		require.Equal(t, api.ExposeConfig{
   303  			Checks: false,
   304  			Paths: []api.ExposePath{{
   305  				Path:            "/health",
   306  				ListenerPort:    23100,
   307  				LocalPathPort:   8000,
   308  				Protocol:        "http",
   309  				ParsedFromCheck: false,
   310  			}},
   311  		}, expose)
   312  	})
   313  }
   314  
   315  func TestConnect_connectProxyExposePaths(t *testing.T) {
   316  	ci.Parallel(t)
   317  
   318  	t.Run("nil", func(t *testing.T) {
   319  		upstreams, err := connectProxyExposePaths(nil, nil)
   320  		require.NoError(t, err)
   321  		require.Empty(t, upstreams)
   322  	})
   323  
   324  	t.Run("no network", func(t *testing.T) {
   325  		original := []structs.ConsulExposePath{{Path: "/path"}}
   326  		_, err := connectProxyExposePaths(original, nil)
   327  		require.EqualError(t, err, `Connect only supported with exactly 1 network (found 0)`)
   328  	})
   329  
   330  	t.Run("normal", func(t *testing.T) {
   331  		original := []structs.ConsulExposePath{{
   332  			Path:          "/health",
   333  			Protocol:      "http",
   334  			LocalPathPort: 8000,
   335  			ListenerPort:  "healthPort",
   336  		}, {
   337  			Path:          "/metrics",
   338  			Protocol:      "grpc",
   339  			LocalPathPort: 9500,
   340  			ListenerPort:  "metricsPort",
   341  		}}
   342  		exposePaths, err := connectProxyExposePaths(original, testConnectNetwork)
   343  		require.NoError(t, err)
   344  		require.Equal(t, []api.ExposePath{
   345  			{
   346  				Path:            "/health",
   347  				Protocol:        "http",
   348  				LocalPathPort:   8000,
   349  				ListenerPort:    23100,
   350  				ParsedFromCheck: false,
   351  			},
   352  			{
   353  				Path:            "/metrics",
   354  				Protocol:        "grpc",
   355  				LocalPathPort:   9500,
   356  				ListenerPort:    23200,
   357  				ParsedFromCheck: false,
   358  			},
   359  		}, exposePaths)
   360  	})
   361  }
   362  
   363  func TestConnect_connectUpstreams(t *testing.T) {
   364  	ci.Parallel(t)
   365  
   366  	t.Run("nil", func(t *testing.T) {
   367  		must.Nil(t, connectUpstreams(nil))
   368  	})
   369  
   370  	t.Run("not empty", func(t *testing.T) {
   371  		must.Eq(t,
   372  			[]api.Upstream{{
   373  				DestinationName: "foo",
   374  				LocalBindPort:   8000,
   375  			}, {
   376  				DestinationName:      "bar",
   377  				DestinationNamespace: "ns2",
   378  				LocalBindPort:        9000,
   379  				Datacenter:           "dc2",
   380  				LocalBindAddress:     "127.0.0.2",
   381  				Config:               map[string]any{"connect_timeout_ms": 5000},
   382  			}},
   383  			connectUpstreams([]structs.ConsulUpstream{{
   384  				DestinationName: "foo",
   385  				LocalBindPort:   8000,
   386  			}, {
   387  				DestinationName:      "bar",
   388  				DestinationNamespace: "ns2",
   389  				LocalBindPort:        9000,
   390  				Datacenter:           "dc2",
   391  				LocalBindAddress:     "127.0.0.2",
   392  				Config:               map[string]any{"connect_timeout_ms": 5000},
   393  			}}),
   394  		)
   395  	})
   396  }
   397  
   398  func TestConnect_connectProxyConfig(t *testing.T) {
   399  	ci.Parallel(t)
   400  
   401  	t.Run("nil map", func(t *testing.T) {
   402  		require.Equal(t, map[string]interface{}{
   403  			"bind_address":     "0.0.0.0",
   404  			"bind_port":        42,
   405  			"envoy_stats_tags": []string{"nomad.alloc_id=test_alloc1"},
   406  		}, connectProxyConfig(nil, 42, structs.AllocInfo{AllocID: "test_alloc1"}))
   407  	})
   408  
   409  	t.Run("pre-existing map", func(t *testing.T) {
   410  		require.Equal(t, map[string]interface{}{
   411  			"bind_address":     "0.0.0.0",
   412  			"bind_port":        42,
   413  			"foo":              "bar",
   414  			"envoy_stats_tags": []string{"nomad.alloc_id=test_alloc2"},
   415  		}, connectProxyConfig(map[string]interface{}{
   416  			"foo": "bar",
   417  		}, 42, structs.AllocInfo{AllocID: "test_alloc2"}))
   418  	})
   419  }
   420  
   421  func TestConnect_getConnectPort(t *testing.T) {
   422  	ci.Parallel(t)
   423  
   424  	networks := structs.Networks{{
   425  		IP: "192.168.30.1",
   426  		DynamicPorts: []structs.Port{{
   427  			Label: "connect-proxy-foo",
   428  			Value: 23456,
   429  			To:    23456,
   430  		}}}}
   431  
   432  	ports := structs.AllocatedPorts{{
   433  		Label:  "foo",
   434  		Value:  23456,
   435  		To:     23456,
   436  		HostIP: "192.168.30.1",
   437  	}}
   438  
   439  	t.Run("normal", func(t *testing.T) {
   440  		nr, err := connectPort("foo", networks, ports)
   441  		require.NoError(t, err)
   442  		require.Equal(t, structs.AllocatedPortMapping{
   443  			Label:  "foo",
   444  			Value:  23456,
   445  			To:     23456,
   446  			HostIP: "192.168.30.1",
   447  		}, nr)
   448  	})
   449  
   450  	t.Run("no such service", func(t *testing.T) {
   451  		_, err := connectPort("other", networks, ports)
   452  		require.EqualError(t, err, `No port of label "other" defined`)
   453  	})
   454  
   455  	t.Run("no network", func(t *testing.T) {
   456  		_, err := connectPort("foo", nil, nil)
   457  		require.EqualError(t, err, "Connect only supported with exactly 1 network (found 0)")
   458  	})
   459  
   460  	t.Run("multi network", func(t *testing.T) {
   461  		_, err := connectPort("foo", append(networks, &structs.NetworkResource{
   462  			Device: "eth1",
   463  			IP:     "10.0.10.0",
   464  		}), nil)
   465  		require.EqualError(t, err, "Connect only supported with exactly 1 network (found 2)")
   466  	})
   467  }
   468  
   469  func TestConnect_getExposePathPort(t *testing.T) {
   470  	ci.Parallel(t)
   471  
   472  	networks := structs.Networks{{
   473  		Device: "eth0",
   474  		IP:     "192.168.30.1",
   475  		DynamicPorts: []structs.Port{{
   476  			Label: "myPort",
   477  			Value: 23456,
   478  			To:    23456,
   479  		}}}}
   480  
   481  	t.Run("normal", func(t *testing.T) {
   482  		ip, port, err := connectExposePathPort("myPort", networks)
   483  		require.NoError(t, err)
   484  		require.Equal(t, ip, "192.168.30.1")
   485  		require.Equal(t, 23456, port)
   486  	})
   487  
   488  	t.Run("no such port label", func(t *testing.T) {
   489  		_, _, err := connectExposePathPort("otherPort", networks)
   490  		require.EqualError(t, err, `No port of label "otherPort" defined`)
   491  	})
   492  
   493  	t.Run("no network", func(t *testing.T) {
   494  		_, _, err := connectExposePathPort("myPort", nil)
   495  		require.EqualError(t, err, "Connect only supported with exactly 1 network (found 0)")
   496  	})
   497  
   498  	t.Run("multi network", func(t *testing.T) {
   499  		_, _, err := connectExposePathPort("myPort", append(networks, &structs.NetworkResource{
   500  			Device: "eth1",
   501  			IP:     "10.0.10.0",
   502  		}))
   503  		require.EqualError(t, err, "Connect only supported with exactly 1 network (found 2)")
   504  	})
   505  }
   506  
   507  func TestConnect_newConnectGateway(t *testing.T) {
   508  	ci.Parallel(t)
   509  
   510  	t.Run("not a gateway", func(t *testing.T) {
   511  		result := newConnectGateway(&structs.ConsulConnect{Native: true})
   512  		require.Nil(t, result)
   513  	})
   514  
   515  	t.Run("canonical empty", func(t *testing.T) {
   516  		result := newConnectGateway(&structs.ConsulConnect{
   517  			Gateway: &structs.ConsulGateway{
   518  				Proxy: &structs.ConsulGatewayProxy{
   519  					ConnectTimeout:                  pointer.Of(1 * time.Second),
   520  					EnvoyGatewayBindTaggedAddresses: false,
   521  					EnvoyGatewayBindAddresses:       nil,
   522  					EnvoyGatewayNoDefaultBind:       false,
   523  					Config:                          nil,
   524  				},
   525  			},
   526  		})
   527  		require.Equal(t, &api.AgentServiceConnectProxyConfig{
   528  			Config: map[string]interface{}{
   529  				"connect_timeout_ms": int64(1000),
   530  			},
   531  		}, result)
   532  	})
   533  
   534  	t.Run("proxy undefined", func(t *testing.T) {
   535  		result := newConnectGateway(&structs.ConsulConnect{
   536  			Gateway: &structs.ConsulGateway{
   537  				Proxy: nil,
   538  			},
   539  		})
   540  		require.Equal(t, &api.AgentServiceConnectProxyConfig{
   541  			Config: nil,
   542  		}, result)
   543  	})
   544  
   545  	t.Run("full", func(t *testing.T) {
   546  		result := newConnectGateway(&structs.ConsulConnect{
   547  			Gateway: &structs.ConsulGateway{
   548  				Proxy: &structs.ConsulGatewayProxy{
   549  					ConnectTimeout:                  pointer.Of(1 * time.Second),
   550  					EnvoyGatewayBindTaggedAddresses: true,
   551  					EnvoyGatewayBindAddresses: map[string]*structs.ConsulGatewayBindAddress{
   552  						"service1": {
   553  							Address: "10.0.0.1",
   554  							Port:    2000,
   555  						},
   556  					},
   557  					EnvoyGatewayNoDefaultBind: true,
   558  					EnvoyDNSDiscoveryType:     "STRICT_DNS",
   559  					Config: map[string]interface{}{
   560  						"foo": 1,
   561  					},
   562  				},
   563  			},
   564  		})
   565  		require.Equal(t, &api.AgentServiceConnectProxyConfig{
   566  			Config: map[string]interface{}{
   567  				"connect_timeout_ms":                  int64(1000),
   568  				"envoy_gateway_bind_tagged_addresses": true,
   569  				"envoy_gateway_bind_addresses": map[string]*structs.ConsulGatewayBindAddress{
   570  					"service1": {
   571  						Address: "10.0.0.1",
   572  						Port:    2000,
   573  					},
   574  				},
   575  				"envoy_gateway_no_default_bind": true,
   576  				"envoy_dns_discovery_type":      "STRICT_DNS",
   577  				"foo":                           1,
   578  			},
   579  		}, result)
   580  	})
   581  }
   582  
   583  func Test_connectMeshGateway(t *testing.T) {
   584  	ci.Parallel(t)
   585  
   586  	t.Run("empty", func(t *testing.T) {
   587  		result := connectMeshGateway(structs.ConsulMeshGateway{})
   588  		require.Equal(t, api.MeshGatewayConfig{Mode: api.MeshGatewayModeDefault}, result)
   589  	})
   590  
   591  	t.Run("local", func(t *testing.T) {
   592  		result := connectMeshGateway(structs.ConsulMeshGateway{Mode: "local"})
   593  		require.Equal(t, api.MeshGatewayConfig{Mode: api.MeshGatewayModeLocal}, result)
   594  	})
   595  
   596  	t.Run("remote", func(t *testing.T) {
   597  		result := connectMeshGateway(structs.ConsulMeshGateway{Mode: "remote"})
   598  		require.Equal(t, api.MeshGatewayConfig{Mode: api.MeshGatewayModeRemote}, result)
   599  	})
   600  
   601  	t.Run("none", func(t *testing.T) {
   602  		result := connectMeshGateway(structs.ConsulMeshGateway{Mode: "none"})
   603  		require.Equal(t, api.MeshGatewayConfig{Mode: api.MeshGatewayModeNone}, result)
   604  	})
   605  
   606  	t.Run("nonsense", func(t *testing.T) {
   607  		result := connectMeshGateway(structs.ConsulMeshGateway{})
   608  		require.Equal(t, api.MeshGatewayConfig{Mode: api.MeshGatewayModeDefault}, result)
   609  	})
   610  }
   611  
   612  func Test_injectNomadInfo(t *testing.T) {
   613  	ci.Parallel(t)
   614  
   615  	info1 := func() map[string]string {
   616  		return map[string]string{
   617  			"nomad.alloc_id=": "abc123",
   618  		}
   619  	}
   620  	info2 := func() map[string]string {
   621  		return map[string]string{
   622  			"nomad.alloc_id=":  "abc123",
   623  			"nomad.namespace=": "testns",
   624  		}
   625  	}
   626  
   627  	try := func(defaultTags map[string]string, cfg, exp map[string]interface{}) {
   628  		// TODO: defaultTags get modified over the execution
   629  		injectNomadInfo(cfg, defaultTags)
   630  		cfgTags, expTags := cfg["envoy_stats_tags"], exp["envoy_stats_tags"]
   631  		delete(cfg, "envoy_stats_tags")
   632  		delete(exp, "envoy_stats_tags")
   633  		require.Equal(t, exp, cfg)
   634  		require.ElementsMatch(t, expTags, cfgTags, "")
   635  	}
   636  
   637  	// empty
   638  	try(
   639  		info1(),
   640  		make(map[string]interface{}),
   641  		map[string]interface{}{
   642  			"envoy_stats_tags": []string{"nomad.alloc_id=abc123"},
   643  		},
   644  	)
   645  
   646  	// merge fresh
   647  	try(
   648  		info1(),
   649  		map[string]interface{}{"foo": "bar"},
   650  		map[string]interface{}{
   651  			"foo":              "bar",
   652  			"envoy_stats_tags": []string{"nomad.alloc_id=abc123"},
   653  		},
   654  	)
   655  
   656  	// merge append
   657  	try(
   658  		info1(),
   659  		map[string]interface{}{
   660  			"foo":              "bar",
   661  			"envoy_stats_tags": []string{"k1=v1", "k2=v2"},
   662  		},
   663  		map[string]interface{}{
   664  			"foo":              "bar",
   665  			"envoy_stats_tags": []string{"k1=v1", "k2=v2", "nomad.alloc_id=abc123"},
   666  		},
   667  	)
   668  
   669  	// merge exists
   670  	try(
   671  		info2(),
   672  		map[string]interface{}{
   673  			"foo":              "bar",
   674  			"envoy_stats_tags": []string{"k1=v1", "k2=v2", "nomad.alloc_id=xyz789"},
   675  		},
   676  		map[string]interface{}{
   677  			"foo":              "bar",
   678  			"envoy_stats_tags": []string{"k1=v1", "k2=v2", "nomad.alloc_id=xyz789", "nomad.namespace=testns"},
   679  		},
   680  	)
   681  
   682  	// merge wrong type
   683  	try(
   684  		info1(),
   685  		map[string]interface{}{
   686  			"envoy_stats_tags": "not a slice of string",
   687  		},
   688  		map[string]interface{}{
   689  			"envoy_stats_tags": []string{"nomad.alloc_id=abc123"},
   690  		},
   691  	)
   692  }