diff --git a/cloud_provider/aws/aws_bid_advisor.py b/cloud_provider/aws/aws_bid_advisor.py index 171455f..3d10192 100644 --- a/cloud_provider/aws/aws_bid_advisor.py +++ b/cloud_provider/aws/aws_bid_advisor.py @@ -169,8 +169,7 @@ def __init__(self, bid_advisor): self.bid_advisor = bid_advisor @retry(wait_exponential_multiplier=1000, stop_max_attempt_number=3) - def get_spot_price_info(self): - """ Issues AWS apis to get spot instance prices. """ + def ec2_get_spot_price_history(self): ec2 = self.bid_advisor.ec2 hour_ago = datetime.now() - timedelta(hours=1) spot_price_info = [] @@ -186,9 +185,13 @@ def get_spot_price_info(self): if response['NextToken']: next_token = response['NextToken'] else: - break + return spot_price_info except Exception as ex: raise Exception("Failed to get spot instance pricing info: " + str(ex)) + + def get_spot_price_info(self): + """ Issues AWS apis to get spot instance prices. """ + spot_price_info = self.ec2_get_spot_price_history() with self.bid_advisor.lock: self.bid_advisor.spot_price_list = spot_price_info logger.info("Spot instance pricing info updated") diff --git a/cloud_provider/aws/aws_bid_advisor_test.py b/cloud_provider/aws/aws_bid_advisor_test.py index e04cf95..f720a8c 100644 --- a/cloud_provider/aws/aws_bid_advisor_test.py +++ b/cloud_provider/aws/aws_bid_advisor_test.py @@ -1,17 +1,21 @@ """The file has unit tests for the AWSBidAdvisor.""" import unittest - +from mock import patch, MagicMock +import datetime +from dateutil.tz import tzutc from cloud_provider.aws.aws_bid_advisor import AWSBidAdvisor REFRESH_INTERVAL = 10 REGION = 'us-west-2' +MOCK_SPOT_PRICE={'NextToken': '', 'SpotPriceHistory': [{'AvailabilityZone': 'us-west-2b', 'InstanceType': 'm5.4xlarge', 'ProductDescription': 'Linux/UNIX', 'SpotPrice': '0.300000', 'Timestamp': datetime.datetime(2019, 7, 13, 20, 30, 22, tzinfo=tzutc())}, {'AvailabilityZone': 'us-west-2c', 'InstanceType': 'm5.4xlarge', 'ProductDescription': 'Linux/UNIX', 'SpotPrice': '0.291400', 'Timestamp': datetime.datetime(2019, 7, 13, 20, 13, 34, tzinfo=tzutc())}, {'AvailabilityZone': 'us-west-2a', 'InstanceType': 'm5.4xlarge', 'ProductDescription': 'Linux/UNIX', 'SpotPrice': '0.320100', 'Timestamp': datetime.datetime(2019, 7, 13, 18, 33, 30, tzinfo=tzutc())}, {'AvailabilityZone': 'us-west-2c', 'InstanceType': 'm3.medium', 'ProductDescription': 'Linux/UNIX', 'SpotPrice': '0.006700', 'Timestamp': datetime.datetime(2019, 7, 13, 17, 7, 9, tzinfo=tzutc())}, {'AvailabilityZone': 'us-west-2b', 'InstanceType': 'm3.medium', 'ProductDescription': 'Linux/UNIX', 'SpotPrice': '0.006700', 'Timestamp': datetime.datetime(2019, 7, 13, 17, 7, 9, tzinfo=tzutc())}, {'AvailabilityZone': 'us-west-2a', 'InstanceType': 'm3.medium', 'ProductDescription': 'Linux/UNIX', 'SpotPrice': '0.006700', 'Timestamp': datetime.datetime(2019, 7, 13, 17, 7, 9, tzinfo=tzutc())}, {'AvailabilityZone': 'us-west-2b', 'InstanceType': 'm5.4xlarge', 'ProductDescription': 'Linux/UNIX', 'SpotPrice': '0.300400', 'Timestamp': datetime.datetime(2019, 7, 13, 15, 46, 1, tzinfo=tzutc())}, {'AvailabilityZone': 'us-west-2c', 'InstanceType': 'm5.4xlarge', 'ProductDescription': 'Linux/UNIX', 'SpotPrice': '0.291500', 'Timestamp': datetime.datetime(2019, 7, 13, 14, 47, 14, tzinfo=tzutc())}, {'AvailabilityZone': 'us-west-2a', 'InstanceType': 'm5.4xlarge', 'ProductDescription': 'Linux/UNIX', 'SpotPrice': '0.321600', 'Timestamp': datetime.datetime(2019, 7, 13, 13, 40, 47, tzinfo=tzutc())}, {'AvailabilityZone': 'us-west-2d', 'InstanceType': 'm5.4xlarge', 'ProductDescription': 'Linux/UNIX', 'SpotPrice': '0.270400', 'Timestamp': datetime.datetime(2019, 7, 13, 6, 23, 5, tzinfo=tzutc())}, {'AvailabilityZone': 'us-west-2c', 'InstanceType': 'm3.medium', 'ProductDescription': 'Linux/UNIX', 'SpotPrice': '0.006700', 'Timestamp': datetime.datetime(2019, 7, 12, 17, 7, 5, tzinfo=tzutc())}, {'AvailabilityZone': 'us-west-2b', 'InstanceType': 'm3.medium', 'ProductDescription': 'Linux/UNIX', 'SpotPrice': '0.006700', 'Timestamp': datetime.datetime(2019, 7, 12, 17, 7, 5, tzinfo=tzutc())}, {'AvailabilityZone': 'us-west-2a', 'InstanceType': 'm3.medium', 'ProductDescription': 'Linux/UNIX', 'SpotPrice': '0.006700', 'Timestamp': datetime.datetime(2019, 7, 12, 17, 7, 5, tzinfo=tzutc())}], 'ResponseMetadata': {'RequestId': 'f428bcba-016f-476f-b9ed-755f71af2d36', 'HTTPStatusCode': 200, 'HTTPHeaders': {'content-type': 'text/xml;charset=UTF-8', 'content-length': '4341', 'vary': 'accept-encoding', 'date': 'Sun, 14 Jul 2019 00:45:52 GMT', 'server': 'AmazonEC2'}, 'RetryAttempts': 0}} class AWSBidAdvisorTest(unittest.TestCase): """ Tests for AWSBidAdvisor. """ + @patch.object(AWSBidAdvisor.SpotInstancePriceUpdater, 'ec2_get_spot_price_history', MagicMock(return_value=MOCK_SPOT_PRICE)) def test_ba_lifecycle(self): """ Tests that the AWSBidVisor starts threads and stops them correctly. @@ -33,6 +37,7 @@ def test_ba_on_demand_pricing(self): updater.get_on_demand_pricing() assert len(bidadv.on_demand_price_dict) > 0 + @patch.object(AWSBidAdvisor.SpotInstancePriceUpdater, 'ec2_get_spot_price_history', MagicMock(return_value=MOCK_SPOT_PRICE)) def test_ba_spot_pricing(self): """ Tests that the AWSBidVisor correctly gets the spot instance pricing. @@ -43,6 +48,7 @@ def test_ba_spot_pricing(self): updater.get_spot_price_info() assert len(bidadv.spot_price_list) > 0 + @patch.object(AWSBidAdvisor.SpotInstancePriceUpdater, 'ec2_get_spot_price_history', MagicMock(return_value=MOCK_SPOT_PRICE)) def test_ba_price_update(self): """ Tests that the AXBidVisor actually updates the pricing info. @@ -107,6 +113,7 @@ def test_ba_get_bid_no_data(self): bid_info = bidadv.get_new_bid(['us-west-2a'], 'm3.large') assert bid_info["type"] == "on-demand" + @patch.object(AWSBidAdvisor.SpotInstancePriceUpdater, 'ec2_get_spot_price_history', MagicMock(return_value=MOCK_SPOT_PRICE)) def test_ba_get_current_price(self): """ Tests that the BidAdvisor returns the most recent price information.