github.com/quite/nomad@v0.8.6/client/driver/env/env_test.go (about)

     1  package env
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"reflect"
     7  	"sort"
     8  	"strings"
     9  	"testing"
    10  
    11  	cstructs "github.com/hashicorp/nomad/client/structs"
    12  	"github.com/hashicorp/nomad/nomad/mock"
    13  	"github.com/hashicorp/nomad/nomad/structs"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  const (
    18  	// Node values that tests can rely on
    19  	metaKey   = "instance"
    20  	metaVal   = "t2-micro"
    21  	attrKey   = "arch"
    22  	attrVal   = "amd64"
    23  	nodeName  = "test node"
    24  	nodeClass = "test class"
    25  
    26  	// Environment variable values that tests can rely on
    27  	envOneKey = "NOMAD_IP"
    28  	envOneVal = "127.0.0.1"
    29  	envTwoKey = "NOMAD_PORT_WEB"
    30  	envTwoVal = ":80"
    31  )
    32  
    33  var (
    34  	// portMap for use in tests as its set after Builder creation
    35  	portMap = map[string]int{
    36  		"https": 443,
    37  	}
    38  )
    39  
    40  func testEnvBuilder() *Builder {
    41  	n := mock.Node()
    42  	n.Attributes = map[string]string{
    43  		attrKey: attrVal,
    44  	}
    45  	n.Meta = map[string]string{
    46  		metaKey: metaVal,
    47  	}
    48  	n.Name = nodeName
    49  	n.NodeClass = nodeClass
    50  
    51  	task := mock.Job().TaskGroups[0].Tasks[0]
    52  	task.Env = map[string]string{
    53  		envOneKey: envOneVal,
    54  		envTwoKey: envTwoVal,
    55  	}
    56  	return NewBuilder(n, mock.Alloc(), task, "global")
    57  }
    58  
    59  func TestEnvironment_ParseAndReplace_Env(t *testing.T) {
    60  	env := testEnvBuilder()
    61  
    62  	input := []string{fmt.Sprintf(`"${%v}"!`, envOneKey), fmt.Sprintf("${%s}${%s}", envOneKey, envTwoKey)}
    63  	act := env.Build().ParseAndReplace(input)
    64  	exp := []string{fmt.Sprintf(`"%s"!`, envOneVal), fmt.Sprintf("%s%s", envOneVal, envTwoVal)}
    65  
    66  	if !reflect.DeepEqual(act, exp) {
    67  		t.Fatalf("ParseAndReplace(%v) returned %#v; want %#v", input, act, exp)
    68  	}
    69  }
    70  
    71  func TestEnvironment_ParseAndReplace_Meta(t *testing.T) {
    72  	input := []string{fmt.Sprintf("${%v%v}", nodeMetaPrefix, metaKey)}
    73  	exp := []string{metaVal}
    74  	env := testEnvBuilder()
    75  	act := env.Build().ParseAndReplace(input)
    76  
    77  	if !reflect.DeepEqual(act, exp) {
    78  		t.Fatalf("ParseAndReplace(%v) returned %#v; want %#v", input, act, exp)
    79  	}
    80  }
    81  
    82  func TestEnvironment_ParseAndReplace_Attr(t *testing.T) {
    83  	input := []string{fmt.Sprintf("${%v%v}", nodeAttributePrefix, attrKey)}
    84  	exp := []string{attrVal}
    85  	env := testEnvBuilder()
    86  	act := env.Build().ParseAndReplace(input)
    87  
    88  	if !reflect.DeepEqual(act, exp) {
    89  		t.Fatalf("ParseAndReplace(%v) returned %#v; want %#v", input, act, exp)
    90  	}
    91  }
    92  
    93  func TestEnvironment_ParseAndReplace_Node(t *testing.T) {
    94  	input := []string{fmt.Sprintf("${%v}", nodeNameKey), fmt.Sprintf("${%v}", nodeClassKey)}
    95  	exp := []string{nodeName, nodeClass}
    96  	env := testEnvBuilder()
    97  	act := env.Build().ParseAndReplace(input)
    98  
    99  	if !reflect.DeepEqual(act, exp) {
   100  		t.Fatalf("ParseAndReplace(%v) returned %#v; want %#v", input, act, exp)
   101  	}
   102  }
   103  
   104  func TestEnvironment_ParseAndReplace_Mixed(t *testing.T) {
   105  	input := []string{
   106  		fmt.Sprintf("${%v}${%v%v}", nodeNameKey, nodeAttributePrefix, attrKey),
   107  		fmt.Sprintf("${%v}${%v%v}", nodeClassKey, nodeMetaPrefix, metaKey),
   108  		fmt.Sprintf("${%v}${%v}", envTwoKey, nodeClassKey),
   109  	}
   110  	exp := []string{
   111  		fmt.Sprintf("%v%v", nodeName, attrVal),
   112  		fmt.Sprintf("%v%v", nodeClass, metaVal),
   113  		fmt.Sprintf("%v%v", envTwoVal, nodeClass),
   114  	}
   115  	env := testEnvBuilder()
   116  	act := env.Build().ParseAndReplace(input)
   117  
   118  	if !reflect.DeepEqual(act, exp) {
   119  		t.Fatalf("ParseAndReplace(%v) returned %#v; want %#v", input, act, exp)
   120  	}
   121  }
   122  
   123  func TestEnvironment_ReplaceEnv_Mixed(t *testing.T) {
   124  	input := fmt.Sprintf("${%v}${%v%v}", nodeNameKey, nodeAttributePrefix, attrKey)
   125  	exp := fmt.Sprintf("%v%v", nodeName, attrVal)
   126  	env := testEnvBuilder()
   127  	act := env.Build().ReplaceEnv(input)
   128  
   129  	if act != exp {
   130  		t.Fatalf("ParseAndReplace(%v) returned %#v; want %#v", input, act, exp)
   131  	}
   132  }
   133  
   134  func TestEnvironment_AsList(t *testing.T) {
   135  	n := mock.Node()
   136  	n.Meta = map[string]string{
   137  		"metaKey": "metaVal",
   138  	}
   139  	a := mock.Alloc()
   140  	a.Resources.Networks[0].ReservedPorts = append(a.Resources.Networks[0].ReservedPorts,
   141  		structs.Port{Label: "ssh", Value: 22},
   142  		structs.Port{Label: "other", Value: 1234},
   143  	)
   144  	a.TaskResources["web"].Networks[0].DynamicPorts[0].Value = 2000
   145  	a.TaskResources["ssh"] = &structs.Resources{
   146  		Networks: []*structs.NetworkResource{
   147  			{
   148  				Device: "eth0",
   149  				IP:     "192.168.0.100",
   150  				MBits:  50,
   151  				ReservedPorts: []structs.Port{
   152  					{Label: "ssh", Value: 22},
   153  					{Label: "other", Value: 1234},
   154  				},
   155  			},
   156  		},
   157  	}
   158  	task := a.Job.TaskGroups[0].Tasks[0]
   159  	task.Env = map[string]string{
   160  		"taskEnvKey": "taskEnvVal",
   161  	}
   162  	task.Resources.Networks = []*structs.NetworkResource{
   163  		{
   164  			IP:            "127.0.0.1",
   165  			ReservedPorts: []structs.Port{{Label: "http", Value: 80}},
   166  			DynamicPorts:  []structs.Port{{Label: "https", Value: 8080}},
   167  		},
   168  	}
   169  	env := NewBuilder(n, a, task, "global").SetDriverNetwork(
   170  		&cstructs.DriverNetwork{PortMap: map[string]int{"https": 443}},
   171  	)
   172  
   173  	act := env.Build().List()
   174  	exp := []string{
   175  		"taskEnvKey=taskEnvVal",
   176  		"NOMAD_ADDR_http=127.0.0.1:80",
   177  		"NOMAD_PORT_http=80",
   178  		"NOMAD_IP_http=127.0.0.1",
   179  		"NOMAD_ADDR_https=127.0.0.1:8080",
   180  		"NOMAD_PORT_https=443",
   181  		"NOMAD_IP_https=127.0.0.1",
   182  		"NOMAD_HOST_PORT_http=80",
   183  		"NOMAD_HOST_PORT_https=8080",
   184  		"NOMAD_TASK_NAME=web",
   185  		"NOMAD_GROUP_NAME=web",
   186  		"NOMAD_ADDR_ssh_other=192.168.0.100:1234",
   187  		"NOMAD_ADDR_ssh_ssh=192.168.0.100:22",
   188  		"NOMAD_IP_ssh_other=192.168.0.100",
   189  		"NOMAD_IP_ssh_ssh=192.168.0.100",
   190  		"NOMAD_PORT_ssh_other=1234",
   191  		"NOMAD_PORT_ssh_ssh=22",
   192  		"NOMAD_CPU_LIMIT=500",
   193  		"NOMAD_DC=dc1",
   194  		"NOMAD_REGION=global",
   195  		"NOMAD_MEMORY_LIMIT=256",
   196  		"NOMAD_META_ELB_CHECK_INTERVAL=30s",
   197  		"NOMAD_META_ELB_CHECK_MIN=3",
   198  		"NOMAD_META_ELB_CHECK_TYPE=http",
   199  		"NOMAD_META_FOO=bar",
   200  		"NOMAD_META_OWNER=armon",
   201  		"NOMAD_META_elb_check_interval=30s",
   202  		"NOMAD_META_elb_check_min=3",
   203  		"NOMAD_META_elb_check_type=http",
   204  		"NOMAD_META_foo=bar",
   205  		"NOMAD_META_owner=armon",
   206  		"NOMAD_JOB_NAME=my-job",
   207  		fmt.Sprintf("NOMAD_ALLOC_ID=%s", a.ID),
   208  		"NOMAD_ALLOC_INDEX=0",
   209  	}
   210  	sort.Strings(act)
   211  	sort.Strings(exp)
   212  	if len(act) != len(exp) {
   213  		t.Fatalf("expected %d vars != %d actual, actual:\n%s\n\nexpected:\n%s\n",
   214  			len(act), len(exp), strings.Join(act, "\n"), strings.Join(exp, "\n"))
   215  	}
   216  	for i := range act {
   217  		if act[i] != exp[i] {
   218  			t.Errorf("%d actual %q != %q expected", i, act[i], exp[i])
   219  		}
   220  	}
   221  }
   222  
   223  func TestEnvironment_VaultToken(t *testing.T) {
   224  	n := mock.Node()
   225  	a := mock.Alloc()
   226  	env := NewBuilder(n, a, a.Job.TaskGroups[0].Tasks[0], "global")
   227  	env.SetVaultToken("123", false)
   228  
   229  	{
   230  		act := env.Build().All()
   231  		if act[VaultToken] != "" {
   232  			t.Fatalf("Unexpected environment variables: %s=%q", VaultToken, act[VaultToken])
   233  		}
   234  	}
   235  
   236  	{
   237  		act := env.SetVaultToken("123", true).Build().List()
   238  		exp := "VAULT_TOKEN=123"
   239  		found := false
   240  		for _, entry := range act {
   241  			if entry == exp {
   242  				found = true
   243  				break
   244  			}
   245  		}
   246  		if !found {
   247  			t.Fatalf("did not find %q in:\n%s", exp, strings.Join(act, "\n"))
   248  		}
   249  	}
   250  }
   251  
   252  func TestEnvironment_Envvars(t *testing.T) {
   253  	envMap := map[string]string{"foo": "baz", "bar": "bang"}
   254  	n := mock.Node()
   255  	a := mock.Alloc()
   256  	task := a.Job.TaskGroups[0].Tasks[0]
   257  	task.Env = envMap
   258  	net := &cstructs.DriverNetwork{PortMap: portMap}
   259  	act := NewBuilder(n, a, task, "global").SetDriverNetwork(net).Build().All()
   260  	for k, v := range envMap {
   261  		actV, ok := act[k]
   262  		if !ok {
   263  			t.Fatalf("missing %q in %#v", k, act)
   264  		}
   265  		if v != actV {
   266  			t.Fatalf("expected %s=%q but found %q", k, v, actV)
   267  		}
   268  	}
   269  }
   270  
   271  func TestEnvironment_Interpolate(t *testing.T) {
   272  	n := mock.Node()
   273  	n.Attributes["arch"] = "x86"
   274  	n.NodeClass = "test class"
   275  	a := mock.Alloc()
   276  	task := a.Job.TaskGroups[0].Tasks[0]
   277  	task.Env = map[string]string{"test": "${node.class}", "test2": "${attr.arch}"}
   278  	env := NewBuilder(n, a, task, "global").Build()
   279  
   280  	exp := []string{fmt.Sprintf("test=%s", n.NodeClass), fmt.Sprintf("test2=%s", n.Attributes["arch"])}
   281  	found1, found2 := false, false
   282  	for _, entry := range env.List() {
   283  		switch entry {
   284  		case exp[0]:
   285  			found1 = true
   286  		case exp[1]:
   287  			found2 = true
   288  		}
   289  	}
   290  	if !found1 || !found2 {
   291  		t.Fatalf("expected to find %q and %q but got:\n%s",
   292  			exp[0], exp[1], strings.Join(env.List(), "\n"))
   293  	}
   294  }
   295  
   296  func TestEnvironment_AppendHostEnvvars(t *testing.T) {
   297  	host := os.Environ()
   298  	if len(host) < 2 {
   299  		t.Skip("No host environment variables. Can't test")
   300  	}
   301  	skip := strings.Split(host[0], "=")[0]
   302  	env := testEnvBuilder().
   303  		SetHostEnvvars([]string{skip}).
   304  		Build()
   305  
   306  	act := env.Map()
   307  	if len(act) < 1 {
   308  		t.Fatalf("Host environment variables not properly set")
   309  	}
   310  	if _, ok := act[skip]; ok {
   311  		t.Fatalf("Didn't filter environment variable %q", skip)
   312  	}
   313  }
   314  
   315  // TestEnvironment_DashesInTaskName asserts dashes in port labels are properly
   316  // converted to underscores in environment variables.
   317  // See: https://github.com/hashicorp/nomad/issues/2405
   318  func TestEnvironment_DashesInTaskName(t *testing.T) {
   319  	a := mock.Alloc()
   320  	task := a.Job.TaskGroups[0].Tasks[0]
   321  	task.Env = map[string]string{"test-one-two": "three-four"}
   322  	envMap := NewBuilder(mock.Node(), a, task, "global").Build().Map()
   323  
   324  	if envMap["test_one_two"] != "three-four" {
   325  		t.Fatalf("Expected test_one_two=three-four in TaskEnv; found:\n%#v", envMap)
   326  	}
   327  }
   328  
   329  // TestEnvironment_UpdateTask asserts env vars and task meta are updated when a
   330  // task is updated.
   331  func TestEnvironment_UpdateTask(t *testing.T) {
   332  	a := mock.Alloc()
   333  	a.Job.TaskGroups[0].Meta = map[string]string{"tgmeta": "tgmetaval"}
   334  	task := a.Job.TaskGroups[0].Tasks[0]
   335  	task.Name = "orig"
   336  	task.Env = map[string]string{"taskenv": "taskenvval"}
   337  	task.Meta = map[string]string{"taskmeta": "taskmetaval"}
   338  	builder := NewBuilder(mock.Node(), a, task, "global")
   339  
   340  	origMap := builder.Build().Map()
   341  	if origMap["NOMAD_TASK_NAME"] != "orig" {
   342  		t.Errorf("Expected NOMAD_TASK_NAME=orig but found %q", origMap["NOMAD_TASK_NAME"])
   343  	}
   344  	if origMap["NOMAD_META_taskmeta"] != "taskmetaval" {
   345  		t.Errorf("Expected NOMAD_META_taskmeta=taskmetaval but found %q", origMap["NOMAD_META_taskmeta"])
   346  	}
   347  	if origMap["taskenv"] != "taskenvval" {
   348  		t.Errorf("Expected taskenv=taskenvva but found %q", origMap["taskenv"])
   349  	}
   350  	if origMap["NOMAD_META_tgmeta"] != "tgmetaval" {
   351  		t.Errorf("Expected NOMAD_META_tgmeta=tgmetaval but found %q", origMap["NOMAD_META_tgmeta"])
   352  	}
   353  
   354  	a.Job.TaskGroups[0].Meta = map[string]string{"tgmeta2": "tgmetaval2"}
   355  	task.Name = "new"
   356  	task.Env = map[string]string{"taskenv2": "taskenvval2"}
   357  	task.Meta = map[string]string{"taskmeta2": "taskmetaval2"}
   358  
   359  	newMap := builder.UpdateTask(a, task).Build().Map()
   360  	if newMap["NOMAD_TASK_NAME"] != "new" {
   361  		t.Errorf("Expected NOMAD_TASK_NAME=new but found %q", newMap["NOMAD_TASK_NAME"])
   362  	}
   363  	if newMap["NOMAD_META_taskmeta2"] != "taskmetaval2" {
   364  		t.Errorf("Expected NOMAD_META_taskmeta=taskmetaval but found %q", newMap["NOMAD_META_taskmeta2"])
   365  	}
   366  	if newMap["taskenv2"] != "taskenvval2" {
   367  		t.Errorf("Expected taskenv=taskenvva but found %q", newMap["taskenv2"])
   368  	}
   369  	if newMap["NOMAD_META_tgmeta2"] != "tgmetaval2" {
   370  		t.Errorf("Expected NOMAD_META_tgmeta=tgmetaval but found %q", newMap["NOMAD_META_tgmeta2"])
   371  	}
   372  	if v, ok := newMap["NOMAD_META_taskmeta"]; ok {
   373  		t.Errorf("Expected NOMAD_META_taskmeta to be unset but found: %q", v)
   374  	}
   375  }
   376  
   377  // TestEnvironment_InterpolateEmptyOptionalMeta asserts that in a parameterized
   378  // job, if an optional meta field is not set, it will get interpolated as an
   379  // empty string.
   380  func TestEnvironment_InterpolateEmptyOptionalMeta(t *testing.T) {
   381  	require := require.New(t)
   382  	a := mock.Alloc()
   383  	a.Job.ParameterizedJob = &structs.ParameterizedJobConfig{
   384  		MetaOptional: []string{"metaopt1", "metaopt2"},
   385  	}
   386  	a.Job.Dispatched = true
   387  	task := a.Job.TaskGroups[0].Tasks[0]
   388  	task.Meta = map[string]string{"metaopt1": "metaopt1val"}
   389  	env := NewBuilder(mock.Node(), a, task, "global").Build()
   390  	require.Equal("metaopt1val", env.ReplaceEnv("${NOMAD_META_metaopt1}"))
   391  	require.Empty(env.ReplaceEnv("${NOMAD_META_metaopt2}"))
   392  }