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  }