Skip to content

Commit

Permalink
Merge pull request #448 from /issues/413
Browse files Browse the repository at this point in the history
Fixes #413 - implement Service Quotas
  • Loading branch information
jantman committed Nov 3, 2019
2 parents b743823 + d307443 commit 5ef6205
Show file tree
Hide file tree
Showing 61 changed files with 2,109 additions and 937 deletions.
13 changes: 13 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Changelog

**Important:** Python versions prior to 3.5, including 2.7, are now pending deprecation. As of January 1, 2020, they will no longer be tested or supported, and awslimitchecker **will require Python 3.5 or newer**. Please see below for details. Also take note that running via the official Docker image is a way to ensure the best version of Python is always used.

**Important:** This release requires a new IAM permission, ``servicequotas:ListServiceQuotas``.

* `Issue #400 <https://github.com/jantman/awslimitchecker/issues/400>`__ / `PR #434 <https://github.com/jantman/awslimitchecker/pull/434>`__ - Support GovCloud region and alternate partitions in STS assumed roles and Trusted Advisor. Thanks to `@djkiourtsis <https://github.com/djkiourtsis>`__.
* `Issue #432 <https://github.com/jantman/awslimitchecker/issues/432>`__ - Update EC2 limit handling for new vCPU-based limits in regions other than ``cn-*`` and ``us-gov-*`` (which still use old per-instance-type limits). See :ref:`section below <changelog.8_0_0_vcpu_limits>` for further information. For regions other than ``cn-*`` and ``us-gov-*``, **this will remove** all 175 ``Running On-Demand <type> instances`` and the ``Running On-Demand EC2 instances`` limit, and replace them with:

Expand All @@ -29,6 +31,8 @@ Changelog
* `Issue #436 <https://github.com/jantman/awslimitchecker/issues/436>`_ - Begin testing under Python 3.8 and base our Docker image on ``python:3.8-alpine``.
* `Issue #435 <https://github.com/jantman/awslimitchecker/issues/435>`_ - Allow configuring the botocore maximum retries for Throttling / RateExceeded errors on a per-AWS-API basis via environment variables. See the relevant sections of the :ref:`CLI Usage <cli_usage.throttling>` or :ref:`Python Usage <python_usage.throttling>` documentation for further details.
* `Issue #431 <https://github.com/jantman/awslimitchecker/issues/431>`_ - Fix a **major under-calculation** of usage for the EC2 ``Rules per VPC security group`` limit. We were previously calculating the number of "Rules" (from port / to port / protocol combinations) in a Security Group, but the limit is actually based on the number of permissions granted. See `this comment <https://github.com/jantman/awslimitchecker/issues/431#issuecomment-548599785>`_ on the issue for further details.
* `Issue #413 <https://github.com/jantman/awslimitchecker/issues/431>`_ - Add support for retrieving limits from the new `Service Quotas service <https://docs.aws.amazon.com/servicequotas/latest/userguide/intro.html>`__ where available. See the :ref:`changelog.8_0_0_service_quotas` section below for more information.
* Bump boto3 minimum version requirement from 1.4.6 to 1.9.175 and botocore minimum version requirement from 1.6.0 to 1.12.175, in order to support Service Quotas.

.. _changelog.8_0_0_vcpu_limits:

Expand All @@ -52,6 +56,15 @@ I have **not** yet implemented Trusted Advisor (TA) support for these new limits

Calculation of current usage for the vCPU limits is based on the `EC2 Optimizing CPU Options documentation <https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-optimize-cpu.html>`__ which specifies, "The number of vCPUs for the instance is the number of CPU cores multiplied by the threads per core." The ``CpuOptions`` field of the EC2 ``DescribeInstances`` API specifies the core and thread count for each running instance.

.. _changelog.8_0_0_service_quotas:

Service Quotas
++++++++++++++

AWS' new `Service Quotas service <https://docs.aws.amazon.com/servicequotas/latest/userguide/intro.html>`__ provides a unified interface to retrieve current limits from many AWS services. These limit values are second only to the services' own APIs (for the services that provide limit information via API), and are much more current and complete than the information provided by Trusted Advisor. The introduction of Service Quotas should greatly reduce the number of limits that need to be retrieved from Trusted Advisor or specified manually.

If you currently have any Limit Overrides set (via either the :ref:`CLI <cli_usage.limit_overrides>` or :ref:`Python API <python_usage.limit_overrides>`), please verify on the :ref:`limits` page whether Service Quotas data is now available for those limits. You should be able to remove manual overrides for the limits that now retrieve data from Service Quotas.

.. _changelog.7_1_0:

7.1.0 (2019-09-10)
Expand Down
9 changes: 6 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,11 @@ A script and python module to check your AWS service limits and usage, and warn
Users building out scalable services in Amazon AWS often run into AWS' `service limits <http://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html>`_ -
often at the least convenient time (i.e. mid-deploy or when autoscaling fails). Amazon's `Trusted Advisor <https://aws.amazon.com/premiumsupport/trustedadvisor/>`_
can help this, but even the version that comes with Business and Enterprise support only monitors a small subset of AWS limits
and only alerts *weekly*. awslimitchecker provides a command line script and reusable package that queries your current
usage of AWS resources and compares it to limits (hard-coded AWS defaults that you can override, API-based limits where available, or data from Trusted
Advisor where available), notifying you when you are approaching or at your limits.
and only alerts *weekly*. The new Service Quotas service can also help with this, but relies on CloudWatch alarms per-limit to notify
you when you approach your limits; this cannot easily scale to the hundreds of current service limits. awslimitchecker provides a command line
script and reusable Python package that queries your current usage of AWS resources and compares it to limits (hard-coded AWS defaults that you
can override, API-based limits where available, Service Quotas data where available, or data from Trusted Advisor where available), notifying
you when you are approaching or at your limits.

Full project documentation for the latest release is available at `http://awslimitchecker.readthedocs.io/en/latest/ <http://awslimitchecker.readthedocs.io/en/latest/>`_.

Expand All @@ -82,6 +84,7 @@ What It Does
- Define custom thresholds per-limit
- where possible, pull current limits from Trusted Advisor API
- where possible, pull current limits from each service's API (for services that provide this information)
- where possible, pull current limits from the Service Quotas service
- Supports explicitly setting the AWS region
- Supports using `STS <http://docs.aws.amazon.com/STS/latest/APIReference/Welcome.html>`_ to assume roles in other accounts, including using ``external_id``.
- Optionally refresh Trusted Advisor "Service Limits" check before polling
Expand Down
16 changes: 14 additions & 2 deletions awslimitchecker/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from .trustedadvisor import TrustedAdvisor
from .version import _get_version_info
from .utils import _get_latest_version
from .quotas import ServiceQuotasClient
import boto3
import sys
import logging
Expand All @@ -64,7 +65,7 @@ def __init__(self, warning_threshold=80, critical_threshold=99,
role_partition='aws', region=None, external_id=None,
mfa_serial_number=None, mfa_token=None, ta_refresh_mode=None,
ta_refresh_timeout=None, ta_api_region='us-east-1',
check_version=True):
check_version=True, skip_quotas=False):
"""
Main AwsLimitChecker class - this should be the only externally-used
portion of awslimitchecker.
Expand Down Expand Up @@ -136,6 +137,9 @@ def __init__(self, warning_threshold=80, critical_threshold=99,
:param check_version: Whether or not to check for latest version of
awslimitchecker on PyPI during instantiation.
:type check_version: bool
:param skip_quotas: If set to True, do not connect to Service Quotas
service or use it to obtain current limits.
:type skip_quotas: bool
"""
# ###### IMPORTANT license notice ##########
# Pursuant to Sections 5(b) and 13 of the GNU Affero General Public
Expand Down Expand Up @@ -183,10 +187,14 @@ def __init__(self, warning_threshold=80, critical_threshold=99,
self.services = {}

boto_conn_kwargs = self._boto_conn_kwargs
self._quotas_client = None
if not skip_quotas:
self._quotas_client = ServiceQuotasClient(boto_conn_kwargs)
for sname, cls in _services.items():
self.services[sname] = cls(warning_threshold,
critical_threshold,
boto_conn_kwargs)
boto_conn_kwargs,
self._quotas_client)

self.ta = TrustedAdvisor(self.services,
boto_conn_kwargs,
Expand Down Expand Up @@ -341,6 +349,7 @@ def get_limits(self, service=None, use_ta=True):
for sname, cls in to_get.items():
if hasattr(cls, '_update_limits_from_api'):
cls._update_limits_from_api()
cls._update_service_quotas()
res[sname] = cls.get_limits()
return res

Expand Down Expand Up @@ -419,6 +428,7 @@ def find_usage(self, service=None, use_ta=True):
for cls in to_get.values():
if hasattr(cls, '_update_limits_from_api'):
cls._update_limits_from_api()
cls._update_service_quotas()
logger.debug("Finding usage for service: %s", cls.service_name)
cls.find_usage()

Expand Down Expand Up @@ -619,6 +629,7 @@ def check_thresholds(self, service=None, use_ta=True):
for sname, cls in to_get.items():
if hasattr(cls, '_update_limits_from_api'):
cls._update_limits_from_api()
cls._update_service_quotas()
tmp = cls.check_thresholds()
if len(tmp) > 0:
res[sname] = tmp
Expand All @@ -637,6 +648,7 @@ def get_required_iam_policy(self):
:rtype: dict
"""
required_actions = [
'servicequotas:ListServiceQuotas',
'support:*',
'trustedadvisor:Describe*',
'trustedadvisor:RefreshCheck'
Expand Down
106 changes: 95 additions & 11 deletions awslimitchecker/limit.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,18 @@
#: indicates a limit value that came from the service's API
SOURCE_API = 3

#: indicates a limit value that came from the Service Quotas service
SOURCE_QUOTAS = 4


class AwsLimit(object):

def __init__(self, name, service, default_limit,
def_warning_threshold, def_critical_threshold,
limit_type=None, limit_subtype=None,
ta_service_name=None, ta_limit_name=None):
ta_service_name=None, ta_limit_name=None,
quotas_service_code=None, quotas_name=None,
quotas_unit='None', quotas_unit_converter=None):
"""
Describes one specific AWS service limit, as well as its
current utilization, default limit, thresholds, and any
Expand Down Expand Up @@ -88,6 +93,28 @@ def __init__(self, name, service, default_limit,
:param ta_limit_name: The limit name returned by Trusted Advisor for
this limit, if different from ``name``.
:type ta_limit_name: str
:param quotas_service_code: the Service Quotas service code to
retrieve this limit from, if different from the
:py:attr:`~._AwsService.quotas_service_code` attribute of
``service``.
:type quotas_service_code: str or None
:param quotas_name: the Service Quotas quota name to use for this
limit, if different from the limit ``name``.
:type quotas_name: str or None
:param quotas_unit: the Service Quotas quota unit that we need our
limit value to be, for quotas that use units. This must be one of
the units supported by :py:class:`~.ServiceQuotasClient`. It defaults
to the string "None", which (for some strange reason) is what's
returned by the Service Quotas API.
:type quotas_unit: str
:param quotas_unit_converter: A callable to be passed to
:py:meth:`~.ServiceQuotasClient.get_quota_value` for unit conversion.
Must take three positional arguments: the Service Quotas value for
this quota (float), the quota ``Unit`` str, and the return value of
:py:meth:`~.quotas_unit`. This callable is responsible for converting
the quota value from the quota Unit to this class's expected unit.
If they cannot be converted, it should log an error and return None.
:type quotas_unit_converter: ``callable``
:raises: ValueError
"""
if def_warning_threshold >= def_critical_threshold:
Expand All @@ -114,6 +141,11 @@ def __init__(self, name, service, default_limit,
self._criticals = []
self._ta_service_name = ta_service_name
self._ta_limit_name = ta_limit_name
self._quotas_service_code = quotas_service_code
self._quotas_name = quotas_name
self._quotas_unit = quotas_unit
self.quotas_limit = None
self.quotas_unit_converter = quotas_unit_converter

def set_limit_override(self, limit_value, override_ta=True):
"""
Expand Down Expand Up @@ -162,20 +194,34 @@ def _set_api_limit(self, limit_value):
"""
self.api_limit = limit_value

def _set_quotas_limit(self, limit_value):
"""
Set the value for the limit as reported by the Service Quotas service.
This method should only be called from the Service class.
:param limit_value: the Service Quotas limit value
:type limit_value: float
"""
self.quotas_limit = limit_value

def get_limit_source(self):
"""
Return :py:const:`~awslimitchecker.limit.SOURCE_DEFAULT` if
Return :py:data:`~awslimitchecker.limit.SOURCE_DEFAULT` if
:py:meth:`~.get_limit` returns the default limit,
:py:const:`~awslimitchecker.limit.SOURCE_OVERRIDE` if it returns a
:py:data:`~awslimitchecker.limit.SOURCE_OVERRIDE` if it returns a
manually-overridden limit,
:py:const:`~awslimitchecker.limit.SOURCE_TA` if it returns a limit from
Trusted Advisor, or :py:const:`~awslimitchecker.limit.SOURCE_API`
if it returns a limit retrieved from the service's API.
:returns: one of :py:const:`~awslimitchecker.limit.SOURCE_DEFAULT`,
:py:const:`~awslimitchecker.limit.SOURCE_OVERRIDE`, or
:py:const:`~awslimitchecker.limit.SOURCE_TA`, or
:py:const:`~awslimitchecker.limit.SOURCE_API`
:py:data:`~awslimitchecker.limit.SOURCE_TA` if it returns a limit from
Trusted Advisor, :py:data:`~awslimitchecker.limit.SOURCE_API` if it
returns a limit retrieved from the service's API, or
:py:data:`~.SOURCE_QUOTAS` if it returns a limit from the Service
Quotas service.
:returns: one of :py:data:`~awslimitchecker.limit.SOURCE_DEFAULT`,
:py:data:`~awslimitchecker.limit.SOURCE_OVERRIDE`, or
:py:data:`~awslimitchecker.limit.SOURCE_TA`, or
:py:data:`~awslimitchecker.limit.SOURCE_API`, or
:py:data:`~.awslimitchecker.limit.SOURCE_QUOTAS`
:rtype: int
"""
if self.limit_override is not None and (
Expand All @@ -185,6 +231,8 @@ def get_limit_source(self):
return SOURCE_OVERRIDE
if self.api_limit is not None:
return SOURCE_API
if self.quotas_limit is not None:
return SOURCE_QUOTAS
if self.ta_limit is not None or self.ta_unlimited is True:
return SOURCE_TA
return SOURCE_DEFAULT
Expand All @@ -203,6 +251,8 @@ def get_limit(self):
return self.limit_override
elif limit_type == SOURCE_API:
return self.api_limit
elif limit_type == SOURCE_QUOTAS:
return self.quotas_limit
elif limit_type == SOURCE_TA:
if self.ta_unlimited is True:
return None
Expand Down Expand Up @@ -444,6 +494,40 @@ def ta_limit_name(self):
return self._ta_limit_name
return self.name

@property
def quotas_service_code(self):
"""
Return the Service Quotas service code to use for this limit.
:return: Service Quotas service code
:rtype: str
"""
if self._quotas_service_code is not None:
return self._quotas_service_code
return self.service.quotas_service_code

@property
def quota_name(self):
"""
Return the Service Quotas quota name to use for this limit.
:return: Service Quotas quota name
:rtype: str
"""
if self._quotas_name is not None:
return self._quotas_name
return self.name

@property
def quotas_unit(self):
"""
Return the Service Quotas unit to use for this limit.
:return: Service Quotas unit
:rtype: str
"""
return self._quotas_unit


class AwsLimitUsage(object):

Expand Down

0 comments on commit 5ef6205

Please sign in to comment.