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 }