Skip to content

Commit

Permalink
Merge pull request #379 from nadlerjessie/network-interface-limits
Browse files Browse the repository at this point in the history
Add Network interfaces per Region limit
  • Loading branch information
jantman authored Jan 30, 2019
2 parents d8eccf1 + 19eb663 commit 7a0fad2
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 2 deletions.
51 changes: 51 additions & 0 deletions awslimitchecker/services/vpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@

logger = logging.getLogger(__name__)

DEFAULT_ENI_LIMIT = 350


class _VpcService(_AwsService):

Expand All @@ -71,6 +73,7 @@ def find_usage(self):
self._find_usage_gateways()
self._find_usage_nat_gateways(subnet_to_az)
self._find_usages_vpn_gateways()
self._find_usage_network_interfaces()
self._have_usage = True
logger.debug("Done checking usage.")

Expand Down Expand Up @@ -210,6 +213,20 @@ def _find_usages_vpn_gateways(self):
aws_type='AWS::EC2::VPNGateway'
)

def _find_usage_network_interfaces(self):
"""find usage of network interfaces"""
enis = paginate_dict(
self.conn.describe_network_interfaces,
alc_marker_path=['NextToken'],
alc_data_path=['NetworkInterfaces'],
alc_marker_param='NextToken'
)

self.limits['Network interfaces per Region']._add_current_usage(
len(enis),
aws_type='AWS::EC2::NetworkInterface'
)

def get_limits(self):
"""
Return all known limits for this service, as a dict of their names
Expand Down Expand Up @@ -307,9 +324,42 @@ def get_limits(self):
self.critical_threshold,
limit_type='AWS::EC2::VPNGateway'
)

limits['Network interfaces per Region'] = AwsLimit(
'Network interfaces per Region',
self,
DEFAULT_ENI_LIMIT,
self.warning_threshold,
self.critical_threshold,
limit_type='AWS::EC2::NetworkInterface'
)
self.limits = limits
return limits

def _update_limits_from_api(self):
"""
Query EC2's DescribeAccountAttributes API action and
update the network interface limit, as needed. Updates ``self.limits``.
More info on the network interface limit, from the docs:
'This limit is the greater of either the default limit (350) or your
On-Demand Instance limit multiplied by 5.
The default limit for On-Demand Instances is 20.'
"""
self.connect()
self.connect_resource()
logger.info("Querying EC2 DescribeAccountAttributes for limits")
attribs = self.conn.describe_account_attributes()
for attrib in attribs['AccountAttributes']:
if attrib['AttributeName'] == 'max-instances':
val = attrib['AttributeValues'][0]['AttributeValue']
if int(val) * 5 > DEFAULT_ENI_LIMIT:
limit_name = 'Network interfaces per Region'
self.limits[limit_name]._set_api_limit(int(val))
else:
continue
logger.debug("Done setting limits from API")

def required_iam_permissions(self):
"""
Return a list of IAM Actions required for this Service to function
Expand All @@ -326,4 +376,5 @@ def required_iam_permissions(self):
'ec2:DescribeSubnets',
'ec2:DescribeVpcs',
'ec2:DescribeVpnGateways',
'ec2:DescribeNetworkInterfaces',
]
98 changes: 98 additions & 0 deletions awslimitchecker/tests/services/result_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,104 @@ class VPC(object):
]
}

test_find_usage_network_interfaces = {
'NetworkInterfaces': [
{
'Association': {
'AllocationId': 'allocation-123',
'AssociationId': 'association-123',
'IpOwnerId': '123456789012',
'PublicDnsName': 'string',
'PublicIp': '50.0.0.0'
},
'Attachment': {
'AttachTime': datetime(2015, 1, 1),
'AttachmentId': 'eni-123',
'DeleteOnTermination': True,
'DeviceIndex': 123,
'InstanceId': 'i-123',
'InstanceOwnerId': '123456789012',
'Status': 'attaching'
},
'AvailabilityZone': 'us-east-2a',
'Description': 'NetworkInface',
'Groups': [
{
'GroupName': 'groupName',
'GroupId': 'sg-123'
},
],
'InterfaceType': 'interface',
'Ipv6Addresses': [],
'MacAddress': 'address',
'NetworkInterfaceId': 'eni-123',
'OwnerId': 'string',
'PrivateDnsName': 'string',
'PrivateIpAddress': 'string',
'PrivateIpAddresses': [
{
'Association': {
'AllocationId': 'allocation-123',
'AssociationId': 'association-123',
'IpOwnerId': '123456789012',
'PublicDnsName': 'string',
'PublicIp': '10.0.0.0'
},
'Primary': True,
'PrivateDnsName': 'string',
'PrivateIpAddress': 'string'
},
],
'RequesterId': '123456789012',
'RequesterManaged': True,
'SourceDestCheck': True,
'Status': 'available',
'SubnetId': 'subnet-123',
'TagSet': [
{
'Key': 'tagkey',
'Value': 'tagvalue'
},
],
'VpcId': 'string'
},
]
}

test_update_limits_from_api_high_max_instances = {
'ResponseMetadata': {
'HTTPStatusCode': 200,
'RequestId': '16b85906-ab0d-4134-b8bb-df3e6120c6c7'
},
'AccountAttributes': [
{
'AttributeName': 'max-instances',
'AttributeValues': [
{
'AttributeValue': '400'
}
]
}
]
}

test_update_limits_from_api_low_max_instances = {
'ResponseMetadata': {
'HTTPStatusCode': 200,
'RequestId': '16b85906-ab0d-4134-b8bb-df3e6120c6c7'
},
'AccountAttributes': [
{
'AttributeName': 'max-instances',
'AttributeValues': [
{
'AttributeValue': '50'
}
]
}
]
}


class RDS(object):
test_find_usage_instances = []
Expand Down
77 changes: 75 additions & 2 deletions awslimitchecker/tests/services/test_vpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@

import sys
from awslimitchecker.tests.services import result_fixtures
from awslimitchecker.services.vpc import _VpcService
from awslimitchecker.services.vpc import _VpcService, DEFAULT_ENI_LIMIT

from botocore.exceptions import ClientError

Expand Down Expand Up @@ -81,6 +81,7 @@ def test_get_limits(self):
'Rules per network ACL',
'Route tables per VPC',
'Virtual private gateways',
'Network interfaces per Region',
])
for name, limit in res.items():
assert limit.service == cls
Expand Down Expand Up @@ -111,7 +112,8 @@ def test_find_usage(self):
_find_usage_route_tables=DEFAULT,
_find_usage_gateways=DEFAULT,
_find_usage_nat_gateways=DEFAULT,
_find_usages_vpn_gateways=DEFAULT
_find_usages_vpn_gateways=DEFAULT,
_find_usage_network_interfaces=DEFAULT,
) as mocks:
mocks['_find_usage_subnets'].return_value = sn
cls = _VpcService(21, 43)
Expand All @@ -128,6 +130,7 @@ def test_find_usage(self):
'_find_usage_route_tables',
'_find_usage_gateways',
'_find_usages_vpn_gateways',
'_find_usage_network_interfaces',
]:
assert mocks[x].mock_calls == [call()]
assert mocks['_find_usage_nat_gateways'].mock_calls == [call(sn)]
Expand Down Expand Up @@ -339,6 +342,75 @@ def test_find_usages_vpn_gateways(self):
]),
]

def test_find_usage_network_interfaces(self):
response = result_fixtures.VPC.test_find_usage_network_interfaces

mock_conn = Mock()
mock_conn.describe_network_interfaces.return_value = response

cls = _VpcService(21, 43)
cls.conn = mock_conn

cls._find_usage_network_interfaces()

assert len(cls.limits['Network interfaces per Region']
.get_current_usage()) == 1
assert cls.limits['Network interfaces per Region'].get_current_usage()[
0].get_value() == 1
assert mock_conn.mock_calls == [
call.describe_network_interfaces(),
]

def test_update_limits_from_api_high_max_instances(self):
fixtures = result_fixtures.VPC()
response = fixtures.test_update_limits_from_api_high_max_instances

mock_conn = Mock()
mock_client_conn = Mock()
mock_client_conn.describe_account_attributes.return_value = response

cls = _VpcService(21, 43)
cls.resource_conn = mock_conn
cls.conn = mock_client_conn
with patch('awslimitchecker.services.vpc.logger') as mock_logger:
cls._update_limits_from_api()
assert mock_conn.mock_calls == []
assert mock_client_conn.mock_calls == [
call.describe_account_attributes()
]
assert mock_logger.mock_calls == [
call.info("Querying EC2 DescribeAccountAttributes for limits"),
call.debug('Done setting limits from API')
]
assert cls.limits['Network interfaces per Region'].api_limit == 400
assert cls.limits['Network interfaces per Region'].get_limit() == 400

def test_update_limits_from_api_low_max_instances(self):
fixtures = result_fixtures.VPC()
response = fixtures.test_update_limits_from_api_low_max_instances

mock_conn = Mock()
mock_client_conn = Mock()
mock_client_conn.describe_account_attributes.return_value = response

cls = _VpcService(21, 43)
cls.resource_conn = mock_conn
cls.conn = mock_client_conn
with patch('awslimitchecker.services.vpc.logger') as mock_logger:
cls._update_limits_from_api()
assert mock_conn.mock_calls == []
assert mock_client_conn.mock_calls == [
call.describe_account_attributes()
]
assert mock_logger.mock_calls == [
call.info("Querying EC2 DescribeAccountAttributes for limits"),
call.debug('Done setting limits from API')
]

limit_name = 'Network interfaces per Region'
assert cls.limits[limit_name].api_limit is None
assert cls.limits[limit_name].get_limit() == DEFAULT_ENI_LIMIT

def test_required_iam_permissions(self):
cls = _VpcService(21, 43)
assert cls.required_iam_permissions() == [
Expand All @@ -348,4 +420,5 @@ def test_required_iam_permissions(self):
'ec2:DescribeSubnets',
'ec2:DescribeVpcs',
'ec2:DescribeVpnGateways',
'ec2:DescribeNetworkInterfaces',
]
Empty file added default)
Empty file.

0 comments on commit 7a0fad2

Please sign in to comment.