github.com/newrelic/go-agent@v3.26.0+incompatible/internal/utilization/utilization_test.go (about)

     1  // Copyright 2020 New Relic Corporation. All rights reserved.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package utilization
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/json"
     9  	"errors"
    10  	"net/http"
    11  	"testing"
    12  
    13  	"github.com/newrelic/go-agent/internal/crossagent"
    14  	"github.com/newrelic/go-agent/internal/logger"
    15  )
    16  
    17  func TestJSONMarshalling(t *testing.T) {
    18  	ramInitializer := new(uint64)
    19  	*ramInitializer = 1024
    20  	actualProcessors := 4
    21  	configProcessors := 16
    22  	u := Data{
    23  		MetadataVersion:   metadataVersion,
    24  		LogicalProcessors: &actualProcessors,
    25  		RAMMiB:            ramInitializer,
    26  		Hostname:          "localhost",
    27  		Vendors: &vendors{
    28  			AWS: &aws{
    29  				InstanceID:       "8BADFOOD",
    30  				InstanceType:     "t2.micro",
    31  				AvailabilityZone: "us-west-1",
    32  			},
    33  			Docker:     &docker{ID: "47cbd16b77c50cbf71401"},
    34  			Kubernetes: &kubernetes{Host: "10.96.0.1"},
    35  		},
    36  		Config: &override{
    37  			LogicalProcessors: &configProcessors,
    38  		},
    39  	}
    40  
    41  	expect := `{
    42  	"metadata_version": 5,
    43  	"logical_processors": 4,
    44  	"total_ram_mib": 1024,
    45  	"hostname": "localhost",
    46  	"config": {
    47  		"logical_processors": 16
    48  	},
    49  	"vendors": {
    50  		"aws": {
    51  			"instanceId": "8BADFOOD",
    52  			"instanceType": "t2.micro",
    53  			"availabilityZone": "us-west-1"
    54  		},
    55  		"docker": {
    56  			"id": "47cbd16b77c50cbf71401"
    57  		},
    58  		"kubernetes": {
    59  			"kubernetes_service_host": "10.96.0.1"
    60  		}
    61  	}
    62  }`
    63  
    64  	j, err := json.MarshalIndent(u, "", "\t")
    65  	if err != nil {
    66  		t.Error(err)
    67  	}
    68  	if string(j) != expect {
    69  		t.Errorf("strings don't match; \nexpected: %s\n  actual: %s\n", expect, string(j))
    70  	}
    71  
    72  	// Test that we marshal not-present values to nil.
    73  	u.RAMMiB = nil
    74  	u.Hostname = ""
    75  	u.Config = nil
    76  	expect = `{
    77  	"metadata_version": 5,
    78  	"logical_processors": 4,
    79  	"total_ram_mib": null,
    80  	"hostname": "",
    81  	"vendors": {
    82  		"aws": {
    83  			"instanceId": "8BADFOOD",
    84  			"instanceType": "t2.micro",
    85  			"availabilityZone": "us-west-1"
    86  		},
    87  		"docker": {
    88  			"id": "47cbd16b77c50cbf71401"
    89  		},
    90  		"kubernetes": {
    91  			"kubernetes_service_host": "10.96.0.1"
    92  		}
    93  	}
    94  }`
    95  
    96  	j, err = json.MarshalIndent(u, "", "\t")
    97  	if err != nil {
    98  		t.Error(err)
    99  	}
   100  	if string(j) != expect {
   101  		t.Errorf("strings don't match; \nexpected: %s\n  actual: %s\n", expect, string(j))
   102  	}
   103  
   104  }
   105  
   106  type errorRoundTripper struct{ error }
   107  
   108  func (e errorRoundTripper) RoundTrip(*http.Request) (*http.Response, error) { return nil, e }
   109  
   110  // Smoke test the Gather method.
   111  func TestUtilizationHash(t *testing.T) {
   112  	config := Config{
   113  		DetectAWS:    true,
   114  		DetectAzure:  true,
   115  		DetectDocker: true,
   116  	}
   117  	client := &http.Client{
   118  		Transport: errorRoundTripper{errors.New("timed out")},
   119  	}
   120  	data := gatherWithClient(config, logger.ShimLogger{}, client)
   121  	if data.MetadataVersion == 0 ||
   122  		nil == data.LogicalProcessors ||
   123  		0 == *data.LogicalProcessors ||
   124  		data.RAMMiB == nil ||
   125  		*data.RAMMiB == 0 ||
   126  		data.Hostname == "" {
   127  		t.Errorf("utilization data unexpected fields: %+v", data)
   128  	}
   129  }
   130  
   131  func TestOverrideFromConfig(t *testing.T) {
   132  	testcases := []struct {
   133  		config Config
   134  		expect string
   135  	}{
   136  		{Config{}, `null`},
   137  		{Config{LogicalProcessors: 16}, `{"logical_processors":16}`},
   138  		{Config{TotalRAMMIB: 1024}, `{"total_ram_mib":1024}`},
   139  		{Config{BillingHostname: "localhost"}, `{"hostname":"localhost"}`},
   140  		{Config{
   141  			LogicalProcessors: 16,
   142  			TotalRAMMIB:       1024,
   143  			BillingHostname:   "localhost",
   144  		}, `{"logical_processors":16,"total_ram_mib":1024,"hostname":"localhost"}`},
   145  	}
   146  
   147  	for _, tc := range testcases {
   148  		ov := overrideFromConfig(tc.config)
   149  		js, err := json.Marshal(ov)
   150  		if nil != err {
   151  			t.Error(tc.expect, err)
   152  			continue
   153  		}
   154  		if string(js) != tc.expect {
   155  			t.Error(tc.expect, string(js))
   156  		}
   157  	}
   158  }
   159  
   160  type utilizationCrossAgentTestcase struct {
   161  	Name              string          `json:"testname"`
   162  	RAMMIB            *uint64         `json:"input_total_ram_mib"`
   163  	LogicalProcessors *int            `json:"input_logical_processors"`
   164  	Hostname          string          `json:"input_hostname"`
   165  	FullHostname      string          `json:"input_full_hostname"`
   166  	Addresses         []string        `json:"input_ip_address"`
   167  	BootID            string          `json:"input_boot_id"`
   168  	AWSID             string          `json:"input_aws_id"`
   169  	AWSType           string          `json:"input_aws_type"`
   170  	AWSZone           string          `json:"input_aws_zone"`
   171  	AzureLocation     string          `json:"input_azure_location"`
   172  	AzureName         string          `json:"input_azure_name"`
   173  	AzureID           string          `json:"input_azure_id"`
   174  	AzureSize         string          `json:"input_azure_size"`
   175  	GCPID             json.Number     `json:"input_gcp_id"`
   176  	GCPType           string          `json:"input_gcp_type"`
   177  	GCPName           string          `json:"input_gcp_name"`
   178  	GCPZone           string          `json:"input_gcp_zone"`
   179  	PCFGUID           string          `json:"input_pcf_guid"`
   180  	PCFIP             string          `json:"input_pcf_ip"`
   181  	PCFMemLimit       string          `json:"input_pcf_mem_limit"`
   182  	ExpectedOutput    json.RawMessage `json:"expected_output_json"`
   183  	Config            struct {
   184  		LogicalProcessors json.RawMessage `json:"NEW_RELIC_UTILIZATION_LOGICAL_PROCESSORS"`
   185  		RAWMMIB           json.RawMessage `json:"NEW_RELIC_UTILIZATION_TOTAL_RAM_MIB"`
   186  		Hostname          string          `json:"NEW_RELIC_UTILIZATION_BILLING_HOSTNAME"`
   187  		KubernetesHost    string          `json:"KUBERNETES_SERVICE_HOST"`
   188  	} `json:"input_environment_variables"`
   189  }
   190  
   191  func crossAgentVendors(tc utilizationCrossAgentTestcase) *vendors {
   192  	v := &vendors{}
   193  
   194  	if tc.AWSID != "" && tc.AWSType != "" && tc.AWSZone != "" {
   195  		v.AWS = &aws{
   196  			InstanceID:       tc.AWSID,
   197  			InstanceType:     tc.AWSType,
   198  			AvailabilityZone: tc.AWSZone,
   199  		}
   200  		v.AWS.validate()
   201  	}
   202  
   203  	if tc.AzureLocation != "" && tc.AzureName != "" && tc.AzureID != "" && tc.AzureSize != "" {
   204  		v.Azure = &azure{
   205  			Location: tc.AzureLocation,
   206  			Name:     tc.AzureName,
   207  			VMID:     tc.AzureID,
   208  			VMSize:   tc.AzureSize,
   209  		}
   210  		v.Azure.validate()
   211  	}
   212  
   213  	if tc.GCPID.String() != "" && tc.GCPType != "" && tc.GCPName != "" && tc.GCPZone != "" {
   214  		v.GCP = &gcp{
   215  			ID:          numericString(tc.GCPID.String()),
   216  			MachineType: tc.GCPType,
   217  			Name:        tc.GCPName,
   218  			Zone:        tc.GCPZone,
   219  		}
   220  		v.GCP.validate()
   221  	}
   222  
   223  	if tc.PCFIP != "" && tc.PCFGUID != "" && tc.PCFMemLimit != "" {
   224  		v.PCF = &pcf{
   225  			InstanceGUID: tc.PCFGUID,
   226  			InstanceIP:   tc.PCFIP,
   227  			MemoryLimit:  tc.PCFMemLimit,
   228  		}
   229  		v.PCF.validate()
   230  	}
   231  
   232  	gatherKubernetes(v, func(key string) string {
   233  		if key == "KUBERNETES_SERVICE_HOST" {
   234  			return tc.Config.KubernetesHost
   235  		}
   236  		return ""
   237  	})
   238  
   239  	if v.isEmpty() {
   240  		return nil
   241  	}
   242  	return v
   243  }
   244  
   245  func compactJSON(js []byte) []byte {
   246  	buf := new(bytes.Buffer)
   247  	if err := json.Compact(buf, js); err != nil {
   248  		return nil
   249  	}
   250  	return buf.Bytes()
   251  }
   252  
   253  func runUtilizationCrossAgentTestcase(t *testing.T, tc utilizationCrossAgentTestcase) {
   254  	var ConfigRAWMMIB int
   255  	if nil != tc.Config.RAWMMIB {
   256  		json.Unmarshal(tc.Config.RAWMMIB, &ConfigRAWMMIB)
   257  	}
   258  	var ConfigLogicalProcessors int
   259  	if nil != tc.Config.LogicalProcessors {
   260  		json.Unmarshal(tc.Config.LogicalProcessors, &ConfigLogicalProcessors)
   261  	}
   262  
   263  	cfg := Config{
   264  		LogicalProcessors: ConfigLogicalProcessors,
   265  		TotalRAMMIB:       ConfigRAWMMIB,
   266  		BillingHostname:   tc.Config.Hostname,
   267  	}
   268  
   269  	data := &Data{
   270  		MetadataVersion:   metadataVersion,
   271  		LogicalProcessors: tc.LogicalProcessors,
   272  		RAMMiB:            tc.RAMMIB,
   273  		Hostname:          tc.Hostname,
   274  		BootID:            tc.BootID,
   275  		Vendors:           crossAgentVendors(tc),
   276  		Config:            overrideFromConfig(cfg),
   277  		FullHostname:      tc.FullHostname,
   278  		Addresses:         tc.Addresses,
   279  	}
   280  
   281  	js, err := json.Marshal(data)
   282  	if nil != err {
   283  		t.Error(tc.Name, err)
   284  	}
   285  
   286  	expect := string(compactJSON(tc.ExpectedOutput))
   287  	if string(js) != expect {
   288  		t.Error(tc.Name, string(js), expect)
   289  	}
   290  }
   291  
   292  func TestUtilizationCrossAgent(t *testing.T) {
   293  	var tcs []utilizationCrossAgentTestcase
   294  
   295  	input, err := crossagent.ReadFile(`utilization/utilization_json.json`)
   296  	if nil != err {
   297  		t.Fatal(err)
   298  	}
   299  
   300  	err = json.Unmarshal(input, &tcs)
   301  	if nil != err {
   302  		t.Fatal(err)
   303  	}
   304  	for _, tc := range tcs {
   305  		runUtilizationCrossAgentTestcase(t, tc)
   306  	}
   307  }
   308  
   309  func TestVendorsIsEmpty(t *testing.T) {
   310  	v := &vendors{}
   311  
   312  	if !v.isEmpty() {
   313  		t.Fatal("default vendors does not register as empty")
   314  	}
   315  
   316  	v.AWS = &aws{}
   317  	v.Azure = &azure{}
   318  	v.PCF = &pcf{}
   319  	v.GCP = &gcp{}
   320  	if v.isEmpty() {
   321  		t.Fatal("non-empty vendors registers as empty")
   322  	}
   323  
   324  	var nilVendors *vendors
   325  	if !nilVendors.isEmpty() {
   326  		t.Fatal("nil vendors should be empty")
   327  	}
   328  }