Skip to content

Commit

Permalink
Merge pull request #484 from /issues/477
Browse files Browse the repository at this point in the history
Fixes #477 - ignore shared VPC resources and dedicated/host EC2 instances
  • Loading branch information
jantman committed Sep 21, 2020
2 parents a6eef26 + 3ac96f1 commit c8bb91b
Show file tree
Hide file tree
Showing 9 changed files with 244 additions and 41 deletions.
8 changes: 8 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
Changelog
=========

Unreleased Changes
------------------

**Important:** This release requires new IAM permissions: ``sts:GetCallerIdentity``

* `Issue #477 <https://github.com/jantman/awslimitchecker/issues/477>`__ - EC2 instances running on Dedicated Hosts (tenancy "host") or single-tenant hardware (tenancy "dedicated") do not count towards On-Demand Instances limits. They were previously being counted towards these limits; they are now excluded from the count.
* `Issue #477 <https://github.com/jantman/awslimitchecker/issues/477>`__ - For all VPC resources that support the ``owner-id`` filter, supply that filter when describing them, set to the current account ID. This will prevent shared resources from other accounts from being counted against the limits.

.. _changelog.8_1_0:

8.1.0 (2020-09-18)
Expand Down
1 change: 1 addition & 0 deletions awslimitchecker/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,7 @@ def get_required_iam_policy(self):
required_actions = [
'servicequotas:ListServiceQuotas',
'support:*',
'sts:GetCallerIdentity',
'trustedadvisor:Describe*',
'trustedadvisor:RefreshCheck'
]
Expand Down
22 changes: 22 additions & 0 deletions awslimitchecker/services/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

import abc
import logging
import boto3
from awslimitchecker.connectable import Connectable

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -90,6 +91,27 @@ def __init__(self, warning_threshold, critical_threshold,
self.limits = {}
self.limits = self.get_limits()
self._have_usage = False
self._current_account_id = None

@property
def current_account_id(self):
"""
Return the numeric Account ID for the account that we are currently
running against.
:return: current account ID
:rtype: str
"""
if self._current_account_id is not None:
return self._current_account_id
kwargs = dict(self._boto3_connection_kwargs)
sts = boto3.client('sts', **kwargs)
logger.info(
"Connected to STS in region %s", sts._client_config.region_name
)
cid = sts.get_caller_identity()
self._current_account_id = cid['Account']
return cid['Account']

@abc.abstractmethod
def find_usage(self):
Expand Down
12 changes: 12 additions & 0 deletions awslimitchecker/services/ec2.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,12 @@ def _instance_usage(self):
logger.info("Spot instance found (%s); skipping from "
"Running On-Demand Instances count", inst.id)
continue
if inst.placement.get('Tenancy', 'default') != 'default':
logger.info(
'Skipping instance %s with Tenancy %s',
inst.id, inst.placement['Tenancy']
)
continue
if inst.state['Name'] in ['stopped', 'terminated']:
logger.debug("Ignoring instance %s in state %s", inst.id,
inst.state['Name'])
Expand Down Expand Up @@ -347,6 +353,12 @@ def _instance_usage_vcpu(self, ris):
logger.info("Spot instance found (%s); skipping from "
"Running On-Demand Instances count", inst.id)
continue
if inst.placement.get('Tenancy', 'default') != 'default':
logger.info(
'Skipping instance %s with Tenancy %s',
inst.id, inst.placement['Tenancy']
)
continue
if inst.state['Name'] in ['stopped', 'terminated']:
logger.debug("Ignoring instance %s in state %s", inst.id,
inst.state['Name'])
Expand Down
33 changes: 22 additions & 11 deletions awslimitchecker/services/vpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@ def find_usage(self):
def _find_usage_vpcs(self):
"""find usage for VPCs"""
# overall number of VPCs
vpcs = self.conn.describe_vpcs()
vpcs = self.conn.describe_vpcs(
Filters=[{'Name': 'owner-id', 'Values': [self.current_account_id]}]
)
self.limits['VPCs']._add_current_usage(
len(vpcs['Vpcs']),
aws_type='AWS::EC2::VPC'
Expand All @@ -92,7 +94,9 @@ def _find_usage_subnets(self):
# subnets per VPC
subnet_to_az = {}
subnets = defaultdict(int)
for subnet in self.conn.describe_subnets()['Subnets']:
for subnet in self.conn.describe_subnets(
Filters=[{'Name': 'owner-id', 'Values': [self.current_account_id]}]
)['Subnets']:
subnets[subnet['VpcId']] += 1
subnet_to_az[subnet['SubnetId']] = subnet['AvailabilityZone']
for vpc_id in subnets:
Expand All @@ -107,7 +111,9 @@ def _find_usage_ACLs(self):
"""find usage for ACLs"""
# Network ACLs per VPC
acls = defaultdict(int)
for acl in self.conn.describe_network_acls()['NetworkAcls']:
for acl in self.conn.describe_network_acls(
Filters=[{'Name': 'owner-id', 'Values': [self.current_account_id]}]
)['NetworkAcls']:
acls[acl['VpcId']] += 1
# Rules per network ACL
self.limits['Rules per network ACL']._add_current_usage(
Expand All @@ -126,7 +132,9 @@ def _find_usage_route_tables(self):
"""find usage for route tables"""
# Route tables per VPC
tables = defaultdict(int)
for table in self.conn.describe_route_tables()['RouteTables']:
for table in self.conn.describe_route_tables(
Filters=[{'Name': 'owner-id', 'Values': [self.current_account_id]}]
)['RouteTables']:
tables[table['VpcId']] += 1
# Entries per route table
routes = [
Expand All @@ -148,7 +156,9 @@ def _find_usage_route_tables(self):
def _find_usage_gateways(self):
"""find usage for Internet Gateways"""
# Internet gateways
gws = self.conn.describe_internet_gateways()
gws = self.conn.describe_internet_gateways(
Filters=[{'Name': 'owner-id', 'Values': [self.current_account_id]}]
)
self.limits['Internet gateways']._add_current_usage(
len(gws['InternetGateways']),
aws_type='AWS::EC2::InternetGateway',
Expand All @@ -166,11 +176,11 @@ def _find_usage_nat_gateways(self, subnet_to_az):
# "This request has been administratively disabled."
try:
gws_per_az = defaultdict(int)
for gw in paginate_dict(self.conn.describe_nat_gateways,
alc_marker_path=['NextToken'],
alc_data_path=['NatGateways'],
alc_marker_param='NextToken'
)['NatGateways']:
for gw in paginate_dict(
self.conn.describe_nat_gateways,
alc_marker_path=['NextToken'], alc_data_path=['NatGateways'],
alc_marker_param='NextToken'
)['NatGateways']:
if gw['State'] not in ['pending', 'available']:
logger.debug(
'Skipping NAT Gateway %s in state: %s',
Expand Down Expand Up @@ -220,7 +230,8 @@ def _find_usage_network_interfaces(self):
self.conn.describe_network_interfaces,
alc_marker_path=['NextToken'],
alc_data_path=['NetworkInterfaces'],
alc_marker_param='NextToken'
alc_marker_param='NextToken',
Filters=[{'Name': 'owner-id', 'Values': [self.current_account_id]}]
)

self.limits['Network interfaces per Region']._add_current_usage(
Expand Down

0 comments on commit c8bb91b

Please sign in to comment.