Merge pull request #36423 from kraney/boto_apigateway_usage_plan

Boto apigateway usage plan
This commit is contained in:
Mike Place 2016-09-20 15:47:03 +09:00 committed by GitHub
commit 67b78c7d97
4 changed files with 1604 additions and 3 deletions

View File

@ -98,6 +98,7 @@ try:
import boto3
# pylint: enable=unused-import
from botocore.exceptions import ClientError
from botocore import __version__ as found_botocore_version
logging.getLogger('boto').setLevel(logging.CRITICAL)
logging.getLogger('boto3').setLevel(logging.CRITICAL)
HAS_BOTO = True
@ -113,6 +114,7 @@ def __virtual__():
'''
required_boto_version = '2.8.0'
required_boto3_version = '1.2.1'
required_botocore_version = '1.4.49'
# the boto_apigateway execution module relies on the connect_to_region() method
# which was added in boto 2.8.0
# https://github.com/boto/boto/commit/33ac26b416fbb48a60602542b4ce15dcc7029f12
@ -125,6 +127,9 @@ def __virtual__():
elif _LooseVersion(boto3.__version__) < _LooseVersion(required_boto3_version):
return (False, 'The boto_apigateway module could not be loaded: '
'boto3 version {0} or later must be installed.'.format(required_boto3_version))
elif _LooseVersion(found_botocore_version) < _LooseVersion(required_botocore_version):
return (False, 'The boto_apigateway module could not be loaded: '
'botocore version {0} or later must be installed.'.format(required_botocore_version))
else:
return True
@ -1388,3 +1393,309 @@ def create_api_integration_response(restApiId, resourcePath, httpMethod, statusC
return {'created': False, 'error': 'no such resource'}
except ClientError as e:
return {'created': False, 'error': salt.utils.boto3.get_error(e)}
def _filter_plans(attr, name, plans):
'''
Helper to return list of usage plan items matching the given attribute value.
'''
return [plan for plan in plans if plan[attr] == name]
def describe_usage_plans(name=None, plan_id=None, region=None, key=None, keyid=None, profile=None):
'''
Returns a list of existing usage plans, optionally filtered to match a given plan name
CLI Example:
.. code-block:: bash
salt myminion boto_apigateway.describe_usage_plans
salt myminion boto_apigateway.describe_usage_plans name='usage plan name'
salt myminion boto_apigateway.describe_usage_plans plan_id='usage plan id'
.. versionadded:: Carbon
'''
try:
conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
plans = _multi_call(conn.get_usage_plans, 'items')
if name:
plans = _filter_plans('name', name, plans)
if plan_id:
plans = _filter_plans('id', plan_id, plans)
return {'plans': [_convert_datetime_str(plan) for plan in plans]}
except ClientError as e:
return {'error': salt.utils.boto3.get_error(e)}
def _validate_throttle(throttle):
'''
Helper to verify that throttling parameters are valid
'''
if throttle is not None:
if not isinstance(throttle, dict):
raise TypeError('throttle must be a dictionary, provided value: {0}'.format(throttle))
def _validate_quota(quota):
'''
Helper to verify that quota parameters are valid
'''
if quota is not None:
if not isinstance(quota, dict):
raise TypeError('quota must be a dictionary, provided value: {0}'.format(quota))
periods = ['DAY', 'WEEK', 'MONTH']
if 'period' not in quota or quota['period'] not in periods:
raise ValueError('quota must have a valid period specified, valid values are {0}'.format(','.join(periods)))
if 'limit' not in quota:
raise ValueError('quota limit must have a valid value')
def create_usage_plan(name, description=None, throttle=None, quota=None, region=None, key=None, keyid=None, profile=None):
'''
Creates a new usage plan with throttling and quotas optionally applied
name
Name of the usage plan
throttle
A dictionary consisting of the following keys:
rateLimit
requests per second at steady rate, float
burstLimit
maximum number of requests per second, integer
quota
A dictionary consisting of the following keys:
limit
number of allowed requests per specified quota period [required if quota parameter is present]
offset
number of requests to be subtracted from limit at the beginning of the period [optional]
period
quota period, must be one of DAY, WEEK, or MONTH. [required if quota parameter is present
CLI Example:
.. code-block:: bash
salt myminion boto_apigateway.create_usage_plan name='usage plan name' throttle='{"rateLimit": 10.0, "burstLimit": 10}'
.. versionadded:: Carbon
'''
try:
_validate_throttle(throttle)
_validate_quota(quota)
values = dict(name=name)
if description:
values['description'] = description
if throttle:
values['throttle'] = throttle
if quota:
values['quota'] = quota
conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
res = conn.create_usage_plan(**values)
return {'created': True, 'result': res}
except ClientError as e:
return {'error': salt.utils.boto3.get_error(e)}
except (TypeError, ValueError) as e:
return {'error': '{0}'.format(e)}
def update_usage_plan(plan_id, throttle=None, quota=None, region=None, key=None, keyid=None, profile=None):
'''
Updates an existing usage plan with throttling and quotas
plan_id
Id of the created usage plan
throttle
A dictionary consisting of the following keys:
rateLimit
requests per second at steady rate, float
burstLimit
maximum number of requests per second, integer
quota
A dictionary consisting of the following keys:
limit
number of allowed requests per specified quota period [required if quota parameter is present]
offset
number of requests to be subtracted from limit at the beginning of the period [optional]
period
quota period, must be one of DAY, WEEK, or MONTH. [required if quota parameter is present
CLI Example:
.. code-block:: bash
salt myminion boto_apigateway.update_usage_plan plan_id='usage plan id' throttle='{"rateLimit": 10.0, "burstLimit": 10}'
.. versionadded:: Carbon
'''
try:
_validate_throttle(throttle)
_validate_quota(quota)
conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
patchOperations = []
if throttle is None:
patchOperations.append({'op': 'remove', 'path': '/throttle'})
else:
if 'rateLimit' in throttle:
patchOperations.append({'op': 'replace', 'path': '/throttle/rateLimit', 'value': str(throttle['rateLimit'])})
if 'burstLimit' in throttle:
patchOperations.append({'op': 'replace', 'path': '/throttle/burstLimit', 'value': str(throttle['burstLimit'])})
if quota is None:
patchOperations.append({'op': 'remove', 'path': '/quota'})
else:
patchOperations.append({'op': 'replace', 'path': '/quota/period', 'value': str(quota['period'])})
patchOperations.append({'op': 'replace', 'path': '/quota/limit', 'value': str(quota['limit'])})
if 'offset' in quota:
patchOperations.append({'op': 'replace', 'path': '/quota/offset', 'value': str(quota['offset'])})
if patchOperations:
res = conn.update_usage_plan(usagePlanId=plan_id,
patchOperations=patchOperations)
return {'updated': True, 'result': res}
return {'updated': False}
except ClientError as e:
return {'error': salt.utils.boto3.get_error(e)}
except (TypeError, ValueError) as e:
return {'error': '{0}'.format(e)}
def delete_usage_plan(plan_id, region=None, key=None, keyid=None, profile=None):
'''
Deletes usage plan identified by plan_id
CLI Example:
.. code-block:: bash
salt myminion boto_apigateway.delete_usage_plan plan_id='usage plan id'
.. versionadded:: Carbon
'''
try:
existing = describe_usage_plans(plan_id=plan_id, region=region, key=key, keyid=keyid, profile=profile)
# don't attempt to delete the usage plan if it does not exist
if 'error' in existing:
return {'error': existing['error']}
if 'plans' in existing and existing['plans']:
conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
res = conn.delete_usage_plan(usagePlanId=plan_id)
return {'deleted': True, 'usagePlanId': plan_id}
except ClientError as e:
return {'error': salt.utils.boto3.get_error(e)}
def _update_usage_plan_apis(plan_id, apis, op, region=None, key=None, keyid=None, profile=None):
'''
Helper function that updates the usage plan identified by plan_id by adding or removing it to each of the stages, specified by apis parameter.
apis
a list of dictionaries, where each dictionary contains the following:
apiId
a string, which is the id of the created API in AWS ApiGateway
stage
a string, which is the stage that the created API is deployed to.
op
'add' or 'remove'
'''
try:
patchOperations = []
for api in apis:
patchOperations.append({
'op': op,
'path': '/apiStages',
'value': '{0}:{1}'.format(api['apiId'], api['stage'])
})
res = None
if patchOperations:
conn = _get_conn(region=region, key=key, keyid=keyid, profile=profile)
res = conn.update_usage_plan(usagePlanId=plan_id,
patchOperations=patchOperations)
return {'success': True, 'result': res}
except ClientError as e:
return {'error': salt.utils.boto3.get_error(e)}
except Exception as e:
return {'error': e}
def attach_usage_plan_to_apis(plan_id, apis, region=None, key=None, keyid=None, profile=None):
'''
Attaches given usage plan to each of the apis provided in a list of apiId and stage values
apis
a list of dictionaries, where each dictionary contains the following:
apiId
a string, which is the id of the created API in AWS ApiGateway
stage
a string, which is the stage that the created API is deployed to.
CLI Example:
.. code-block:: bash
salt myminion boto_apigateway.attach_usage_plan_to_apis plan_id='usage plan id' apis='[{"apiId": "some id 1", "stage": "some stage 1"}]'
.. versionadded:: Carbon
'''
return _update_usage_plan_apis(plan_id, apis, 'add', region=region, key=key, keyid=keyid, profile=profile)
def detach_usage_plan_from_apis(plan_id, apis, region=None, key=None, keyid=None, profile=None):
'''
Detaches given usage plan from each of the apis provided in a list of apiId and stage value
apis
a list of dictionaries, where each dictionary contains the following:
apiId
a string, which is the id of the created API in AWS ApiGateway
stage
a string, which is the stage that the created API is deployed to.
CLI Example:
.. code-block:: bash
salt myminion boto_apigateway.detach_usage_plan_to_apis plan_id='usage plan id' apis='[{"apiId": "some id 1", "stage": "some stage 1"}]'
.. versionadded:: Carbon
'''
return _update_usage_plan_apis(plan_id, apis, 'remove', region=region, key=key, keyid=keyid, profile=profile)

View File

@ -1615,3 +1615,393 @@ class _Swagger(object):
ret = self._deploy_method(ret, path, method, method_data, api_key_required,
lambda_integration_role, lambda_region, authorization_type)
return ret
def usage_plan_present(name, plan_name, description=None, throttle=None, quota=None, region=None, key=None, keyid=None, profile=None):
'''
Ensure the spcifieda usage plan with the corresponding metrics is deployed
name
name of the state
plan_name
[Required] name of the usage plan
throttle
[Optional] throttling parameters expressed as a dictionary.
If provided, at least one of the throttling parameters must be present
rateLimit
rate per second at which capacity bucket is populated
burstLimit
maximum rate allowed
quota
[Optional] quota on the number of api calls permitted by the plan.
If provided, limit and period must be present
limit
[Required] number of calls permitted per quota period
offset
[Optional] number of calls to be subtracted from the limit at the beginning of the period
period
[Required] period to which quota applies. Must be DAY, WEEK or MONTH
.. code-block:: yaml
UsagePlanPresent:
boto_apigateway.usage_plan_present:
- plan_name: my_usage_plan
- throttle:
rateLimit: 70
burstLimit: 100
- quota:
limit: 1000
offset: 0
period: DAY
- profile: my_profile
.. versionadded:: Carbon
'''
func_params = locals()
ret = {'name': name,
'result': True,
'comment': '',
'changes': {}
}
try:
common_args = dict([('region', region),
('key', key),
('keyid', keyid),
('profile', profile)])
existing = __salt__['boto_apigateway.describe_usage_plans'](name=plan_name, **common_args)
if 'error' in existing:
ret['result'] = False
ret['comment'] = 'Failed to describe existing usage plans'
return ret
if not existing['plans']:
# plan does not exist, we need to create it
if __opts__['test']:
ret['comment'] = 'a new usage plan {0} would be created'.format(plan_name)
ret['result'] = None
return ret
result = __salt__['boto_apigateway.create_usage_plan'](name=plan_name,
description=description,
throttle=throttle,
quota=quota,
**common_args)
if 'error' in result:
ret['result'] = False
ret['comment'] = 'Failed to create a usage plan {0}, {1}'.format(plan_name, result['error'])
return ret
ret['changes']['old'] = {'plan': None}
ret['comment'] = 'A new usage plan {0} has been created'.format(plan_name)
else:
# need an existing plan modified to match given value
plan = existing['plans'][0]
needs_updating = False
modifiable_params = (('throttle', ('rateLimit', 'burstLimit')), ('quota', ('limit', 'offset', 'period')))
for p, fields in modifiable_params:
for f in fields:
actual_param = {} if func_params.get(p) is None else func_params.get(p)
if plan.get(p, {}).get(f, None) != actual_param.get(f, None):
needs_updating = True
break
if not needs_updating:
ret['comment'] = 'usage plan {0} is already in a correct state'.format(plan_name)
ret['result'] = True
return ret
if __opts__['test']:
ret['comment'] = 'a new usage plan {0} would be updated'.format(plan_name)
ret['result'] = None
return ret
result = __salt__['boto_apigateway.update_usage_plan'](plan['id'],
throttle=throttle,
quota=quota,
**common_args)
if 'error' in result:
ret['result'] = False
ret['comment'] = 'Failed to update a usage plan {0}, {1}'.format(plan_name, result['error'])
return ret
ret['changes']['old'] = {'plan': plan}
ret['comment'] = 'usage plan {0} has been updated'.format(plan_name)
newstate = __salt__['boto_apigateway.describe_usage_plans'](name=plan_name, **common_args)
if 'error' in existing:
ret['result'] = False
ret['comment'] = 'Failed to describe existing usage plans after updates'
return ret
ret['changes']['new'] = {'plan': newstate['plans'][0]}
except (ValueError, IOError) as e:
ret['result'] = False
ret['comment'] = '{0}'.format(e.args)
return ret
def usage_plan_absent(name, plan_name, region=None, key=None, keyid=None, profile=None):
'''
Ensures usage plan identified by name is no longer present
name
name of the state
plan_name
name of the plan to remove
.. code-block:: yaml
usage plan absent:
boto_apigateway.usage_plan_absent:
- plan_name: my_usage_plan
- profile: my_profile
.. versionadded:: Carbon
'''
ret = {'name': name,
'result': True,
'comment': '',
'changes': {}
}
try:
common_args = dict([('region', region),
('key', key),
('keyid', keyid),
('profile', profile)])
existing = __salt__['boto_apigateway.describe_usage_plans'](name=plan_name, **common_args)
if 'error' in existing:
ret['result'] = False
ret['comment'] = 'Failed to describe existing usage plans'
return ret
if not existing['plans']:
ret['comment'] = 'Usage plan {0} does not exist already'.format(plan_name)
return ret
if __opts__['test']:
ret['comment'] = 'Usage plan {0} exists and would be deleted'.format(plan_name)
ret['result'] = None
return ret
plan_id = existing['plans'][0]['id']
result = __salt__['boto_apigateway.delete_usage_plan'](plan_id, **common_args)
if 'error' in result:
ret['result'] = False
ret['comment'] = 'Failed to delete usage plan {0}, {1}'.format(plan_name, result)
return ret
ret['comment'] = 'Usage plan {0} has been deleted'.format(plan_name)
ret['changes']['old'] = {'plan': existing['plans'][0]}
ret['changes']['new'] = {'plan': None}
except (ValueError, IOError) as e:
ret['result'] = False
ret['comment'] = '{0}'.format(e.args)
return ret
def usage_plan_association_present(name, plan_name, api_stages, region=None, key=None, keyid=None, profile=None):
'''
Ensures usage plan identified by name is added to provided api_stages
name
name of the state
plan_name
name of the plan to use
api_stages
list of dictionaries, where each dictionary consists of the following keys:
apiId
apiId of the api to attach usage plan to
stage
stage name of the api to attach usage plan to
.. code-block:: yaml
UsagePlanAssociationPresent:
boto_apigateway.usage_plan_association_present:
- plan_name: my_plan
- api_stages:
- apiId: 9kb0404ec0
stage: my_stage
- apiId: l9v7o2aj90
stage: my_stage
- profile: my_profile
.. versionadded:: Carbon
'''
ret = {'name': name,
'result': True,
'comment': '',
'changes': {}
}
try:
common_args = dict([('region', region),
('key', key),
('keyid', keyid),
('profile', profile)])
existing = __salt__['boto_apigateway.describe_usage_plans'](name=plan_name, **common_args)
if 'error' in existing:
ret['result'] = False
ret['comment'] = 'Failed to describe existing usage plans'
return ret
if not existing['plans']:
ret['comment'] = 'Usage plan {0} does not exist'.format(plan_name)
ret['result'] = False
return ret
if len(existing['plans']) != 1:
ret['comment'] = 'There are multiple usage plans with the same name - it is not supported'
ret['result'] = False
return ret
plan = existing['plans'][0]
plan_id = plan['id']
plan_stages = plan.get('apiStages', [])
stages_to_add = []
for api in api_stages:
if api not in plan_stages:
stages_to_add.append(api)
if not stages_to_add:
ret['comment'] = 'Usage plan is already asssociated to all api stages'
return ret
result = __salt__['boto_apigateway.attach_usage_plan_to_apis'](plan_id, stages_to_add, **common_args)
if 'error' in result:
ret['comment'] = 'Failed to associate a usage plan {0} to the apis {1}, {2}'.format(plan_name, stages_to_add, result['error'])
ret['result'] = False
return ret
ret['comment'] = 'successfully associated usage plan to apis'
ret['changes']['old'] = plan_stages
ret['changes']['new'] = result.get('result', {}).get('apiStages', [])
except (ValueError, IOError) as e:
ret['result'] = False
ret['comment'] = '{0}'.format(e.args)
return ret
def usage_plan_association_absent(name, plan_name, api_stages, region=None, key=None, keyid=None, profile=None):
'''
Ensures usage plan identified by name is removed from provided api_stages
If a plan is associated to stages not listed in api_stages parameter,
those associations remain intact
name
name of the state
plan_name
name of the plan to use
api_stages
list of dictionaries, where each dictionary consists of the following keys:
apiId
apiId of the api to detach usage plan from
stage
stage name of the api to detach usage plan from
.. code-block:: yaml
UsagePlanAssociationAbsent:
boto_apigateway.usage_plan_association_absent:
- plan_name: my_plan
- api_stages:
- apiId: 9kb0404ec0
stage: my_stage
- apiId: l9v7o2aj90
stage: my_stage
- profile: my_profile
.. versionadded:: Carbon
'''
ret = {'name': name,
'result': True,
'comment': '',
'changes': {}
}
try:
common_args = dict([('region', region),
('key', key),
('keyid', keyid),
('profile', profile)])
existing = __salt__['boto_apigateway.describe_usage_plans'](name=plan_name, **common_args)
if 'error' in existing:
ret['result'] = False
ret['comment'] = 'Failed to describe existing usage plans'
return ret
if not existing['plans']:
ret['comment'] = 'Usage plan {0} does not exist'.format(plan_name)
ret['result'] = False
return ret
if len(existing['plans']) != 1:
ret['comment'] = 'There are multiple usage plans with the same name - it is not supported'
ret['result'] = False
return ret
plan = existing['plans'][0]
plan_id = plan['id']
plan_stages = plan.get('apiStages', [])
if not plan_stages:
ret['comment'] = 'Usage plan {0} has no associated stages already'.format(plan_name)
return ret
stages_to_remove = []
for api in api_stages:
if api in plan_stages:
stages_to_remove.append(api)
if not stages_to_remove:
ret['comment'] = 'Usage plan is already not asssociated to any api stages'
return ret
result = __salt__['boto_apigateway.detach_usage_plan_from_apis'](plan_id, stages_to_remove, **common_args)
if 'error' in result:
ret['comment'] = 'Failed to disassociate a usage plan {0} from the apis {1}, {2}'.format(plan_name, stages_to_remove, result['error'])
ret['result'] = False
return ret
ret['comment'] = 'successfully disassociated usage plan from apis'
ret['changes']['old'] = plan_stages
ret['changes']['new'] = result.get('result', {}).get('apiStages', [])
except (ValueError, IOError) as e:
ret['result'] = False
ret['comment'] = '{0}'.format(e.args)
return ret

View File

@ -24,6 +24,7 @@ from salt.modules import boto_apigateway
# pylint: disable=import-error,no-name-in-module
try:
import boto3
import botocore
from botocore.exceptions import ClientError
HAS_BOTO = True
except ImportError:
@ -37,6 +38,7 @@ from salt.ext.six.moves import range, zip
# which was added in boto 2.8.0
# https://github.com/boto/boto/commit/33ac26b416fbb48a60602542b4ce15dcc7029f12
required_boto3_version = '1.2.1'
required_botocore_version = '1.4.49'
region = 'us-east-1'
access_key = 'GKTADJGHEIQSXMKKRBJ08H'
@ -91,6 +93,57 @@ api_create_resource_ret = {
u'pathPart': u'api3',
'ResponseMetadata': {'HTTPStatusCode': 200, 'RequestId': '2d31072c-9d15-11e5-9977-6d9fcfda9c0a'}}
usage_plan1 = dict(
id='plan1_id',
name='plan1_name',
description='plan1_desc',
apiStages=[],
throttle=dict(
burstLimit=123,
rateLimit=123.0
),
quota=dict(
limit=123,
offset=123,
period='DAY'
)
)
usage_plan2 = dict(
id='plan2_id',
name='plan2_name',
description='plan2_desc',
apiStages=[],
throttle=dict(
burstLimit=123,
rateLimit=123.0
),
quota=dict(
limit=123,
offset=123,
period='DAY'
)
)
usage_plan1b = dict(
id='another_plan1_id',
name='plan1_name',
description='another_plan1_desc',
apiStages=[],
throttle=dict(
burstLimit=123,
rateLimit=123.0
),
quota=dict(
limit=123,
offset=123,
period='DAY'
)
)
usage_plans_ret = dict(
items=[
usage_plan1, usage_plan2, usage_plan1b
]
)
log = logging.getLogger(__name__)
opts = salt.config.DEFAULT_MINION_OPTS
@ -115,6 +168,18 @@ def _has_required_boto():
return True
def _has_required_botocore():
'''
Returns True/False boolean depending on if botocore supports usage plan
'''
if not HAS_BOTO:
return False
elif LooseVersion(botocore.__version__) < LooseVersion(required_botocore_version):
return False
else:
return True
class BotoApiGatewayTestCaseBase(TestCase):
conn = None
@ -154,9 +219,12 @@ class BotoApiGatewayTestCaseMixin(object):
@skipIf(True, 'Skip these tests while investigating failures')
@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
' or equal to version {0}'
.format(required_boto3_version))
@skipIf(_has_required_boto() is False,
'The boto3 module must be greater than'
' or equal to version {0}'.format(required_boto3_version))
@skipIf(_has_required_botocore() is False,
'The botocore module must be greater than'
' or equal to version {0}'.format(required_botocore_version))
@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoApiGatewayTestCase(BotoApiGatewayTestCaseBase, BotoApiGatewayTestCaseMixin):
'''
@ -1379,6 +1447,205 @@ class BotoApiGatewayTestCase(BotoApiGatewayTestCaseBase, BotoApiGatewayTestCaseM
**conn_parameters)
self.assertTrue(result.get('error'))
def test_that_when_describing_usage_plans_and_an_exception_is_thrown_in_get_usage_plans(self):
'''
Tests True for existence of 'error'
'''
self.conn.get_usage_plans.side_effect = ClientError(error_content, 'get_usage_plans_exception')
result = boto_apigateway.describe_usage_plans(name='some plan', **conn_parameters)
self.assertEqual(result.get('error').get('message'), error_message.format('get_usage_plans_exception'))
def test_that_when_describing_usage_plans_and_plan_name_or_id_does_not_exist_that_results_have_empty_plans_list(self):
'''
Tests for plans equaling empty list
'''
self.conn.get_usage_plans.return_value = usage_plans_ret
result = boto_apigateway.describe_usage_plans(name='does not exist', **conn_parameters)
self.assertEqual(result.get('plans'), [])
result = boto_apigateway.describe_usage_plans(plan_id='does not exist', **conn_parameters)
self.assertEqual(result.get('plans'), [])
result = boto_apigateway.describe_usage_plans(name='does not exist', plan_id='does not exist', **conn_parameters)
self.assertEqual(result.get('plans'), [])
result = boto_apigateway.describe_usage_plans(name='plan1_name', plan_id='does not exist', **conn_parameters)
self.assertEqual(result.get('plans'), [])
result = boto_apigateway.describe_usage_plans(name='does not exist', plan_id='plan1_id', **conn_parameters)
self.assertEqual(result.get('plans'), [])
def test_that_when_describing_usage_plans_for_plans_that_exist_that_the_function_returns_all_matching_plans(self):
'''
Tests for plans filtering properly if they exist
'''
self.conn.get_usage_plans.return_value = usage_plans_ret
result = boto_apigateway.describe_usage_plans(name=usage_plan1['name'], **conn_parameters)
self.assertEqual(len(result.get('plans')), 2)
for plan in result['plans']:
self.assertTrue(plan in [usage_plan1, usage_plan1b])
def test_that_when_creating_or_updating_a_usage_plan_and_throttle_or_quota_failed_to_validate_that_an_error_is_returned(self):
'''
Tests for TypeError and ValueError in throttle and quota
'''
for throttle, quota in (([], None), (None, []), ('abc', None), (None, 'def')):
res = boto_apigateway.create_usage_plan('plan1_name', description=None, throttle=throttle, quota=quota, **conn_parameters)
self.assertNotEqual(None, res.get('error'))
res = boto_apigateway.update_usage_plan('plan1_id', throttle=throttle, quota=quota, **conn_parameters)
self.assertNotEqual(None, res.get('error'))
for quota in ({'limit': 123}, {'period': 123}, {'period': 'DAY'}):
res = boto_apigateway.create_usage_plan('plan1_name', description=None, throttle=None, quota=quota, **conn_parameters)
self.assertNotEqual(None, res.get('error'))
res = boto_apigateway.update_usage_plan('plan1_id', quota=quota, **conn_parameters)
self.assertNotEqual(None, res.get('error'))
self.conn.get_usage_plans.assert_not_called()
self.conn.create_usage_plan.assert_not_called()
self.conn.update_usage_plan.assert_not_called()
def test_that_when_creating_a_usage_plan_and_create_usage_plan_throws_an_exception_that_an_error_is_returned(self):
'''
tests for ClientError
'''
self.conn.create_usage_plan.side_effect = ClientError(error_content, 'create_usage_plan_exception')
result = boto_apigateway.create_usage_plan(name='some plan', **conn_parameters)
self.assertEqual(result.get('error').get('message'), error_message.format('create_usage_plan_exception'))
def test_that_create_usage_plan_succeeds(self):
'''
tests for success user plan creation
'''
res = 'unit test create_usage_plan succeeded'
self.conn.create_usage_plan.return_value = res
result = boto_apigateway.create_usage_plan(name='some plan', **conn_parameters)
self.assertEqual(result.get('created'), True)
self.assertEqual(result.get('result'), res)
def test_that_when_udpating_a_usage_plan_and_update_usage_plan_throws_an_exception_that_an_error_is_returned(self):
'''
tests for ClientError
'''
self.conn.update_usage_plan.side_effect = ClientError(error_content, 'update_usage_plan_exception')
result = boto_apigateway.update_usage_plan(plan_id='plan1_id', **conn_parameters)
self.assertEqual(result.get('error').get('message'), error_message.format('update_usage_plan_exception'))
def test_that_when_updating_a_usage_plan_and_if_throttle_and_quota_parameters_are_none_update_usage_plan_removes_throttle_and_quota(self):
'''
tests for throttle and quota removal
'''
ret = 'some success status'
self.conn.update_usage_plan.return_value = ret
result = boto_apigateway.update_usage_plan(plan_id='plan1_id', throttle=None, quota=None, **conn_parameters)
self.assertEqual(result.get('updated'), True)
self.assertEqual(result.get('result'), ret)
self.conn.update_usage_plan.assert_called_once()
def test_that_when_deleting_usage_plan_and_describe_usage_plans_had_error_that_the_same_error_is_returned(self):
'''
tests for error in describe_usage_plans returns error
'''
ret = 'get_usage_plans_exception'
self.conn.get_usage_plans.side_effect = ClientError(error_content, ret)
result = boto_apigateway.delete_usage_plan(plan_id='some plan id', **conn_parameters)
self.assertEqual(result.get('error').get('message'), error_message.format(ret))
self.conn.delete_usage_plan.assert_not_called()
def test_that_when_deleting_usage_plan_and_plan_exists_that_the_functions_returns_deleted_true(self):
self.conn.get_usage_plans.return_value = usage_plans_ret
ret = 'delete_usage_plan_retval'
self.conn.delete_usage_plan.return_value = ret
result = boto_apigateway.delete_usage_plan(plan_id='plan1_id', **conn_parameters)
self.assertEqual(result.get('deleted'), True)
self.assertEqual(result.get('usagePlanId'), 'plan1_id')
self.conn.delete_usage_plan.assert_called_once()
def test_that_when_deleting_usage_plan_and_plan_does_not_exist_that_the_functions_returns_deleted_true(self):
'''
tests for ClientError
'''
self.conn.get_usage_plans.return_value = dict(
items=[]
)
ret = 'delete_usage_plan_retval'
self.conn.delete_usage_plan.return_value = ret
result = boto_apigateway.delete_usage_plan(plan_id='plan1_id', **conn_parameters)
self.assertEqual(result.get('deleted'), True)
self.assertEqual(result.get('usagePlanId'), 'plan1_id')
self.conn.delete_usage_plan.assert_not_called()
def test_that_when_deleting_usage_plan_and_delete_usage_plan_throws_exception_that_an_error_is_returned(self):
'''
tests for ClientError
'''
self.conn.get_usage_plans.return_value = usage_plans_ret
error_msg = 'delete_usage_plan_exception'
self.conn.delete_usage_plan.side_effect = ClientError(error_content, error_msg)
result = boto_apigateway.delete_usage_plan(plan_id='plan1_id', **conn_parameters)
self.assertEqual(result.get('error').get('message'), error_message.format(error_msg))
self.conn.delete_usage_plan.assert_called_once()
def test_that_attach_or_detach_usage_plan_when_apis_is_empty_that_success_is_returned(self):
'''
tests for border cases when apis is empty list
'''
result = boto_apigateway.attach_usage_plan_to_apis(plan_id='plan1_id', apis=[], **conn_parameters)
self.assertEqual(result.get('success'), True)
self.assertEqual(result.get('result', 'no result?'), None)
self.conn.update_usage_plan.assert_not_called()
result = boto_apigateway.detach_usage_plan_from_apis(plan_id='plan1_id', apis=[], **conn_parameters)
self.assertEqual(result.get('success'), True)
self.assertEqual(result.get('result', 'no result?'), None)
self.conn.update_usage_plan.assert_not_called()
def test_that_attach_or_detach_usage_plan_when_api_does_not_contain_apiId_or_stage_that_an_error_is_returned(self):
'''
tests for invalid key in api object
'''
for api in ({'apiId': 'some Id'}, {'stage': 'some stage'}, {}):
result = boto_apigateway.attach_usage_plan_to_apis(plan_id='plan1_id', apis=[api], **conn_parameters)
self.assertNotEqual(result.get('error'), None)
result = boto_apigateway.detach_usage_plan_from_apis(plan_id='plan1_id', apis=[api], **conn_parameters)
self.assertNotEqual(result.get('error'), None)
self.conn.update_usage_plan.assert_not_called()
def test_that_attach_or_detach_usage_plan_and_update_usage_plan_throws_exception_that_an_error_is_returned(self):
'''
tests for ClientError
'''
api = {'apiId': 'some_id', 'stage': 'some_stage'}
error_msg = 'update_usage_plan_exception'
self.conn.update_usage_plan.side_effect = ClientError(error_content, error_msg)
result = boto_apigateway.attach_usage_plan_to_apis(plan_id='plan1_id', apis=[api], **conn_parameters)
self.assertEqual(result.get('error').get('message'), error_message.format(error_msg))
result = boto_apigateway.detach_usage_plan_from_apis(plan_id='plan1_id', apis=[api], **conn_parameters)
self.assertEqual(result.get('error').get('message'), error_message.format(error_msg))
def test_that_attach_or_detach_usage_plan_updated_successfully(self):
'''
tests for update_usage_plan called
'''
api = {'apiId': 'some_id', 'stage': 'some_stage'}
attach_ret = 'update_usage_plan_add_op_succeeded'
detach_ret = 'update_usage_plan_remove_op_succeeded'
self.conn.update_usage_plan.side_effect = [attach_ret, detach_ret]
result = boto_apigateway.attach_usage_plan_to_apis(plan_id='plan1_id', apis=[api], **conn_parameters)
self.assertEqual(result.get('success'), True)
self.assertEqual(result.get('result'), attach_ret)
result = boto_apigateway.detach_usage_plan_from_apis(plan_id='plan1_id', apis=[api], **conn_parameters)
self.assertEqual(result.get('success'), True)
self.assertEqual(result.get('result'), detach_ret)
if __name__ == '__main__':
from integration import run_tests # pylint: disable=import-error
run_tests(BotoApiGatewayTestCase, needs_daemon=False)

View File

@ -29,6 +29,7 @@ from unit.modules.boto_apigateway_test import BotoApiGatewayTestCaseMixin
# Import 3rd-party libs
try:
import boto3
import botocore
from botocore.exceptions import ClientError
HAS_BOTO = True
except ImportError:
@ -36,12 +37,20 @@ except ImportError:
from salt.ext.six.moves import range
# Import Salt Libs
from salt.states import boto_apigateway
boto_apigateway.__salt__ = {}
boto_apigateway.__opts__ = {}
# pylint: enable=import-error,no-name-in-module
# the boto_apigateway module relies on the connect_to_region() method
# which was added in boto 2.8.0
# https://github.com/boto/boto/commit/33ac26b416fbb48a60602542b4ce15dcc7029f12
required_boto3_version = '1.2.1'
required_botocore_version = '1.4.49'
region = 'us-east-1'
access_key = 'GKTADJGHEIQSXMKKRBJ08H'
@ -298,6 +307,10 @@ method_ret = dict(apiKeyRequired=False,
requestModels={'application/json': 'User'},
requestParameters={})
throttle_rateLimit = 10.0
association_stage_1 = {'apiId': 'apiId1', 'stage': 'stage1'}
association_stage_2 = {'apiId': 'apiId1', 'stage': 'stage2'}
log = logging.getLogger(__name__)
opts = salt.config.DEFAULT_MINION_OPTS
@ -321,6 +334,18 @@ def _has_required_boto():
return True
def _has_required_botocore():
'''
Returns True/False boolean depending on if botocore supports usage plan
'''
if not HAS_BOTO:
return False
elif LooseVersion(botocore.__version__) < LooseVersion(required_botocore_version):
return False
else:
return True
class TempSwaggerFile(object):
_tmp_swagger_dict = {'info': {'version': '0.0.0',
'description': 'salt boto apigateway unit test service',
@ -975,3 +1000,611 @@ class BotoApiGatewayTestCase(BotoApiGatewayStateTestCaseBase, BotoApiGatewayTest
self.assertIs(result.get('result'), True)
self.assertIsNot(result.get('abort'), True)
@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
' or equal to version {0}'
.format(required_boto3_version))
@skipIf(_has_required_botocore() is False,
'The botocore module must be greater than'
' or equal to version {0}'.format(required_botocore_version))
@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoApiGatewayUsagePlanTestCase(BotoApiGatewayStateTestCaseBase, BotoApiGatewayTestCaseMixin):
'''
TestCase for salt.modules.boto_apigateway state.module, usage_plans portion
'''
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(return_value={'error': 'error'})})
def test_usage_plan_present_if_describe_fails(self, *args):
'''
Tests correct error processing for describe_usage_plan failure
'''
result = {}
result = boto_apigateway.usage_plan_present('name', 'plan_name', **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], False)
self.assertIn('comment', result)
self.assertEqual(result['comment'], 'Failed to describe existing usage plans')
self.assertIn('changes', result)
self.assertEqual(result['changes'], {})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(return_value={'plans': []})})
@patch.dict(boto_apigateway.__opts__, {'test': True})
def test_usage_plan_present_if_there_is_no_such_plan_and_test_option_is_set(self, *args):
'''
TestCse for salt.modules.boto_apigateway state.module, checking that if __opts__['test'] is set
and usage plan does not exist, correct diagnostic will be returned
'''
result = {}
result = boto_apigateway.usage_plan_present('name', 'plan_name', **conn_parameters)
self.assertIn('comment', result)
self.assertEqual(result['comment'], 'a new usage plan plan_name would be created')
self.assertIn('result', result)
self.assertEqual(result['result'], None)
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(return_value={'plans': []})})
@patch.dict(boto_apigateway.__opts__, {'test': False})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.create_usage_plan': MagicMock(return_value={'error': 'error'})})
def test_usage_plan_present_if_create_usage_plan_fails(self, *args):
'''
Tests behavior for the case when creating a new usage plan fails
'''
result = {}
result = boto_apigateway.usage_plan_present('name', 'plan_name', **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], False)
self.assertIn('comment', result)
self.assertEqual(result['comment'], 'Failed to create a usage plan plan_name, error')
self.assertIn('changes', result)
self.assertEqual(result['changes'], {})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(return_value={'plans': [{
'id': 'planid',
'name': 'planname'
}]})})
@patch.dict(boto_apigateway.__opts__, {'test': False})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.update_usage_plan': MagicMock()})
def test_usage_plan_present_if_plan_is_there_and_needs_no_updates(self, *args):
'''
Tests behavior for the case when plan is present and needs no updates
'''
result = {}
result = boto_apigateway.usage_plan_present('name', 'plan_name', **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], True)
self.assertIn('comment', result)
self.assertEqual(result['comment'], 'usage plan plan_name is already in a correct state')
self.assertIn('changes', result)
self.assertEqual(result['changes'], {})
boto_apigateway.__salt__['boto_apigateway.update_usage_plan'].assert_not_called()
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(return_value={'plans': [{
'id': 'planid',
'name': 'planname',
'throttle': {'rateLimit': 10.0}
}]})})
@patch.dict(boto_apigateway.__opts__, {'test': True})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.update_usage_plan': MagicMock()})
def test_usage_plan_present_if_plan_is_there_and_needs_updates_but_test_is_set(self, *args):
'''
Tests behavior when usage plan needs to be updated by tests option is set
'''
result = {}
result = boto_apigateway.usage_plan_present('name', 'plan_name', **conn_parameters)
self.assertIn('comment', result)
self.assertEqual(result['comment'], 'a new usage plan plan_name would be updated')
self.assertIn('result', result)
self.assertEqual(result['result'], None)
boto_apigateway.__salt__['boto_apigateway.update_usage_plan'].assert_not_called()
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(return_value={'plans': [{
'id': 'planid',
'name': 'planname',
'throttle': {'rateLimit': 10.0}
}]})})
@patch.dict(boto_apigateway.__opts__, {'test': False})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.update_usage_plan': MagicMock(return_value={'error': 'error'})})
def test_usage_plan_present_if_plan_is_there_and_needs_updates_but_update_fails(self, *args):
'''
Tests error processing for the case when updating an existing usage plan fails
'''
result = {}
result = boto_apigateway.usage_plan_present('name', 'plan_name', **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], False)
self.assertIn('comment', result)
self.assertEqual(result['comment'], 'Failed to update a usage plan plan_name, error')
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(side_effect=[{'plans': []}, {'plans': [{'id': 'id'}]}])})
@patch.dict(boto_apigateway.__opts__, {'test': False})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.create_usage_plan': MagicMock(return_value={'created': True})})
# @patch.dict(boto_apigateway.__salt__, {'boto_apigateway.update_usage_plan': MagicMock(return_value={'error': 'error'})})
def test_usage_plan_present_if_plan_has_been_created(self, *args):
'''
Tests successful case for creating a new usage plan
'''
result = {}
result = boto_apigateway.usage_plan_present('name', 'plan_name', **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], True)
self.assertIn('comment', result)
self.assertEqual(result['comment'], 'A new usage plan plan_name has been created')
self.assertEqual(result['changes']['old'], {'plan': None})
self.assertEqual(result['changes']['new'], {'plan': {'id': 'id'}})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(side_effect=[{'plans': [{'id': 'id'}]},
{'plans': [{'id': 'id',
'throttle': {'rateLimit': throttle_rateLimit}}]}])})
@patch.dict(boto_apigateway.__opts__, {'test': False})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.update_usage_plan': MagicMock(return_value={'updated': True})})
def test_usage_plan_present_if_plan_has_been_updated(self, *args):
'''
Tests successful case for updating a usage plan
'''
result = {}
result = boto_apigateway.usage_plan_present('name', 'plan_name', throttle={'rateLimit': throttle_rateLimit}, **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], True)
self.assertIn('comment', result)
self.assertEqual(result['comment'], 'usage plan plan_name has been updated')
self.assertEqual(result['changes']['old'], {'plan': {'id': 'id'}})
self.assertEqual(result['changes']['new'], {'plan': {'id': 'id', 'throttle': {'rateLimit': throttle_rateLimit}}})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(side_effect=ValueError('error'))})
def test_usage_plan_present_if_ValueError_is_raised(self, *args):
'''
Tests error processing for the case when ValueError is raised when creating a usage plan
'''
result = {}
result = boto_apigateway.usage_plan_present('name', 'plan_name', throttle={'rateLimit': throttle_rateLimit}, **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], False)
self.assertIn('comment', result)
self.assertEqual(result['comment'], "('error',)")
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(side_effect=IOError('error'))})
def test_usage_plan_present_if_IOError_is_raised(self, *args):
'''
Tests error processing for the case when IOError is raised when creating a usage plan
'''
result = {}
result = boto_apigateway.usage_plan_present('name', 'plan_name', throttle={'rateLimit': throttle_rateLimit}, **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], False)
self.assertIn('comment', result)
self.assertEqual(result['comment'], "('error',)")
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(return_value={'error': 'error'})})
def test_usage_plan_absent_if_describe_fails(self, *args):
'''
Tests correct error processing for describe_usage_plan failure
'''
result = {}
result = boto_apigateway.usage_plan_absent('name', 'plan_name', **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], False)
self.assertIn('comment', result)
self.assertEqual(result['comment'], 'Failed to describe existing usage plans')
self.assertIn('changes', result)
self.assertEqual(result['changes'], {})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(return_value={'plans': []})})
def test_usage_plan_absent_if_plan_is_not_present(self, *args):
'''
Tests behavior for the case when the plan that needs to be absent does not exist
'''
result = {}
result = boto_apigateway.usage_plan_absent('name', 'plan_name', **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], True)
self.assertIn('comment', result)
self.assertEqual(result['comment'], 'Usage plan plan_name does not exist already')
self.assertIn('changes', result)
self.assertEqual(result['changes'], {})
@patch.dict(boto_apigateway.__opts__, {'test': True})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(return_value={'plans': [{'id': 'id'}]})})
def test_usage_plan_absent_if_plan_is_present_but_test_option_is_set(self, *args):
'''
Tests behavior for the case when usage plan needs to be deleted by tests option is set
'''
result = {}
result = boto_apigateway.usage_plan_absent('name', 'plan_name', **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], None)
self.assertIn('comment', result)
self.assertEqual(result['comment'], 'Usage plan plan_name exists and would be deleted')
self.assertIn('changes', result)
self.assertEqual(result['changes'], {})
@patch.dict(boto_apigateway.__opts__, {'test': False})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(return_value={'plans': [{'id': 'id'}]})})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.delete_usage_plan': MagicMock(return_value={'error': 'error'})})
def test_usage_plan_absent_if_plan_is_present_but_delete_fails(self, *args):
'''
Tests correct error processing when deleting a usage plan fails
'''
result = {}
result = boto_apigateway.usage_plan_absent('name', 'plan_name', **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], False)
self.assertIn('comment', result)
self.assertEqual(result['comment'], 'Failed to delete usage plan plan_name, {\'error\': \'error\'}')
self.assertIn('changes', result)
self.assertEqual(result['changes'], {})
@patch.dict(boto_apigateway.__opts__, {'test': False})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(return_value={'plans': [{'id': 'id'}]})})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.delete_usage_plan': MagicMock(return_value={'deleted': True})})
def test_usage_plan_absent_if_plan_has_been_deleted(self, *args):
'''
Tests successful case for deleting a usage plan
'''
result = {}
result = boto_apigateway.usage_plan_absent('name', 'plan_name', **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], True)
self.assertIn('comment', result)
self.assertEqual(result['comment'], 'Usage plan plan_name has been deleted')
self.assertIn('changes', result)
self.assertEqual(result['changes'], {'new': {'plan': None}, 'old': {'plan': {'id': 'id'}}})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(side_effect=ValueError('error'))})
def test_usage_plan_absent_if_ValueError_is_raised(self, *args):
'''
Tests correct error processing for the case when ValueError is raised when deleting a usage plan
'''
result = {}
result = boto_apigateway.usage_plan_absent('name', 'plan_name', **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], False)
self.assertIn('comment', result)
self.assertEqual(result['comment'], "('error',)")
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(side_effect=IOError('error'))})
def test_usage_plan_absent_if_IOError_is_raised(self, *args):
'''
Tests correct error processing for the case when IOError is raised when deleting a usage plan
'''
result = {}
result = boto_apigateway.usage_plan_absent('name', 'plan_name', **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], False)
self.assertIn('comment', result)
self.assertEqual(result['comment'], "('error',)")
@skipIf(HAS_BOTO is False, 'The boto module must be installed.')
@skipIf(_has_required_boto() is False, 'The boto3 module must be greater than'
' or equal to version {0}'
.format(required_boto3_version))
@skipIf(_has_required_botocore() is False,
'The botocore module must be greater than'
' or equal to version {0}'.format(required_botocore_version))
@skipIf(NO_MOCK, NO_MOCK_REASON)
class BotoApiGatewayUsagePlanAssociationTestCase(BotoApiGatewayStateTestCaseBase, BotoApiGatewayTestCaseMixin):
'''
TestCase for salt.modules.boto_apigateway state.module, usage_plans_association portion
'''
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(return_value={'error': 'error'})})
def test_usage_plan_association_present_if_describe_fails(self, *args):
'''
Tests correct error processing for describe_usage_plan failure
'''
result = {}
result = boto_apigateway.usage_plan_association_present('name', 'plan_name', [association_stage_1], **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], False)
self.assertIn('comment', result)
self.assertEqual(result['comment'], 'Failed to describe existing usage plans')
self.assertIn('changes', result)
self.assertEqual(result['changes'], {})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(return_value={'plans': []})})
def test_usage_plan_association_present_if_plan_is_not_present(self, *args):
'''
Tests correct error processing if a plan for which association has been requested is not present
'''
result = {}
result = boto_apigateway.usage_plan_association_present('name', 'plan_name', [association_stage_1], **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], False)
self.assertIn('comment', result)
self.assertEqual(result['comment'], 'Usage plan plan_name does not exist')
self.assertIn('changes', result)
self.assertEqual(result['changes'], {})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(return_value={'plans': [{'id': 'id1'},
{'id': 'id2'}]})})
def test_usage_plan_association_present_if_multiple_plans_with_the_same_name_exist(self, *args):
'''
Tests correct error processing for the case when multiple plans with the same name exist
'''
result = {}
result = boto_apigateway.usage_plan_association_present('name', 'plan_name', [association_stage_1], **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], False)
self.assertIn('comment', result)
self.assertEqual(result['comment'], 'There are multiple usage plans with the same name - it is not supported')
self.assertIn('changes', result)
self.assertEqual(result['changes'], {})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(return_value={'plans': [{'id': 'id1',
'apiStages':
[association_stage_1]}]})})
def test_usage_plan_association_present_if_association_already_exists(self, *args):
'''
Tests the behavior for the case when requested association is already present
'''
result = {}
result = boto_apigateway.usage_plan_association_present('name', 'plan_name', [association_stage_1], **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], True)
self.assertIn('comment', result)
self.assertEqual(result['comment'], 'Usage plan is already asssociated to all api stages')
self.assertIn('changes', result)
self.assertEqual(result['changes'], {})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(return_value={'plans': [{'id': 'id1',
'apiStages':
[association_stage_1]}]})})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.attach_usage_plan_to_apis': MagicMock(return_value={'error': 'error'})})
def test_usage_plan_association_present_if_update_fails(self, *args):
'''
Tests correct error processing for the case when adding associations fails
'''
result = {}
result = boto_apigateway.usage_plan_association_present('name', 'plan_name', [association_stage_2], **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], False)
self.assertIn('comment', result)
self.assertTrue(result['comment'].startswith('Failed to associate a usage plan'))
self.assertIn('changes', result)
self.assertEqual(result['changes'], {})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(return_value={'plans': [{'id': 'id1',
'apiStages':
[association_stage_1]}]})})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.attach_usage_plan_to_apis': MagicMock(return_value={'result': {'apiStages': [association_stage_1,
association_stage_2]}})})
def test_usage_plan_association_present_success(self, *args):
'''
Tests successful case for adding usage plan associations to a given api stage
'''
result = {}
result = boto_apigateway.usage_plan_association_present('name', 'plan_name', [association_stage_2], **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], True)
self.assertIn('comment', result)
self.assertEqual(result['comment'], 'successfully associated usage plan to apis')
self.assertIn('changes', result)
self.assertEqual(result['changes'], {'new': [association_stage_1, association_stage_2], 'old': [association_stage_1]})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(side_effect=ValueError('error'))})
def test_usage_plan_association_present_if_value_error_is_thrown(self, *args):
'''
Tests correct error processing for the case when IOError is raised while trying to set usage plan associations
'''
result = {}
result = boto_apigateway.usage_plan_association_present('name', 'plan_name', [], **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], False)
self.assertIn('comment', result)
self.assertEqual(result['comment'], "('error',)")
self.assertIn('changes', result)
self.assertEqual(result['changes'], {})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(side_effect=IOError('error'))})
def test_usage_plan_association_present_if_io_error_is_thrown(self, *args):
'''
Tests correct error processing for the case when IOError is raised while trying to set usage plan associations
'''
result = {}
result = boto_apigateway.usage_plan_association_present('name', 'plan_name', [], **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], False)
self.assertIn('comment', result)
self.assertEqual(result['comment'], "('error',)")
self.assertIn('changes', result)
self.assertEqual(result['changes'], {})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(return_value={'error': 'error'})})
def test_usage_plan_association_absent_if_describe_fails(self, *args):
'''
Tests correct error processing for describe_usage_plan failure
'''
result = {}
result = boto_apigateway.usage_plan_association_absent('name', 'plan_name', [association_stage_1], **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], False)
self.assertIn('comment', result)
self.assertEqual(result['comment'], 'Failed to describe existing usage plans')
self.assertIn('changes', result)
self.assertEqual(result['changes'], {})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(return_value={'plans': []})})
def test_usage_plan_association_absent_if_plan_is_not_present(self, *args):
'''
Tests error processing for the case when plan for which associations need to be modified is not present
'''
result = {}
result = boto_apigateway.usage_plan_association_absent('name', 'plan_name', [association_stage_1], **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], False)
self.assertIn('comment', result)
self.assertEqual(result['comment'], 'Usage plan plan_name does not exist')
self.assertIn('changes', result)
self.assertEqual(result['changes'], {})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(return_value={'plans': [{'id': 'id1'},
{'id': 'id2'}]})})
def test_usage_plan_association_absent_if_multiple_plans_with_the_same_name_exist(self, *args):
'''
Tests the case when there are multiple plans with the same name but different Ids
'''
result = {}
result = boto_apigateway.usage_plan_association_absent('name', 'plan_name', [association_stage_1], **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], False)
self.assertIn('comment', result)
self.assertEqual(result['comment'], 'There are multiple usage plans with the same name - it is not supported')
self.assertIn('changes', result)
self.assertEqual(result['changes'], {})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(return_value={'plans': [{'id': 'id1', 'apiStages': []}]})})
def test_usage_plan_association_absent_if_plan_has_no_associations(self, *args):
'''
Tests the case when the plan has no associations at all
'''
result = {}
result = boto_apigateway.usage_plan_association_absent('name', 'plan_name', [association_stage_1], **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], True)
self.assertIn('comment', result)
self.assertEqual(result['comment'], 'Usage plan plan_name has no associated stages already')
self.assertIn('changes', result)
self.assertEqual(result['changes'], {})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(return_value={'plans': [{'id': 'id1', 'apiStages': [association_stage_1]}]})})
def test_usage_plan_association_absent_if_plan_has_no_specific_association(self, *args):
'''
Tests the case when requested association is not present already
'''
result = {}
result = boto_apigateway.usage_plan_association_absent('name', 'plan_name', [association_stage_2], **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], True)
self.assertIn('comment', result)
self.assertEqual(result['comment'], 'Usage plan is already not asssociated to any api stages')
self.assertIn('changes', result)
self.assertEqual(result['changes'], {})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(return_value={'plans': [{'id': 'id1', 'apiStages': [association_stage_1,
association_stage_2]}]})})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.detach_usage_plan_from_apis': MagicMock(return_value={'error': 'error'})})
def test_usage_plan_association_absent_if_detaching_association_fails(self, *args):
'''
Tests correct error processing when detaching the usage plan from the api function is called
'''
result = {}
result = boto_apigateway.usage_plan_association_absent('name', 'plan_name', [association_stage_2], **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], False)
self.assertIn('comment', result)
self.assertTrue(result['comment'].startswith('Failed to disassociate a usage plan plan_name from the apis'))
self.assertIn('changes', result)
self.assertEqual(result['changes'], {})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(return_value={'plans': [{'id': 'id1', 'apiStages': [association_stage_1,
association_stage_2]}]})})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.detach_usage_plan_from_apis': MagicMock(return_value={'result': {'apiStages': [association_stage_1]}})})
def test_usage_plan_association_absent_success(self, *args):
'''
Tests successful case of disaccosiation the usage plan from api stages
'''
result = {}
result = boto_apigateway.usage_plan_association_absent('name', 'plan_name', [association_stage_2], **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], True)
self.assertIn('comment', result)
self.assertEqual(result['comment'], 'successfully disassociated usage plan from apis')
self.assertIn('changes', result)
self.assertEqual(result['changes'], {'new': [association_stage_1], 'old': [association_stage_1, association_stage_2]})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(side_effect=ValueError('error'))})
def test_usage_plan_association_absent_if_ValueError_is_raised(self, *args):
'''
Tests correct error processing for the case where ValueError is raised while trying to remove plan associations
'''
result = {}
result = boto_apigateway.usage_plan_association_absent('name', 'plan_name', [association_stage_1], **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], False)
self.assertIn('comment', result)
self.assertEqual(result['comment'], "('error',)")
self.assertIn('changes', result)
self.assertEqual(result['changes'], {})
@patch.dict(boto_apigateway.__salt__, {'boto_apigateway.describe_usage_plans': MagicMock(side_effect=IOError('error'))})
def test_usage_plan_association_absent_if_IOError_is_raised(self, *args):
'''
Tests correct error processing for the case where IOError exception is raised while trying to remove plan associations
'''
result = {}
result = boto_apigateway.usage_plan_association_absent('name', 'plan_name', [association_stage_1], **conn_parameters)
self.assertIn('result', result)
self.assertEqual(result['result'], False)
self.assertIn('comment', result)
self.assertEqual(result['comment'], "('error',)")
self.assertIn('changes', result)
self.assertEqual(result['changes'], {})