open-nomad/client/taskenv/services_test.go
Seth Hoenig b51459a879 consul/connect: interpolate connect block
This PR enables job submitters to use interpolation in the connect
block of jobs making use of consul connect. Before, only the name of
the connect service would be interpolated, and only for a few select
identifiers related to the job itself (#6853). Now, all connect fields
can be interpolated using the full spectrum of runtime parameters.

Note that the service name is interpolated at job-submission time,
and cannot make use of values known only at runtime.

Fixes #7221
2020-12-09 09:10:00 -06:00

417 lines
10 KiB
Go

package taskenv
import (
"testing"
"time"
"github.com/hashicorp/nomad/helper"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/stretchr/testify/require"
)
// TestInterpolateServices asserts that all service
// and check fields are properly interpolated.
func TestInterpolateServices(t *testing.T) {
t.Parallel()
services := []*structs.Service{
{
Name: "${name}",
PortLabel: "${portlabel}",
Tags: []string{"${tags}"},
Meta: map[string]string{
"meta-key": "${meta}",
},
CanaryMeta: map[string]string{
"canarymeta-key": "${canarymeta}",
},
Checks: []*structs.ServiceCheck{
{
Name: "${checkname}",
Type: "${checktype}",
Command: "${checkcmd}",
Args: []string{"${checkarg}"},
Path: "${checkstr}",
Protocol: "${checkproto}",
PortLabel: "${checklabel}",
InitialStatus: "${checkstatus}",
Method: "${checkmethod}",
Header: map[string][]string{
"${checkheaderk}": {"${checkheaderv}"},
},
},
},
},
}
env := &TaskEnv{
EnvMap: map[string]string{
"name": "name",
"portlabel": "portlabel",
"tags": "tags",
"meta": "meta-value",
"canarymeta": "canarymeta-value",
"checkname": "checkname",
"checktype": "checktype",
"checkcmd": "checkcmd",
"checkarg": "checkarg",
"checkstr": "checkstr",
"checkpath": "checkpath",
"checkproto": "checkproto",
"checklabel": "checklabel",
"checkstatus": "checkstatus",
"checkmethod": "checkmethod",
"checkheaderk": "checkheaderk",
"checkheaderv": "checkheaderv",
},
}
interpolated := InterpolateServices(env, services)
exp := []*structs.Service{
{
Name: "name",
PortLabel: "portlabel",
Tags: []string{"tags"},
Meta: map[string]string{
"meta-key": "meta-value",
},
CanaryMeta: map[string]string{
"canarymeta-key": "canarymeta-value",
},
Checks: []*structs.ServiceCheck{
{
Name: "checkname",
Type: "checktype",
Command: "checkcmd",
Args: []string{"checkarg"},
Path: "checkstr",
Protocol: "checkproto",
PortLabel: "checklabel",
InitialStatus: "checkstatus",
Method: "checkmethod",
Header: map[string][]string{
"checkheaderk": {"checkheaderv"},
},
},
},
},
}
require.Equal(t, exp, interpolated)
}
var testEnv = NewTaskEnv(
map[string]string{"foo": "bar", "baz": "blah"},
nil,
nil,
)
func TestInterpolate_interpolateMapStringSliceString(t *testing.T) {
t.Parallel()
t.Run("nil", func(t *testing.T) {
require.Nil(t, interpolateMapStringSliceString(testEnv, nil))
})
t.Run("not nil", func(t *testing.T) {
require.Equal(t, map[string][]string{
"a": {"b"},
"bar": {"blah", "c"},
}, interpolateMapStringSliceString(testEnv, map[string][]string{
"a": {"b"},
"${foo}": {"${baz}", "c"},
}))
})
}
func TestInterpolate_interpolateMapStringString(t *testing.T) {
t.Parallel()
t.Run("nil", func(t *testing.T) {
require.Nil(t, interpolateMapStringString(testEnv, nil))
})
t.Run("not nil", func(t *testing.T) {
require.Equal(t, map[string]string{
"a": "b",
"bar": "blah",
}, interpolateMapStringString(testEnv, map[string]string{
"a": "b",
"${foo}": "${baz}",
}))
})
}
func TestInterpolate_interpolateMapStringInterface(t *testing.T) {
t.Parallel()
t.Run("nil", func(t *testing.T) {
require.Nil(t, interpolateMapStringInterface(testEnv, nil))
})
t.Run("not nil", func(t *testing.T) {
require.Equal(t, map[string]interface{}{
"a": 1,
"bar": 2,
}, interpolateMapStringInterface(testEnv, map[string]interface{}{
"a": 1,
"${foo}": 2,
}))
})
}
func TestInterpolate_interpolateConnect(t *testing.T) {
t.Parallel()
env := NewTaskEnv(map[string]string{
"tag1": "_tag1",
"port1": "12345",
"address1": "1.2.3.4",
"destination1": "_dest1",
"datacenter1": "_datacenter1",
"path1": "_path1",
"protocol1": "_protocol1",
"port2": "_port2",
"config1": "_config1",
"driver1": "_driver1",
"user1": "_user1",
"config2": "_config2",
"env1": "_env1",
"env2": "_env2",
"mode1": "_mode1",
"device1": "_device1",
"cidr1": "10.0.0.0/64",
"ip1": "1.1.1.1",
"server1": "10.0.0.1",
"search1": "10.0.0.2",
"option1": "10.0.0.3",
"port3": "_port3",
"network1": "_network1",
"port4": "_port4",
"network2": "_network2",
"resource1": "_resource1",
"meta1": "_meta1",
"meta2": "_meta2",
"signal1": "_signal1",
"bind1": "_bind1",
"address2": "10.0.0.4",
"config3": "_config3",
"protocol2": "_protocol2",
"service1": "_service1",
"host1": "_host1",
}, nil, nil)
connect := &structs.ConsulConnect{
Native: false,
SidecarService: &structs.ConsulSidecarService{
Tags: []string{"${tag1}", "tag2"},
Port: "${port1}",
Proxy: &structs.ConsulProxy{
LocalServiceAddress: "${address1}",
LocalServicePort: 10000,
Upstreams: []structs.ConsulUpstream{{
DestinationName: "${destination1}",
Datacenter: "${datacenter1}",
LocalBindPort: 10001,
}},
Expose: &structs.ConsulExposeConfig{
Paths: []structs.ConsulExposePath{{
Path: "${path1}",
Protocol: "${protocol1}",
ListenerPort: "${port2}",
LocalPathPort: 10002,
}},
},
Config: map[string]interface{}{
"${config1}": 1,
},
},
},
SidecarTask: &structs.SidecarTask{
Name: "name", // not interpolated by taskenv
Driver: "${driver1}",
User: "${user1}",
Config: map[string]interface{}{"${config2}": 2},
Env: map[string]string{"${env1}": "${env2}"},
Resources: &structs.Resources{
CPU: 1,
MemoryMB: 2,
DiskMB: 3,
IOPS: 4,
Networks: structs.Networks{{
Mode: "${mode1}",
Device: "${device1}",
CIDR: "${cidr1}",
IP: "${ip1}",
MBits: 1,
DNS: &structs.DNSConfig{
Servers: []string{"${server1}"},
Searches: []string{"${search1}"},
Options: []string{"${option1}"},
},
ReservedPorts: []structs.Port{{
Label: "${port3}",
Value: 9000,
To: 9000,
HostNetwork: "${network1}",
}},
DynamicPorts: []structs.Port{{
Label: "${port4}",
Value: 9001,
To: 9001,
HostNetwork: "${network2}",
}},
}},
Devices: structs.ResourceDevices{{
Name: "${resource1}",
}},
},
Meta: map[string]string{"${meta1}": "${meta2}"},
KillTimeout: helper.TimeToPtr(1 * time.Second),
LogConfig: &structs.LogConfig{
MaxFiles: 1,
MaxFileSizeMB: 2,
},
ShutdownDelay: helper.TimeToPtr(2 * time.Second),
KillSignal: "${signal1}",
},
Gateway: &structs.ConsulGateway{
Proxy: &structs.ConsulGatewayProxy{
ConnectTimeout: helper.TimeToPtr(3 * time.Second),
EnvoyGatewayBindTaggedAddresses: true,
EnvoyGatewayBindAddresses: map[string]*structs.ConsulGatewayBindAddress{
"${bind1}": {
Address: "${address2}",
Port: 8000,
},
},
EnvoyGatewayNoDefaultBind: true,
Config: map[string]interface{}{
"${config3}": 4,
},
},
Ingress: &structs.ConsulIngressConfigEntry{
TLS: &structs.ConsulGatewayTLSConfig{
Enabled: true,
},
Listeners: []*structs.ConsulIngressListener{{
Protocol: "${protocol2}",
Port: 8001,
Services: []*structs.ConsulIngressService{{
Name: "${service1}",
Hosts: []string{"${host1}", "host2"},
}},
}},
},
},
}
result := interpolateConnect(env, connect)
require.Equal(t, &structs.ConsulConnect{
Native: false,
SidecarService: &structs.ConsulSidecarService{
Tags: []string{"_tag1", "tag2"},
Port: "12345",
Proxy: &structs.ConsulProxy{
LocalServiceAddress: "1.2.3.4",
LocalServicePort: 10000,
Upstreams: []structs.ConsulUpstream{{
DestinationName: "_dest1",
Datacenter: "_datacenter1",
LocalBindPort: 10001,
}},
Expose: &structs.ConsulExposeConfig{
Paths: []structs.ConsulExposePath{{
Path: "_path1",
Protocol: "_protocol1",
ListenerPort: "_port2",
LocalPathPort: 10002,
}},
},
Config: map[string]interface{}{
"_config1": 1,
},
},
},
SidecarTask: &structs.SidecarTask{
Name: "name", // not interpolated by InterpolateServices
Driver: "_driver1",
User: "_user1",
Config: map[string]interface{}{"_config2": 2},
Env: map[string]string{"_env1": "_env2"},
Resources: &structs.Resources{
CPU: 1,
MemoryMB: 2,
DiskMB: 3,
IOPS: 4,
Networks: structs.Networks{{
Mode: "_mode1",
Device: "_device1",
CIDR: "10.0.0.0/64",
IP: "1.1.1.1",
MBits: 1,
DNS: &structs.DNSConfig{
Servers: []string{"10.0.0.1"},
Searches: []string{"10.0.0.2"},
Options: []string{"10.0.0.3"},
},
ReservedPorts: []structs.Port{{
Label: "_port3",
Value: 9000,
To: 9000,
HostNetwork: "_network1",
}},
DynamicPorts: []structs.Port{{
Label: "_port4",
Value: 9001,
To: 9001,
HostNetwork: "_network2",
}},
}},
Devices: structs.ResourceDevices{{
Name: "_resource1",
}},
},
Meta: map[string]string{"_meta1": "_meta2"},
KillTimeout: helper.TimeToPtr(1 * time.Second),
LogConfig: &structs.LogConfig{
MaxFiles: 1,
MaxFileSizeMB: 2,
},
ShutdownDelay: helper.TimeToPtr(2 * time.Second),
KillSignal: "_signal1",
},
Gateway: &structs.ConsulGateway{
Proxy: &structs.ConsulGatewayProxy{
ConnectTimeout: helper.TimeToPtr(3 * time.Second),
EnvoyGatewayBindTaggedAddresses: true,
EnvoyGatewayBindAddresses: map[string]*structs.ConsulGatewayBindAddress{
"_bind1": {
Address: "10.0.0.4",
Port: 8000,
},
},
EnvoyGatewayNoDefaultBind: true,
Config: map[string]interface{}{
"_config3": 4,
},
},
Ingress: &structs.ConsulIngressConfigEntry{
TLS: &structs.ConsulGatewayTLSConfig{
Enabled: true,
},
Listeners: []*structs.ConsulIngressListener{{
Protocol: "_protocol2",
Port: 8001,
Services: []*structs.ConsulIngressService{{
Name: "_service1",
Hosts: []string{"_host1", "host2"},
}},
}},
},
},
}, result)
}