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