github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/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.Networks) 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, "2200", response.Attributes["cpu.frequency"]) 220 require.Equal(t, "8", response.Attributes["cpu.numcores"]) 221 require.Equal(t, "17600", response.Attributes["cpu.totalcompute"]) 222 require.Equal(t, 17600, response.Resources.CPU) 223 require.Equal(t, int64(17600), response.NodeResources.Cpu.CpuShares) 224 } 225 226 func TestCPUFingerprint_AWS_OverrideCompute(t *testing.T) { 227 endpoint, cleanup := startFakeEC2Metadata(t, awsStubs) 228 defer cleanup() 229 230 f := NewEnvAWSFingerprint(testlog.HCLogger(t)) 231 f.(*EnvAWSFingerprint).endpoint = endpoint 232 233 node := &structs.Node{Attributes: make(map[string]string)} 234 235 request := &FingerprintRequest{Config: &config.Config{ 236 CpuCompute: 99999, 237 }, Node: node} 238 var response FingerprintResponse 239 err := f.Fingerprint(request, &response) 240 require.NoError(t, err) 241 require.True(t, response.Detected) 242 require.Equal(t, "2200", response.Attributes["cpu.frequency"]) 243 require.Equal(t, "8", response.Attributes["cpu.numcores"]) 244 require.Equal(t, "99999", response.Attributes["cpu.totalcompute"]) 245 require.Nil(t, response.Resources) // defaults in cpu fingerprinter 246 require.Zero(t, response.NodeResources.Cpu) // defaults in cpu fingerprinter 247 } 248 249 func TestCPUFingerprint_AWS_InstanceNotFound(t *testing.T) { 250 endpoint, cleanup := startFakeEC2Metadata(t, unknownInstanceType) 251 defer cleanup() 252 253 f := NewEnvAWSFingerprint(testlog.HCLogger(t)) 254 f.(*EnvAWSFingerprint).endpoint = endpoint 255 256 node := &structs.Node{Attributes: make(map[string]string)} 257 258 request := &FingerprintRequest{Config: &config.Config{}, Node: node} 259 var response FingerprintResponse 260 err := f.Fingerprint(request, &response) 261 require.NoError(t, err) 262 require.True(t, response.Detected) 263 require.NotContains(t, response.Attributes, "cpu.modelname") 264 require.NotContains(t, response.Attributes, "cpu.frequency") 265 require.NotContains(t, response.Attributes, "cpu.numcores") 266 require.NotContains(t, response.Attributes, "cpu.totalcompute") 267 require.Nil(t, response.Resources) 268 require.Nil(t, response.NodeResources) 269 } 270 271 /// Utility functions for tests 272 273 func startFakeEC2Metadata(t *testing.T, endpoints []endpoint) (endpoint string, cleanup func()) { 274 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 275 for _, e := range endpoints { 276 if r.RequestURI == e.Uri { 277 w.Header().Set("Content-Type", e.ContentType) 278 fmt.Fprintln(w, e.Body) 279 } 280 } 281 })) 282 283 return ts.URL + "/latest", ts.Close 284 } 285 286 type routes struct { 287 Endpoints []*endpoint `json:"endpoints"` 288 } 289 290 type endpoint struct { 291 Uri string `json:"uri"` 292 ContentType string `json:"content-type"` 293 Body string `json:"body"` 294 } 295 296 // awsStubs mimics normal EC2 instance metadata 297 var awsStubs = []endpoint{ 298 { 299 Uri: "/latest/meta-data/ami-id", 300 ContentType: "text/plain", 301 Body: "ami-1234", 302 }, 303 { 304 Uri: "/latest/meta-data/hostname", 305 ContentType: "text/plain", 306 Body: "ip-10-0-0-207.us-west-2.compute.internal", 307 }, 308 { 309 Uri: "/latest/meta-data/placement/availability-zone", 310 ContentType: "text/plain", 311 Body: "us-west-2a", 312 }, 313 { 314 Uri: "/latest/meta-data/instance-id", 315 ContentType: "text/plain", 316 Body: "i-b3ba3875", 317 }, 318 { 319 Uri: "/latest/meta-data/instance-type", 320 ContentType: "text/plain", 321 Body: "t3a.2xlarge", 322 }, 323 { 324 Uri: "/latest/meta-data/local-hostname", 325 ContentType: "text/plain", 326 Body: "ip-10-0-0-207.us-west-2.compute.internal", 327 }, 328 { 329 Uri: "/latest/meta-data/local-ipv4", 330 ContentType: "text/plain", 331 Body: "10.0.0.207", 332 }, 333 { 334 Uri: "/latest/meta-data/public-hostname", 335 ContentType: "text/plain", 336 Body: "ec2-54-191-117-175.us-west-2.compute.amazonaws.com", 337 }, 338 { 339 Uri: "/latest/meta-data/public-ipv4", 340 ContentType: "text/plain", 341 Body: "54.191.117.175", 342 }, 343 { 344 Uri: "/latest/meta-data/mac", 345 ContentType: "text/plain", 346 Body: "0a:20:d2:42:b3:55", 347 }, 348 } 349 350 var unknownInstanceType = []endpoint{ 351 { 352 Uri: "/latest/meta-data/ami-id", 353 ContentType: "text/plain", 354 Body: "ami-1234", 355 }, 356 { 357 Uri: "/latest/meta-data/hostname", 358 ContentType: "text/plain", 359 Body: "ip-10-0-0-207.us-west-2.compute.internal", 360 }, 361 { 362 Uri: "/latest/meta-data/placement/availability-zone", 363 ContentType: "text/plain", 364 Body: "us-west-2a", 365 }, 366 { 367 Uri: "/latest/meta-data/instance-id", 368 ContentType: "text/plain", 369 Body: "i-b3ba3875", 370 }, 371 { 372 Uri: "/latest/meta-data/instance-type", 373 ContentType: "text/plain", 374 Body: "xyz123.uber", 375 }, 376 } 377 378 // noNetworkAWSStubs mimics an EC2 instance but without local ip address 379 // may happen in environments with odd EC2 Metadata emulation 380 var noNetworkAWSStubs = []endpoint{ 381 { 382 Uri: "/latest/meta-data/ami-id", 383 ContentType: "text/plain", 384 Body: "ami-1234", 385 }, 386 { 387 Uri: "/latest/meta-data/hostname", 388 ContentType: "text/plain", 389 Body: "ip-10-0-0-207.us-west-2.compute.internal", 390 }, 391 { 392 Uri: "/latest/meta-data/placement/availability-zone", 393 ContentType: "text/plain", 394 Body: "us-west-2a", 395 }, 396 { 397 Uri: "/latest/meta-data/instance-id", 398 ContentType: "text/plain", 399 Body: "i-b3ba3875", 400 }, 401 { 402 Uri: "/latest/meta-data/instance-type", 403 ContentType: "text/plain", 404 Body: "m3.2xlarge", 405 }, 406 { 407 Uri: "/latest/meta-data/local-hostname", 408 ContentType: "text/plain", 409 Body: "ip-10-0-0-207.us-west-2.compute.internal", 410 }, 411 { 412 Uri: "/latest/meta-data/local-ipv4", 413 ContentType: "text/plain", 414 Body: "", 415 }, 416 { 417 Uri: "/latest/meta-data/public-hostname", 418 ContentType: "text/plain", 419 Body: "ec2-54-191-117-175.us-west-2.compute.amazonaws.com", 420 }, 421 { 422 Uri: "/latest/meta-data/public-ipv4", 423 ContentType: "text/plain", 424 Body: "54.191.117.175", 425 }, 426 } 427 428 // incompleteAWSImitationsStub mimics environments where some AWS endpoints 429 // return empty, namely Hetzner 430 var incompleteAWSImitationStubs = []endpoint{ 431 { 432 Uri: "/latest/meta-data/hostname", 433 ContentType: "text/plain", 434 Body: "ip-10-0-0-207.us-west-2.compute.internal", 435 }, 436 { 437 Uri: "/latest/meta-data/instance-id", 438 ContentType: "text/plain", 439 Body: "i-b3ba3875", 440 }, 441 { 442 Uri: "/latest/meta-data/local-ipv4", 443 ContentType: "text/plain", 444 Body: "", 445 }, 446 { 447 Uri: "/latest/meta-data/public-ipv4", 448 ContentType: "text/plain", 449 Body: "54.191.117.175", 450 }, 451 }