Skip to content

Commit 8ab5365

Browse files
committed
AWS utilization caching mock tests
1 parent 7ed050c commit 8ab5365

3 files changed

Lines changed: 209 additions & 1 deletion

File tree

newrelic/common/utilization.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,10 @@ def fetch(cls):
197197
# Cache this for forced agent restarts within the same
198198
# environment if return value is valid.
199199
try:
200-
availabilityZone, instanceId, instanceType = resp[1].decode("utf-8").strip()
200+
response_dict = json.loads(resp[1].decode("utf-8"))
201+
availabilityZone = response_dict.get("availabilityZone", None)
202+
instanceId = response_dict.get("instanceId", None)
203+
instanceType = response_dict.get("instanceType", None)
201204
if all((availabilityZone, instanceId, instanceType)):
202205
# Cache the utilization data for reuse
203206
cls._utilization_data = resp[1]

tests/agent_unittests/aws.json

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
[
2+
{
3+
"testname": "auth token fails, no cached data",
4+
"auth_token_cls": "no_token",
5+
"uri": {
6+
"http://169.254.169.254/latest/dynamic/instance-identity/document": {
7+
"response": {
8+
"instanceId": null,
9+
"instanceType": null,
10+
"availabilityZone": null
11+
},
12+
"timeout": false
13+
}
14+
},
15+
"expected_vendors_hash": null,
16+
"expected_metrics": {
17+
"Supportability/utilization/aws/error": {
18+
"call_count": 0
19+
}
20+
}
21+
},
22+
{
23+
"testname": "auth token succeeds, but utilization data is not valid, no cached data",
24+
"auth_token_cls": "fake_token",
25+
"uri": {
26+
"http://169.254.169.254/latest/dynamic/instance-identity/document": {
27+
"response": {
28+
"instanceId": "i-test.19characters",
29+
"instanceType": null,
30+
"availabilityZone": "us-west-2b"
31+
},
32+
"timeout": false
33+
}
34+
},
35+
"expected_vendors_hash": null,
36+
"expected_metrics": {
37+
"Supportability/utilization/aws/error": {
38+
"call_count": 1
39+
}
40+
}
41+
},
42+
{
43+
"testname": "auth token succeeds, utilization data is valid, data is cached (part 1)",
44+
"auth_token_cls": "fake_token",
45+
"uri": {
46+
"http://169.254.169.254/latest/dynamic/instance-identity/document": {
47+
"response": {
48+
"instanceId": "i-test.19characters",
49+
"instanceType": "test.type",
50+
"availabilityZone": "us-west-2b"
51+
},
52+
"timeout": false
53+
}
54+
},
55+
"expected_vendors_hash": {
56+
"aws": {
57+
"instanceId": "i-test.19characters",
58+
"instanceType": "test.type",
59+
"availabilityZone": "us-west-2b"
60+
}
61+
}
62+
},
63+
{
64+
"testname": "auth token fails, but cached data exists, return cached data",
65+
"auth_token_cls": "no_token",
66+
"uri": {
67+
"http://169.254.169.254/latest/dynamic/instance-identity/document": {
68+
"response": {
69+
"instanceId": null,
70+
"instanceType": null,
71+
"availabilityZone": null
72+
},
73+
"timeout": false
74+
}
75+
},
76+
"expected_vendors_hash": {
77+
"aws": {
78+
"instanceId": "i-test.19characters",
79+
"instanceType": "test.type",
80+
"availabilityZone": "us-west-2b"
81+
}
82+
}
83+
},
84+
{
85+
"testname": "auth token succeeds, utilization data is valid, data is cached (part 2)",
86+
"auth_token_cls": "fake_token",
87+
"uri": {
88+
"http://169.254.169.254/latest/dynamic/instance-identity/document": {
89+
"response": {
90+
"instanceId": "i-test.19characters",
91+
"instanceType": "test.type",
92+
"availabilityZone": "us-east-2b"
93+
},
94+
"timeout": false
95+
}
96+
},
97+
"expected_vendors_hash": {
98+
"aws": {
99+
"instanceId": "i-test.19characters",
100+
"instanceType": "test.type",
101+
"availabilityZone": "us-east-2b"
102+
}
103+
}
104+
}
105+
]
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# Copyright 2010 New Relic, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import json
16+
import os
17+
18+
import pytest
19+
from testing_support.mock_http_client import create_client_cls
20+
from testing_support.validators.validate_internal_metrics import validate_internal_metrics
21+
22+
from newrelic.common.utilization import AWSUtilization
23+
24+
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
25+
FIXTURE = os.path.normpath(os.path.join(CURRENT_DIR, "aws.json"))
26+
27+
_parameters_list = ["testname", "auth_token_cls", "uri", "expected_vendors_hash", "expected_metrics"]
28+
29+
_parameters = ",".join(_parameters_list)
30+
31+
@classmethod
32+
def fake_token(cls):
33+
return "FakeToken"
34+
35+
@classmethod
36+
def no_token(cls):
37+
return None
38+
39+
def _load_tests():
40+
with open(FIXTURE) as fh:
41+
js = fh.read()
42+
return json.loads(js)
43+
44+
45+
def _parametrize_test(test):
46+
return tuple([test.get(f, None) for f in _parameters_list])
47+
48+
# Load the tests from the JSON fixture
49+
_aws_tests = [_parametrize_test(t) for t in _load_tests()]
50+
51+
52+
# Order of tests:
53+
# 1. auth token fails, no cached data
54+
# 2. Auth token succeeds, but utilization data is invalid, no cached data
55+
# 3. Auth token succeeds, utilization data is valid, data is cached (check both cache and returned value)
56+
# 4. Auth token fails, but cached data is valid, return cached data
57+
# 5. Auth token succeeds, utilization data is valid, data is cached (check both cache and returned value)
58+
59+
60+
@pytest.mark.parametrize(_parameters, _aws_tests)
61+
def test_aws_utilization_caching(monkeypatch, testname, auth_token_cls, uri, expected_vendors_hash, expected_metrics):
62+
63+
def _get_mock_return_value(api_result):
64+
if api_result["timeout"]:
65+
return 0, None
66+
else:
67+
body = json.dumps(api_result["response"])
68+
return 200, body.encode("utf-8")
69+
70+
url, api_result = uri.popitem()
71+
status, data = _get_mock_return_value(api_result)
72+
73+
client_cls = create_client_cls(status, data, url)
74+
75+
monkeypatch.setattr(AWSUtilization, "CLIENT_CLS", client_cls)
76+
monkeypatch.setattr(AWSUtilization, "fetchAuthToken", fake_token if auth_token_cls == "fake_token" else no_token)
77+
78+
metrics = []
79+
if expected_metrics:
80+
metrics = [(k, v.get("call_count")) for k, v in expected_metrics.items()]
81+
82+
# Define function that actually runs the test
83+
84+
@validate_internal_metrics(metrics=metrics)
85+
def _test_aws_data():
86+
data = AWSUtilization.detect()
87+
88+
if data:
89+
aws_vendor_hash = {"aws": data}
90+
else:
91+
aws_vendor_hash = None
92+
93+
assert aws_vendor_hash == expected_vendors_hash
94+
if expected_vendors_hash is not None:
95+
# Check that the cached data is set to the most recent valid data
96+
assert json.loads(AWSUtilization._utilization_data.decode("utf-8")) == data
97+
98+
_test_aws_data()
99+
100+
assert not client_cls.FAIL

0 commit comments

Comments
 (0)