github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/command/agent/consul/connect_test.go (about)

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