github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/fingerprint/env_aws_test.go (about) 1 package fingerprint 2 3 import ( 4 "fmt" 5 "net/http" 6 "net/http/httptest" 7 "testing" 8 9 "github.com/hashicorp/nomad/ci" 10 "github.com/hashicorp/nomad/client/config" 11 "github.com/hashicorp/nomad/helper/testlog" 12 "github.com/hashicorp/nomad/nomad/structs" 13 "github.com/stretchr/testify/require" 14 ) 15 16 func TestEnvAWSFingerprint_nonAws(t *testing.T) { 17 ci.Parallel(t) 18 19 f := NewEnvAWSFingerprint(testlog.HCLogger(t)) 20 f.(*EnvAWSFingerprint).endpoint = "http://127.0.0.1/latest" 21 22 node := &structs.Node{ 23 Attributes: make(map[string]string), 24 } 25 26 request := &FingerprintRequest{Config: &config.Config{}, Node: node} 27 var response FingerprintResponse 28 err := f.Fingerprint(request, &response) 29 require.NoError(t, err) 30 require.Empty(t, response.Attributes) 31 } 32 33 func TestEnvAWSFingerprint_aws(t *testing.T) { 34 ci.Parallel(t) 35 36 endpoint, cleanup := startFakeEC2Metadata(t, awsStubs) 37 defer cleanup() 38 39 f := NewEnvAWSFingerprint(testlog.HCLogger(t)) 40 f.(*EnvAWSFingerprint).endpoint = endpoint 41 42 node := &structs.Node{ 43 Attributes: make(map[string]string), 44 } 45 46 request := &FingerprintRequest{Config: &config.Config{}, Node: node} 47 var response FingerprintResponse 48 err := f.Fingerprint(request, &response) 49 require.NoError(t, err) 50 51 keys := []string{ 52 "platform.aws.ami-id", 53 "unique.platform.aws.hostname", 54 "unique.platform.aws.instance-id", 55 "platform.aws.instance-life-cycle", 56 "platform.aws.instance-type", 57 "unique.platform.aws.local-hostname", 58 "unique.platform.aws.local-ipv4", 59 "unique.platform.aws.public-hostname", 60 "unique.platform.aws.public-ipv4", 61 "platform.aws.placement.availability-zone", 62 "unique.network.ip-address", 63 } 64 65 for _, k := range keys { 66 assertNodeAttributeContains(t, response.Attributes, k) 67 } 68 69 require.NotEmpty(t, response.Links) 70 71 // confirm we have at least instance-id and ami-id 72 for _, k := range []string{"aws.ec2"} { 73 assertNodeLinksContains(t, response.Links, k) 74 } 75 } 76 77 func TestNetworkFingerprint_AWS(t *testing.T) { 78 ci.Parallel(t) 79 80 endpoint, cleanup := startFakeEC2Metadata(t, awsStubs) 81 defer cleanup() 82 83 f := NewEnvAWSFingerprint(testlog.HCLogger(t)) 84 f.(*EnvAWSFingerprint).endpoint = endpoint 85 86 node := &structs.Node{ 87 Attributes: make(map[string]string), 88 } 89 90 request := &FingerprintRequest{Config: &config.Config{}, Node: node} 91 var response FingerprintResponse 92 err := f.Fingerprint(request, &response) 93 require.NoError(t, err) 94 95 assertNodeAttributeContains(t, response.Attributes, "unique.network.ip-address") 96 97 require.NotNil(t, response.NodeResources) 98 require.Len(t, response.NodeResources.Networks, 1) 99 100 // Test at least the first Network Resource 101 net := response.NodeResources.Networks[0] 102 require.NotEmpty(t, net.IP, "Expected Network Resource to have an IP") 103 require.NotEmpty(t, net.CIDR, "Expected Network Resource to have a CIDR") 104 require.NotEmpty(t, net.Device, "Expected Network Resource to have a Device Name") 105 } 106 107 func TestNetworkFingerprint_AWS_network(t *testing.T) { 108 ci.Parallel(t) 109 110 endpoint, cleanup := startFakeEC2Metadata(t, awsStubs) 111 defer cleanup() 112 113 f := NewEnvAWSFingerprint(testlog.HCLogger(t)) 114 f.(*EnvAWSFingerprint).endpoint = endpoint 115 116 { 117 node := &structs.Node{ 118 Attributes: make(map[string]string), 119 } 120 121 request := &FingerprintRequest{Config: &config.Config{}, Node: node} 122 var response FingerprintResponse 123 err := f.Fingerprint(request, &response) 124 require.NoError(t, err) 125 126 require.True(t, response.Detected, "expected response to be applicable") 127 128 assertNodeAttributeContains(t, response.Attributes, "unique.network.ip-address") 129 130 require.NotNil(t, response.NodeResources) 131 require.Len(t, response.NodeResources.Networks, 1) 132 133 // Test at least the first Network Resource 134 net := response.NodeResources.Networks[0] 135 require.NotEmpty(t, net.IP, "Expected Network Resource to have an IP") 136 require.NotEmpty(t, net.CIDR, "Expected Network Resource to have a CIDR") 137 require.NotEmpty(t, net.Device, "Expected Network Resource to have a Device Name") 138 require.Equal(t, 1000, net.MBits) 139 } 140 141 // Try again this time setting a network speed in the config 142 { 143 node := &structs.Node{ 144 Attributes: make(map[string]string), 145 } 146 147 cfg := &config.Config{ 148 NetworkSpeed: 10, 149 } 150 151 request := &FingerprintRequest{Config: cfg, Node: node} 152 var response FingerprintResponse 153 err := f.Fingerprint(request, &response) 154 require.NoError(t, err) 155 156 assertNodeAttributeContains(t, response.Attributes, "unique.network.ip-address") 157 158 require.NotNil(t, response.NodeResources) 159 require.Len(t, response.NodeResources.Networks, 1) 160 161 // Test at least the first Network Resource 162 net := response.NodeResources.Networks[0] 163 require.NotEmpty(t, net.IP, "Expected Network Resource to have an IP") 164 require.NotEmpty(t, net.CIDR, "Expected Network Resource to have a CIDR") 165 require.NotEmpty(t, net.Device, "Expected Network Resource to have a Device Name") 166 require.Equal(t, 10, net.MBits) 167 } 168 } 169 170 func TestNetworkFingerprint_AWS_NoNetwork(t *testing.T) { 171 ci.Parallel(t) 172 173 endpoint, cleanup := startFakeEC2Metadata(t, noNetworkAWSStubs) 174 defer cleanup() 175 176 f := NewEnvAWSFingerprint(testlog.HCLogger(t)) 177 f.(*EnvAWSFingerprint).endpoint = endpoint 178 179 node := &structs.Node{ 180 Attributes: make(map[string]string), 181 } 182 183 request := &FingerprintRequest{Config: &config.Config{}, Node: node} 184 var response FingerprintResponse 185 err := f.Fingerprint(request, &response) 186 require.NoError(t, err) 187 188 require.True(t, response.Detected, "expected response to be applicable") 189 190 require.Equal(t, "ami-1234", response.Attributes["platform.aws.ami-id"]) 191 192 require.Nil(t, response.NodeResources.Networks) 193 } 194 195 func TestNetworkFingerprint_AWS_IncompleteImitation(t *testing.T) { 196 ci.Parallel(t) 197 198 endpoint, cleanup := startFakeEC2Metadata(t, incompleteAWSImitationStubs) 199 defer cleanup() 200 201 f := NewEnvAWSFingerprint(testlog.HCLogger(t)) 202 f.(*EnvAWSFingerprint).endpoint = endpoint 203 204 node := &structs.Node{ 205 Attributes: make(map[string]string), 206 } 207 208 request := &FingerprintRequest{Config: &config.Config{}, Node: node} 209 var response FingerprintResponse 210 err := f.Fingerprint(request, &response) 211 require.NoError(t, err) 212 213 require.False(t, response.Detected, "expected response not to be applicable") 214 215 require.NotContains(t, response.Attributes, "platform.aws.ami-id") 216 require.Nil(t, response.NodeResources) 217 } 218 219 func TestCPUFingerprint_AWS_InstanceFound(t *testing.T) { 220 ci.Parallel(t) 221 222 endpoint, cleanup := startFakeEC2Metadata(t, awsStubs) 223 defer cleanup() 224 225 f := NewEnvAWSFingerprint(testlog.HCLogger(t)) 226 f.(*EnvAWSFingerprint).endpoint = endpoint 227 228 node := &structs.Node{Attributes: make(map[string]string)} 229 230 request := &FingerprintRequest{Config: &config.Config{}, Node: node} 231 var response FingerprintResponse 232 err := f.Fingerprint(request, &response) 233 require.NoError(t, err) 234 require.True(t, response.Detected) 235 require.Equal(t, "2200", response.Attributes["cpu.frequency"]) 236 require.Equal(t, "8", response.Attributes["cpu.numcores"]) 237 require.Equal(t, "17600", response.Attributes["cpu.totalcompute"]) 238 require.Equal(t, 17600, response.Resources.CPU) 239 require.Equal(t, int64(17600), response.NodeResources.Cpu.CpuShares) 240 } 241 242 func TestCPUFingerprint_AWS_OverrideCompute(t *testing.T) { 243 ci.Parallel(t) 244 245 endpoint, cleanup := startFakeEC2Metadata(t, awsStubs) 246 defer cleanup() 247 248 f := NewEnvAWSFingerprint(testlog.HCLogger(t)) 249 f.(*EnvAWSFingerprint).endpoint = endpoint 250 251 node := &structs.Node{Attributes: make(map[string]string)} 252 253 request := &FingerprintRequest{Config: &config.Config{ 254 CpuCompute: 99999, 255 }, Node: node} 256 var response FingerprintResponse 257 err := f.Fingerprint(request, &response) 258 require.NoError(t, err) 259 require.True(t, response.Detected) 260 require.Equal(t, "2200", response.Attributes["cpu.frequency"]) 261 require.Equal(t, "8", response.Attributes["cpu.numcores"]) 262 require.Equal(t, "99999", response.Attributes["cpu.totalcompute"]) 263 require.Nil(t, response.Resources) // defaults in cpu fingerprinter 264 require.Zero(t, response.NodeResources.Cpu) // defaults in cpu fingerprinter 265 } 266 267 func TestCPUFingerprint_AWS_InstanceNotFound(t *testing.T) { 268 ci.Parallel(t) 269 270 endpoint, cleanup := startFakeEC2Metadata(t, unknownInstanceType) 271 defer cleanup() 272 273 f := NewEnvAWSFingerprint(testlog.HCLogger(t)) 274 f.(*EnvAWSFingerprint).endpoint = endpoint 275 276 node := &structs.Node{Attributes: make(map[string]string)} 277 278 request := &FingerprintRequest{Config: &config.Config{}, Node: node} 279 var response FingerprintResponse 280 err := f.Fingerprint(request, &response) 281 require.NoError(t, err) 282 require.True(t, response.Detected) 283 require.NotContains(t, response.Attributes, "cpu.modelname") 284 require.NotContains(t, response.Attributes, "cpu.frequency") 285 require.NotContains(t, response.Attributes, "cpu.numcores") 286 require.NotContains(t, response.Attributes, "cpu.totalcompute") 287 require.Nil(t, response.Resources) 288 require.Nil(t, response.NodeResources) 289 } 290 291 /// Utility functions for tests 292 293 func startFakeEC2Metadata(t *testing.T, endpoints []endpoint) (endpoint string, cleanup func()) { 294 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 295 for _, e := range endpoints { 296 if r.RequestURI == e.Uri { 297 w.Header().Set("Content-Type", e.ContentType) 298 fmt.Fprintln(w, e.Body) 299 } 300 } 301 })) 302 303 return ts.URL + "/latest", ts.Close 304 } 305 306 type routes struct { 307 Endpoints []*endpoint `json:"endpoints"` 308 } 309 310 type endpoint struct { 311 Uri string `json:"uri"` 312 ContentType string `json:"content-type"` 313 Body string `json:"body"` 314 } 315 316 // awsStubs mimics normal EC2 instance metadata 317 var awsStubs = []endpoint{ 318 { 319 Uri: "/latest/meta-data/ami-id", 320 ContentType: "text/plain", 321 Body: "ami-1234", 322 }, 323 { 324 Uri: "/latest/meta-data/hostname", 325 ContentType: "text/plain", 326 Body: "ip-10-0-0-207.us-west-2.compute.internal", 327 }, 328 { 329 Uri: "/latest/meta-data/placement/availability-zone", 330 ContentType: "text/plain", 331 Body: "us-west-2a", 332 }, 333 { 334 Uri: "/latest/meta-data/instance-id", 335 ContentType: "text/plain", 336 Body: "i-b3ba3875", 337 }, 338 { 339 Uri: "/latest/meta-data/instance-life-cycle", 340 ContentType: "text/plain", 341 Body: "on-demand", 342 }, 343 { 344 Uri: "/latest/meta-data/instance-type", 345 ContentType: "text/plain", 346 Body: "t3a.2xlarge", 347 }, 348 { 349 Uri: "/latest/meta-data/local-hostname", 350 ContentType: "text/plain", 351 Body: "ip-10-0-0-207.us-west-2.compute.internal", 352 }, 353 { 354 Uri: "/latest/meta-data/local-ipv4", 355 ContentType: "text/plain", 356 Body: "10.0.0.207", 357 }, 358 { 359 Uri: "/latest/meta-data/public-hostname", 360 ContentType: "text/plain", 361 Body: "ec2-54-191-117-175.us-west-2.compute.amazonaws.com", 362 }, 363 { 364 Uri: "/latest/meta-data/public-ipv4", 365 ContentType: "text/plain", 366 Body: "54.191.117.175", 367 }, 368 { 369 Uri: "/latest/meta-data/mac", 370 ContentType: "text/plain", 371 Body: "0a:20:d2:42:b3:55", 372 }, 373 } 374 375 var unknownInstanceType = []endpoint{ 376 { 377 Uri: "/latest/meta-data/ami-id", 378 ContentType: "text/plain", 379 Body: "ami-1234", 380 }, 381 { 382 Uri: "/latest/meta-data/hostname", 383 ContentType: "text/plain", 384 Body: "ip-10-0-0-207.us-west-2.compute.internal", 385 }, 386 { 387 Uri: "/latest/meta-data/placement/availability-zone", 388 ContentType: "text/plain", 389 Body: "us-west-2a", 390 }, 391 { 392 Uri: "/latest/meta-data/instance-id", 393 ContentType: "text/plain", 394 Body: "i-b3ba3875", 395 }, 396 { 397 Uri: "/latest/meta-data/instance-life-cycle", 398 ContentType: "text/plain", 399 Body: "on-demand", 400 }, 401 { 402 Uri: "/latest/meta-data/instance-type", 403 ContentType: "text/plain", 404 Body: "xyz123.uber", 405 }, 406 } 407 408 // noNetworkAWSStubs mimics an EC2 instance but without local ip address 409 // may happen in environments with odd EC2 Metadata emulation 410 var noNetworkAWSStubs = []endpoint{ 411 { 412 Uri: "/latest/meta-data/ami-id", 413 ContentType: "text/plain", 414 Body: "ami-1234", 415 }, 416 { 417 Uri: "/latest/meta-data/hostname", 418 ContentType: "text/plain", 419 Body: "ip-10-0-0-207.us-west-2.compute.internal", 420 }, 421 { 422 Uri: "/latest/meta-data/placement/availability-zone", 423 ContentType: "text/plain", 424 Body: "us-west-2a", 425 }, 426 { 427 Uri: "/latest/meta-data/instance-id", 428 ContentType: "text/plain", 429 Body: "i-b3ba3875", 430 }, 431 { 432 Uri: "/latest/meta-data/instance-life-cycle", 433 ContentType: "text/plain", 434 Body: "on-demand", 435 }, 436 { 437 Uri: "/latest/meta-data/instance-type", 438 ContentType: "text/plain", 439 Body: "m3.2xlarge", 440 }, 441 { 442 Uri: "/latest/meta-data/local-hostname", 443 ContentType: "text/plain", 444 Body: "ip-10-0-0-207.us-west-2.compute.internal", 445 }, 446 { 447 Uri: "/latest/meta-data/local-ipv4", 448 ContentType: "text/plain", 449 Body: "", 450 }, 451 { 452 Uri: "/latest/meta-data/public-hostname", 453 ContentType: "text/plain", 454 Body: "ec2-54-191-117-175.us-west-2.compute.amazonaws.com", 455 }, 456 { 457 Uri: "/latest/meta-data/public-ipv4", 458 ContentType: "text/plain", 459 Body: "54.191.117.175", 460 }, 461 } 462 463 // incompleteAWSImitationsStub mimics environments where some AWS endpoints 464 // return empty, namely Hetzner 465 var incompleteAWSImitationStubs = []endpoint{ 466 { 467 Uri: "/latest/meta-data/hostname", 468 ContentType: "text/plain", 469 Body: "ip-10-0-0-207.us-west-2.compute.internal", 470 }, 471 { 472 Uri: "/latest/meta-data/instance-id", 473 ContentType: "text/plain", 474 Body: "i-b3ba3875", 475 }, 476 { 477 Uri: "/latest/meta-data/local-ipv4", 478 ContentType: "text/plain", 479 Body: "", 480 }, 481 { 482 Uri: "/latest/meta-data/public-ipv4", 483 ContentType: "text/plain", 484 Body: "54.191.117.175", 485 }, 486 }