Merge remote-tracking branch 'spulec/master'

This commit is contained in:
Alexander Mohr 2017-10-10 13:10:46 -07:00
commit 5fe5efdb2d
21 changed files with 1330 additions and 127 deletions

View File

@ -3,6 +3,12 @@ Moto Changelog
Latest Latest
------ ------
1.1.21
-----
* ELBv2 bugfixes
* Removing GPL'd dependency
1.1.20 1.1.20
----- -----

View File

@ -94,7 +94,8 @@ It gets even better! Moto isn't just for Python code and it isn't just for S3. L
|------------------------------------------------------------------------------| |------------------------------------------------------------------------------|
| IAM | @mock_iam | core endpoints done | | IAM | @mock_iam | core endpoints done |
|------------------------------------------------------------------------------| |------------------------------------------------------------------------------|
| Lambda | @mock_lambda | basic endpoints done | | Lambda | @mock_lambda | basic endpoints done, requires |
| | | docker |
|------------------------------------------------------------------------------| |------------------------------------------------------------------------------|
| Logs | @mock_logs | basic endpoints done | | Logs | @mock_logs | basic endpoints done |
|------------------------------------------------------------------------------| |------------------------------------------------------------------------------|

View File

@ -132,6 +132,7 @@ class LambdaFunction(BaseModel):
self.logs_backend = logs_backends[self.region] self.logs_backend = logs_backends[self.region]
self.environment_vars = spec.get('Environment', {}).get('Variables', {}) self.environment_vars = spec.get('Environment', {}).get('Variables', {})
self.docker_client = docker.from_env() self.docker_client = docker.from_env()
self.policy = ""
# Unfortunately mocking replaces this method w/o fallback enabled, so we # Unfortunately mocking replaces this method w/o fallback enabled, so we
# need to replace it if we detect it's been mocked # need to replace it if we detect it's been mocked
@ -527,6 +528,9 @@ class LambdaBackend(BaseBackend):
pass pass
# Don't care # Don't care
def add_policy(self, function_name, policy):
self.get_function(function_name).policy = policy
def do_validate_s3(): def do_validate_s3():
return os.environ.get('VALIDATE_LAMBDA_S3', '') in ['', '1', 'true'] return os.environ.get('VALIDATE_LAMBDA_S3', '') in ['', '1', 'true']

View File

@ -57,6 +57,35 @@ class LambdaResponse(BaseResponse):
else: else:
raise ValueError("Cannot handle {0} request".format(request.method)) raise ValueError("Cannot handle {0} request".format(request.method))
def policy(self, request, full_url, headers):
if request.method == 'GET':
return self._get_policy(request, full_url, headers)
if request.method == 'POST':
return self._add_policy(request, full_url, headers)
def _add_policy(self, request, full_url, headers):
lambda_backend = self.get_lambda_backend(full_url)
path = request.path if hasattr(request, 'path') else request.path_url
function_name = path.split('/')[-2]
if lambda_backend.has_function(function_name):
policy = request.body.decode('utf8')
lambda_backend.add_policy(function_name, policy)
return 200, {}, json.dumps(dict(Statement=policy))
else:
return 404, {}, "{}"
def _get_policy(self, request, full_url, headers):
lambda_backend = self.get_lambda_backend(full_url)
path = request.path if hasattr(request, 'path') else request.path_url
function_name = path.split('/')[-2]
if lambda_backend.has_function(function_name):
function = lambda_backend.get_function(function_name)
return 200, {}, json.dumps(dict(Policy="{\"Statement\":[" + function.policy + "]}"))
else:
return 404, {}, "{}"
def _invoke(self, request, full_url): def _invoke(self, request, full_url):
response_headers = {} response_headers = {}
lambda_backend = self.get_lambda_backend(full_url) lambda_backend = self.get_lambda_backend(full_url)

View File

@ -12,5 +12,6 @@ url_paths = {
r'{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_-]+)/?$': response.function, r'{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_-]+)/?$': response.function,
r'{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_-]+)/invocations/?$': response.invoke, r'{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_-]+)/invocations/?$': response.invoke,
r'{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_-]+)/invoke-async/?$': response.invoke_async, r'{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_-]+)/invoke-async/?$': response.invoke_async,
r'{0}/(?P<api_version>[^/]+)/tags/(?P<resource_arn>.+)': response.tag r'{0}/(?P<api_version>[^/]+)/tags/(?P<resource_arn>.+)': response.tag,
r'{0}/(?P<api_version>[^/]+)/functions/(?P<function_name>[\w_-]+)/policy/?$': response.policy
} }

View File

@ -1,4 +1,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import re
import six
# TODO add tests for all of these # TODO add tests for all of these
EQ_FUNCTION = lambda item_value, test_value: item_value == test_value # flake8: noqa EQ_FUNCTION = lambda item_value, test_value: item_value == test_value # flake8: noqa
@ -39,3 +41,452 @@ COMPARISON_FUNCS = {
def get_comparison_func(range_comparison): def get_comparison_func(range_comparison):
return COMPARISON_FUNCS.get(range_comparison) return COMPARISON_FUNCS.get(range_comparison)
#
def get_filter_expression(expr, names, values):
# Examples
# expr = 'Id > 5 AND attribute_exists(test) AND Id BETWEEN 5 AND 6 OR length < 6 AND contains(test, 1) AND 5 IN (4,5, 6) OR (Id < 5 AND 5 > Id)'
# expr = 'Id > 5 AND Subs < 7'
# Need to do some dodgyness for NOT i think.
if 'NOT' in expr:
raise NotImplementedError('NOT not supported yet')
if names is None:
names = {}
if values is None:
values = {}
# Do substitutions
for key, value in names.items():
expr = expr.replace(key, value)
for key, value in values.items():
if 'N' in value:
expr.replace(key, float(value['N']))
else:
expr = expr.replace(key, value['S'])
# Remove all spaces, tbf we could just skip them in the next step.
# The number of known options is really small so we can do a fair bit of cheating
#expr = list(re.sub('\s', '', expr)) # 'Id>5ANDattribute_exists(test)ORNOTlength<6'
expr = list(expr)
# DodgyTokenisation stage 1
def is_value(val):
return val not in ('<', '>', '=', '(', ')')
def contains_keyword(val):
for kw in ('BETWEEN', 'IN', 'AND', 'OR', 'NOT'):
if kw in val:
return kw
return None
def is_function(val):
return val in ('attribute_exists', 'attribute_not_exists', 'attribute_type', 'begins_with', 'contains', 'size')
# Does the main part of splitting between sections of characters
tokens = []
stack = ''
while len(expr) > 0:
current_char = expr.pop(0)
if current_char == ' ':
if len(stack) > 0:
tokens.append(stack)
stack = ''
elif current_char == ',': # Split params ,
if len(stack) > 0:
tokens.append(stack)
stack = ''
elif is_value(current_char):
stack += current_char
kw = contains_keyword(stack)
if kw is not None:
# We have a kw in the stack, could be AND or something like 5AND
tmp = stack.replace(kw, '')
if len(tmp) > 0:
tokens.append(tmp)
tokens.append(kw)
stack = ''
else:
if len(stack) > 0:
tokens.append(stack)
tokens.append(current_char)
stack = ''
if len(stack) > 0:
tokens.append(stack)
def is_op(val):
return val in ('<', '>', '=', '>=', '<=', '<>', 'BETWEEN', 'IN', 'AND', 'OR', 'NOT')
# DodgyTokenisation stage 2, it groups together some elements to make RPN'ing it later easier.
tokens2 = []
token_iterator = iter(tokens)
for token in token_iterator:
if token == '(':
tuple_list = []
next_token = six.next(token_iterator)
while next_token != ')':
try:
next_token = int(next_token)
except ValueError:
try:
next_token = float(next_token)
except ValueError:
pass
tuple_list.append(next_token)
next_token = six.next(token_iterator)
# Sigh, we only want to group a tuple if it doesnt contain operators
if any([is_op(item) for item in tuple_list]):
tokens2.append('(')
tokens2.extend(tuple_list)
tokens2.append(')')
else:
tokens2.append(tuple(tuple_list))
elif token == 'BETWEEN':
field = tokens2.pop()
op1 = int(six.next(token_iterator))
and_op = six.next(token_iterator)
assert and_op == 'AND'
op2 = int(six.next(token_iterator))
tokens2.append(['between', field, op1, op2])
elif is_function(token):
function_list = [token]
lbracket = six.next(token_iterator)
assert lbracket == '('
next_token = six.next(token_iterator)
while next_token != ')':
function_list.append(next_token)
next_token = six.next(token_iterator)
tokens2.append(function_list)
else:
try:
token = int(token)
except ValueError:
try:
token = float(token)
except ValueError:
pass
tokens2.append(token)
# Start of the Shunting-Yard algorithm. <-- Proper beast algorithm!
def is_number(val):
return val not in ('<', '>', '=', '>=', '<=', '<>', 'BETWEEN', 'IN', 'AND', 'OR', 'NOT')
OPS = {'<': 5, '>': 5, '=': 5, '>=': 5, '<=': 5, '<>': 5, 'IN': 8, 'AND': 11, 'OR': 12, 'NOT': 10, 'BETWEEN': 9, '(': 100, ')': 100}
def shunting_yard(token_list):
output = []
op_stack = []
# Basically takes in an infix notation calculation, converts it to a reverse polish notation where there is no
# ambiguity on which order operators are applied.
while len(token_list) > 0:
token = token_list.pop(0)
if token == '(':
op_stack.append(token)
elif token == ')':
while len(op_stack) > 0 and op_stack[-1] != '(':
output.append(op_stack.pop())
lbracket = op_stack.pop()
assert lbracket == '('
elif is_number(token):
output.append(token)
else:
# Must be operator kw
while len(op_stack) > 0 and OPS[op_stack[-1]] <= OPS[token]:
output.append(op_stack.pop())
op_stack.append(token)
while len(op_stack) > 0:
output.append(op_stack.pop())
return output
output = shunting_yard(tokens2)
# Hacky function to convert dynamo functions (which are represented as lists) to their Class equivalent
def to_func(val):
if isinstance(val, list):
func_name = val.pop(0)
# Expand rest of the list to arguments
val = FUNC_CLASS[func_name](*val)
return val
# Simple reverse polish notation execution. Builts up a nested filter object.
# The filter object then takes a dynamo item and returns true/false
stack = []
for token in output:
if is_op(token):
op2 = stack.pop()
op1 = stack.pop()
op_cls = OP_CLASS[token]
stack.append(op_cls(op1, op2))
else:
stack.append(to_func(token))
result = stack.pop(0)
if len(stack) > 0:
raise ValueError('Malformed filter expression')
return result
class Op(object):
"""
Base class for a FilterExpression operator
"""
OP = ''
def __init__(self, lhs, rhs):
self.lhs = lhs
self.rhs = rhs
def _lhs(self, item):
"""
:type item: moto.dynamodb2.models.Item
"""
lhs = self.lhs
if isinstance(self.lhs, (Op, Func)):
lhs = self.lhs.expr(item)
elif isinstance(self.lhs, six.string_types):
try:
lhs = item.attrs[self.lhs].cast_value
except Exception:
pass
return lhs
def _rhs(self, item):
rhs = self.rhs
if isinstance(self.rhs, (Op, Func)):
rhs = self.rhs.expr(item)
elif isinstance(self.rhs, six.string_types):
try:
rhs = item.attrs[self.rhs].cast_value
except Exception:
pass
return rhs
def expr(self, item):
return True
def __repr__(self):
return '({0} {1} {2})'.format(self.lhs, self.OP, self.rhs)
class Func(object):
"""
Base class for a FilterExpression function
"""
FUNC = 'Unknown'
def expr(self, item):
return True
def __repr__(self):
return 'Func(...)'.format(self.FUNC)
class OpAnd(Op):
OP = 'AND'
def expr(self, item):
lhs = self._lhs(item)
rhs = self._rhs(item)
return lhs and rhs
class OpLessThan(Op):
OP = '<'
def expr(self, item):
lhs = self._lhs(item)
rhs = self._rhs(item)
return lhs < rhs
class OpGreaterThan(Op):
OP = '>'
def expr(self, item):
lhs = self._lhs(item)
rhs = self._rhs(item)
return lhs > rhs
class OpEqual(Op):
OP = '='
def expr(self, item):
lhs = self._lhs(item)
rhs = self._rhs(item)
return lhs == rhs
class OpNotEqual(Op):
OP = '<>'
def expr(self, item):
lhs = self._lhs(item)
rhs = self._rhs(item)
return lhs == rhs
class OpLessThanOrEqual(Op):
OP = '<='
def expr(self, item):
lhs = self._lhs(item)
rhs = self._rhs(item)
return lhs <= rhs
class OpGreaterThanOrEqual(Op):
OP = '>='
def expr(self, item):
lhs = self._lhs(item)
rhs = self._rhs(item)
return lhs >= rhs
class OpOr(Op):
OP = 'OR'
def expr(self, item):
lhs = self._lhs(item)
rhs = self._rhs(item)
return lhs or rhs
class OpIn(Op):
OP = 'IN'
def expr(self, item):
lhs = self._lhs(item)
rhs = self._rhs(item)
return lhs in rhs
class FuncAttrExists(Func):
FUNC = 'attribute_exists'
def __init__(self, attribute):
self.attr = attribute
def expr(self, item):
return self.attr in item.attrs
class FuncAttrNotExists(Func):
FUNC = 'attribute_not_exists'
def __init__(self, attribute):
self.attr = attribute
def expr(self, item):
return self.attr not in item.attrs
class FuncAttrType(Func):
FUNC = 'attribute_type'
def __init__(self, attribute, _type):
self.attr = attribute
self.type = _type
def expr(self, item):
return self.attr in item.attrs and item.attrs[self.attr].type == self.type
class FuncBeginsWith(Func):
FUNC = 'begins_with'
def __init__(self, attribute, substr):
self.attr = attribute
self.substr = substr
def expr(self, item):
return self.attr in item.attrs and item.attrs[self.attr].type == 'S' and item.attrs[self.attr].value.startswith(self.substr)
class FuncContains(Func):
FUNC = 'contains'
def __init__(self, attribute, operand):
self.attr = attribute
self.operand = operand
def expr(self, item):
if self.attr not in item.attrs:
return False
if item.attrs[self.attr].type in ('S', 'SS', 'NS', 'BS', 'L', 'M'):
return self.operand in item.attrs[self.attr].value
return False
class FuncSize(Func):
FUNC = 'contains'
def __init__(self, attribute):
self.attr = attribute
def expr(self, item):
if self.attr not in item.attrs:
raise ValueError('Invalid attribute name {0}'.format(self.attr))
if item.attrs[self.attr].type in ('S', 'SS', 'NS', 'B', 'BS', 'L', 'M'):
return len(item.attrs[self.attr].value)
raise ValueError('Invalid filter expression')
class FuncBetween(Func):
FUNC = 'between'
def __init__(self, attribute, start, end):
self.attr = attribute
self.start = start
self.end = end
def expr(self, item):
if self.attr not in item.attrs:
raise ValueError('Invalid attribute name {0}'.format(self.attr))
return self.start <= item.attrs[self.attr].cast_value <= self.end
OP_CLASS = {
'AND': OpAnd,
'OR': OpOr,
'IN': OpIn,
'<': OpLessThan,
'>': OpGreaterThan,
'<=': OpLessThanOrEqual,
'>=': OpGreaterThanOrEqual,
'=': OpEqual,
'<>': OpNotEqual
}
FUNC_CLASS = {
'attribute_exists': FuncAttrExists,
'attribute_not_exists': FuncAttrNotExists,
'attribute_type': FuncAttrType,
'begins_with': FuncBeginsWith,
'contains': FuncContains,
'size': FuncSize,
'between': FuncBetween
}

View File

@ -8,7 +8,7 @@ import re
from moto.compat import OrderedDict from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel from moto.core import BaseBackend, BaseModel
from moto.core.utils import unix_time from moto.core.utils import unix_time
from .comparisons import get_comparison_func from .comparisons import get_comparison_func, get_filter_expression, Op
class DynamoJsonEncoder(json.JSONEncoder): class DynamoJsonEncoder(json.JSONEncoder):
@ -508,15 +508,15 @@ class Table(BaseModel):
else: else:
yield hash_set yield hash_set
def scan(self, filters, limit, exclusive_start_key): def scan(self, filters, limit, exclusive_start_key, filter_expression=None):
results = [] results = []
scanned_count = 0 scanned_count = 0
for result in self.all_items(): for item in self.all_items():
scanned_count += 1 scanned_count += 1
passes_all_conditions = True passes_all_conditions = True
for attribute_name, (comparison_operator, comparison_objs) in filters.items(): for attribute_name, (comparison_operator, comparison_objs) in filters.items():
attribute = result.attrs.get(attribute_name) attribute = item.attrs.get(attribute_name)
if attribute: if attribute:
# Attribute found # Attribute found
@ -532,8 +532,11 @@ class Table(BaseModel):
passes_all_conditions = False passes_all_conditions = False
break break
if filter_expression is not None:
passes_all_conditions &= filter_expression.expr(item)
if passes_all_conditions: if passes_all_conditions:
results.append(result) results.append(item)
results, last_evaluated_key = self._trim_results(results, limit, results, last_evaluated_key = self._trim_results(results, limit,
exclusive_start_key) exclusive_start_key)
@ -698,7 +701,7 @@ class DynamoDBBackend(BaseBackend):
return table.query(hash_key, range_comparison, range_values, limit, return table.query(hash_key, range_comparison, range_values, limit,
exclusive_start_key, scan_index_forward, projection_expression, index_name, **filter_kwargs) exclusive_start_key, scan_index_forward, projection_expression, index_name, **filter_kwargs)
def scan(self, table_name, filters, limit, exclusive_start_key): def scan(self, table_name, filters, limit, exclusive_start_key, filter_expression, expr_names, expr_values):
table = self.tables.get(table_name) table = self.tables.get(table_name)
if not table: if not table:
return None, None, None return None, None, None
@ -708,7 +711,12 @@ class DynamoDBBackend(BaseBackend):
dynamo_types = [DynamoType(value) for value in comparison_values] dynamo_types = [DynamoType(value) for value in comparison_values]
scan_filters[key] = (comparison_operator, dynamo_types) scan_filters[key] = (comparison_operator, dynamo_types)
return table.scan(scan_filters, limit, exclusive_start_key) if filter_expression is not None:
filter_expression = get_filter_expression(filter_expression, expr_names, expr_values)
else:
filter_expression = Op(None, None) # Will always eval to true
return table.scan(scan_filters, limit, exclusive_start_key, filter_expression)
def update_item(self, table_name, key, update_expression, attribute_updates, expression_attribute_names, def update_item(self, table_name, key, update_expression, attribute_updates, expression_attribute_names,
expression_attribute_values, expected=None): expression_attribute_values, expected=None):

View File

@ -432,13 +432,29 @@ class DynamoHandler(BaseResponse):
comparison_values = scan_filter.get("AttributeValueList", []) comparison_values = scan_filter.get("AttributeValueList", [])
filters[attribute_name] = (comparison_operator, comparison_values) filters[attribute_name] = (comparison_operator, comparison_values)
filter_expression = self.body.get('FilterExpression')
expression_attribute_values = self.body.get('ExpressionAttributeValues', {})
expression_attribute_names = self.body.get('ExpressionAttributeNames', {})
exclusive_start_key = self.body.get('ExclusiveStartKey') exclusive_start_key = self.body.get('ExclusiveStartKey')
limit = self.body.get("Limit") limit = self.body.get("Limit")
try:
items, scanned_count, last_evaluated_key = dynamodb_backend2.scan(name, filters, items, scanned_count, last_evaluated_key = dynamodb_backend2.scan(name, filters,
limit, limit,
exclusive_start_key) exclusive_start_key,
filter_expression,
expression_attribute_names,
expression_attribute_values)
except ValueError as err:
er = 'com.amazonaws.dynamodb.v20111205#ValidationError'
return self.error(er, 'Bad Filter Expression: {0}'.format(err))
except Exception as err:
er = 'com.amazonaws.dynamodb.v20111205#InternalFailure'
return self.error(er, 'Internal error. {0}'.format(err))
# Items should be a list, at least an empty one. Is None if table does not exist.
# Should really check this at the beginning
if items is None: if items is None:
er = 'com.amazonaws.dynamodb.v20111205#ResourceNotFoundException' er = 'com.amazonaws.dynamodb.v20111205#ResourceNotFoundException'
return self.error(er, 'Requested resource not found') return self.error(er, 'Requested resource not found')

View File

@ -109,6 +109,7 @@ from .utils import (
random_vpn_connection_id, random_vpn_connection_id,
random_customer_gateway_id, random_customer_gateway_id,
is_tag_filter, is_tag_filter,
tag_filter_matches,
) )
RESOURCES_DIR = os.path.join(os.path.dirname(__file__), 'resources') RESOURCES_DIR = os.path.join(os.path.dirname(__file__), 'resources')
@ -374,6 +375,7 @@ class Instance(TaggedEC2Resource, BotoInstance):
self.source_dest_check = "true" self.source_dest_check = "true"
self.launch_time = utc_date_and_time() self.launch_time = utc_date_and_time()
self.disable_api_termination = kwargs.get("disable_api_termination", False) self.disable_api_termination = kwargs.get("disable_api_termination", False)
self._spot_fleet_id = kwargs.get("spot_fleet_id", None)
associate_public_ip = kwargs.get("associate_public_ip", False) associate_public_ip = kwargs.get("associate_public_ip", False)
if in_ec2_classic: if in_ec2_classic:
# If we are in EC2-Classic, autoassign a public IP # If we are in EC2-Classic, autoassign a public IP
@ -511,6 +513,14 @@ class Instance(TaggedEC2Resource, BotoInstance):
self.teardown_defaults() self.teardown_defaults()
if self._spot_fleet_id:
spot_fleet = self.ec2_backend.get_spot_fleet_request(self._spot_fleet_id)
for spec in spot_fleet.launch_specs:
if spec.instance_type == self.instance_type and spec.subnet_id == self.subnet_id:
break
spot_fleet.fulfilled_capacity -= spec.weighted_capacity
spot_fleet.spot_requests = [req for req in spot_fleet.spot_requests if req.instance != self]
self._state.name = "terminated" self._state.name = "terminated"
self._state.code = 48 self._state.code = 48
@ -1300,7 +1310,7 @@ class SecurityGroup(TaggedEC2Resource):
elif is_tag_filter(key): elif is_tag_filter(key):
tag_value = self.get_filter_value(key) tag_value = self.get_filter_value(key)
if isinstance(filter_value, list): if isinstance(filter_value, list):
return any(v in tag_value for v in filter_value) return tag_filter_matches(self, key, filter_value)
return tag_value in filter_value return tag_value in filter_value
else: else:
attr_name = to_attr(key) attr_name = to_attr(key)
@ -2623,7 +2633,7 @@ class SpotInstanceRequest(BotoSpotRequest, TaggedEC2Resource):
def __init__(self, ec2_backend, spot_request_id, price, image_id, type, def __init__(self, ec2_backend, spot_request_id, price, image_id, type,
valid_from, valid_until, launch_group, availability_zone_group, valid_from, valid_until, launch_group, availability_zone_group,
key_name, security_groups, user_data, instance_type, placement, key_name, security_groups, user_data, instance_type, placement,
kernel_id, ramdisk_id, monitoring_enabled, subnet_id, kernel_id, ramdisk_id, monitoring_enabled, subnet_id, spot_fleet_id,
**kwargs): **kwargs):
super(SpotInstanceRequest, self).__init__(**kwargs) super(SpotInstanceRequest, self).__init__(**kwargs)
ls = LaunchSpecification() ls = LaunchSpecification()
@ -2646,6 +2656,7 @@ class SpotInstanceRequest(BotoSpotRequest, TaggedEC2Resource):
ls.placement = placement ls.placement = placement
ls.monitored = monitoring_enabled ls.monitored = monitoring_enabled
ls.subnet_id = subnet_id ls.subnet_id = subnet_id
self.spot_fleet_id = spot_fleet_id
if security_groups: if security_groups:
for group_name in security_groups: for group_name in security_groups:
@ -2678,6 +2689,7 @@ class SpotInstanceRequest(BotoSpotRequest, TaggedEC2Resource):
key_name=self.launch_specification.key_name, key_name=self.launch_specification.key_name,
security_group_names=[], security_group_names=[],
security_group_ids=self.launch_specification.groups, security_group_ids=self.launch_specification.groups,
spot_fleet_id=self.spot_fleet_id,
) )
instance = reservation.instances[0] instance = reservation.instances[0]
return instance return instance
@ -2693,7 +2705,7 @@ class SpotRequestBackend(object):
valid_until, launch_group, availability_zone_group, valid_until, launch_group, availability_zone_group,
key_name, security_groups, user_data, key_name, security_groups, user_data,
instance_type, placement, kernel_id, ramdisk_id, instance_type, placement, kernel_id, ramdisk_id,
monitoring_enabled, subnet_id): monitoring_enabled, subnet_id, spot_fleet_id=None):
requests = [] requests = []
for _ in range(count): for _ in range(count):
spot_request_id = random_spot_request_id() spot_request_id = random_spot_request_id()
@ -2701,7 +2713,7 @@ class SpotRequestBackend(object):
spot_request_id, price, image_id, type, valid_from, valid_until, spot_request_id, price, image_id, type, valid_from, valid_until,
launch_group, availability_zone_group, key_name, security_groups, launch_group, availability_zone_group, key_name, security_groups,
user_data, instance_type, placement, kernel_id, ramdisk_id, user_data, instance_type, placement, kernel_id, ramdisk_id,
monitoring_enabled, subnet_id) monitoring_enabled, subnet_id, spot_fleet_id)
self.spot_instance_requests[spot_request_id] = request self.spot_instance_requests[spot_request_id] = request
requests.append(request) requests.append(request)
return requests return requests
@ -2747,7 +2759,7 @@ class SpotFleetRequest(TaggedEC2Resource):
self.iam_fleet_role = iam_fleet_role self.iam_fleet_role = iam_fleet_role
self.allocation_strategy = allocation_strategy self.allocation_strategy = allocation_strategy
self.state = "active" self.state = "active"
self.fulfilled_capacity = self.target_capacity self.fulfilled_capacity = 0.0
self.launch_specs = [] self.launch_specs = []
for spec in launch_specs: for spec in launch_specs:
@ -2768,7 +2780,7 @@ class SpotFleetRequest(TaggedEC2Resource):
) )
self.spot_requests = [] self.spot_requests = []
self.create_spot_requests() self.create_spot_requests(self.target_capacity)
@property @property
def physical_resource_id(self): def physical_resource_id(self):
@ -2798,31 +2810,32 @@ class SpotFleetRequest(TaggedEC2Resource):
return spot_fleet_request return spot_fleet_request
def get_launch_spec_counts(self): def get_launch_spec_counts(self, weight_to_add):
weight_map = defaultdict(int) weight_map = defaultdict(int)
if self.allocation_strategy == 'diversified':
weight_so_far = 0 weight_so_far = 0
if self.allocation_strategy == 'diversified':
launch_spec_index = 0 launch_spec_index = 0
while True: while True:
launch_spec = self.launch_specs[ launch_spec = self.launch_specs[
launch_spec_index % len(self.launch_specs)] launch_spec_index % len(self.launch_specs)]
weight_map[launch_spec] += 1 weight_map[launch_spec] += 1
weight_so_far += launch_spec.weighted_capacity weight_so_far += launch_spec.weighted_capacity
if weight_so_far >= self.target_capacity: if weight_so_far >= weight_to_add:
break break
launch_spec_index += 1 launch_spec_index += 1
else: # lowestPrice else: # lowestPrice
cheapest_spec = sorted( cheapest_spec = sorted(
self.launch_specs, key=lambda spec: float(spec.spot_price))[0] self.launch_specs, key=lambda spec: float(spec.spot_price))[0]
extra = 1 if self.target_capacity % cheapest_spec.weighted_capacity else 0 weight_so_far = weight_to_add + (weight_to_add % cheapest_spec.weighted_capacity)
weight_map[cheapest_spec] = int( weight_map[cheapest_spec] = int(
self.target_capacity // cheapest_spec.weighted_capacity) + extra weight_so_far // cheapest_spec.weighted_capacity)
return weight_map.items() return weight_map, weight_so_far
def create_spot_requests(self): def create_spot_requests(self, weight_to_add):
for launch_spec, count in self.get_launch_spec_counts(): weight_map, added_weight = self.get_launch_spec_counts(weight_to_add)
for launch_spec, count in weight_map.items():
requests = self.ec2_backend.request_spot_instances( requests = self.ec2_backend.request_spot_instances(
price=launch_spec.spot_price, price=launch_spec.spot_price,
image_id=launch_spec.image_id, image_id=launch_spec.image_id,
@ -2841,12 +2854,28 @@ class SpotFleetRequest(TaggedEC2Resource):
ramdisk_id=None, ramdisk_id=None,
monitoring_enabled=launch_spec.monitoring, monitoring_enabled=launch_spec.monitoring,
subnet_id=launch_spec.subnet_id, subnet_id=launch_spec.subnet_id,
spot_fleet_id=self.id,
) )
self.spot_requests.extend(requests) self.spot_requests.extend(requests)
self.fulfilled_capacity += added_weight
return self.spot_requests return self.spot_requests
def terminate_instances(self): def terminate_instances(self):
pass instance_ids = []
new_fulfilled_capacity = self.fulfilled_capacity
for req in self.spot_requests:
instance = req.instance
for spec in self.launch_specs:
if spec.instance_type == instance.instance_type and spec.subnet_id == instance.subnet_id:
break
if new_fulfilled_capacity - spec.weighted_capacity < self.target_capacity:
continue
new_fulfilled_capacity -= spec.weighted_capacity
instance_ids.append(instance.id)
self.spot_requests = [req for req in self.spot_requests if req.instance.id not in instance_ids]
self.ec2_backend.terminate_instances(instance_ids)
class SpotFleetBackend(object): class SpotFleetBackend(object):
@ -2882,12 +2911,26 @@ class SpotFleetBackend(object):
def cancel_spot_fleet_requests(self, spot_fleet_request_ids, terminate_instances): def cancel_spot_fleet_requests(self, spot_fleet_request_ids, terminate_instances):
spot_requests = [] spot_requests = []
for spot_fleet_request_id in spot_fleet_request_ids: for spot_fleet_request_id in spot_fleet_request_ids:
spot_fleet = self.spot_fleet_requests.pop(spot_fleet_request_id) spot_fleet = self.spot_fleet_requests[spot_fleet_request_id]
if terminate_instances: if terminate_instances:
spot_fleet.target_capacity = 0
spot_fleet.terminate_instances() spot_fleet.terminate_instances()
spot_requests.append(spot_fleet) spot_requests.append(spot_fleet)
del self.spot_fleet_requests[spot_fleet_request_id]
return spot_requests return spot_requests
def modify_spot_fleet_request(self, spot_fleet_request_id, target_capacity, terminate_instances):
if target_capacity < 0:
raise ValueError('Cannot reduce spot fleet capacity below 0')
spot_fleet_request = self.spot_fleet_requests[spot_fleet_request_id]
delta = target_capacity - spot_fleet_request.fulfilled_capacity
spot_fleet_request.target_capacity = target_capacity
if delta > 0:
spot_fleet_request.create_spot_requests(delta)
elif delta < 0 and terminate_instances == 'Default':
spot_fleet_request.terminate_instances()
return True
class ElasticAddress(object): class ElasticAddress(object):
def __init__(self, domain): def __init__(self, domain):

View File

@ -29,6 +29,15 @@ class SpotFleets(BaseResponse):
template = self.response_template(DESCRIBE_SPOT_FLEET_TEMPLATE) template = self.response_template(DESCRIBE_SPOT_FLEET_TEMPLATE)
return template.render(requests=requests) return template.render(requests=requests)
def modify_spot_fleet_request(self):
spot_fleet_request_id = self._get_param("SpotFleetRequestId")
target_capacity = self._get_int_param("TargetCapacity")
terminate_instances = self._get_param("ExcessCapacityTerminationPolicy", if_none="Default")
successful = self.ec2_backend.modify_spot_fleet_request(
spot_fleet_request_id, target_capacity, terminate_instances)
template = self.response_template(MODIFY_SPOT_FLEET_REQUEST_TEMPLATE)
return template.render(successful=successful)
def request_spot_fleet(self): def request_spot_fleet(self):
spot_config = self._get_dict_param("SpotFleetRequestConfig.") spot_config = self._get_dict_param("SpotFleetRequestConfig.")
spot_price = spot_config['spot_price'] spot_price = spot_config['spot_price']
@ -56,6 +65,11 @@ REQUEST_SPOT_FLEET_TEMPLATE = """<RequestSpotFleetResponse xmlns="http://ec2.ama
<spotFleetRequestId>{{ request.id }}</spotFleetRequestId> <spotFleetRequestId>{{ request.id }}</spotFleetRequestId>
</RequestSpotFleetResponse>""" </RequestSpotFleetResponse>"""
MODIFY_SPOT_FLEET_REQUEST_TEMPLATE = """<ModifySpotFleetRequestResponse xmlns="http://ec2.amazonaws.com/doc/2016-09-15/">
<requestId>21681fea-9987-aef3-2121-example</requestId>
<return>{{ 'true' if successful else 'false' }}</return>
</ModifySpotFleetRequestResponse>"""
DESCRIBE_SPOT_FLEET_TEMPLATE = """<DescribeSpotFleetRequestsResponse xmlns="http://ec2.amazonaws.com/doc/2016-09-15/"> DESCRIBE_SPOT_FLEET_TEMPLATE = """<DescribeSpotFleetRequestsResponse xmlns="http://ec2.amazonaws.com/doc/2016-09-15/">
<requestId>4d68a6cc-8f2e-4be1-b425-example</requestId> <requestId>4d68a6cc-8f2e-4be1-b425-example</requestId>
<spotFleetRequestConfigSet> <spotFleetRequestConfigSet>

View File

@ -51,7 +51,7 @@ def random_ami_id():
def random_instance_id(): def random_instance_id():
return random_id(prefix=EC2_RESOURCE_TO_PREFIX['instance']) return random_id(prefix=EC2_RESOURCE_TO_PREFIX['instance'], size=17)
def random_reservation_id(): def random_reservation_id():

View File

@ -152,6 +152,13 @@ class InvalidDescribeRulesRequest(ELBClientError):
) )
class ResourceInUseError(ELBClientError):
def __init__(self, msg="A specified resource is in use"):
super(ResourceInUseError, self).__init__(
"ResourceInUse", msg)
class RuleNotFoundError(ELBClientError): class RuleNotFoundError(ELBClientError):
def __init__(self): def __init__(self):

View File

@ -21,6 +21,7 @@ from .exceptions import (
InvalidActionTypeError, InvalidActionTypeError,
ActionTargetGroupNotFoundError, ActionTargetGroupNotFoundError,
InvalidDescribeRulesRequest, InvalidDescribeRulesRequest,
ResourceInUseError,
RuleNotFoundError, RuleNotFoundError,
DuplicatePriorityError, DuplicatePriorityError,
InvalidTargetGroupNameError, InvalidTargetGroupNameError,
@ -426,11 +427,18 @@ class ELBv2Backend(BaseBackend):
# however, boto3 does't raise error even if rule is not found # however, boto3 does't raise error even if rule is not found
def delete_target_group(self, target_group_arn): def delete_target_group(self, target_group_arn):
target_group = self.target_groups.pop(target_group_arn, None) if target_group_arn not in self.target_groups:
if target_group:
return target_group
raise TargetGroupNotFoundError() raise TargetGroupNotFoundError()
target_group = self.target_groups[target_group_arn]
if target_group:
if self._any_listener_using(target_group_arn):
raise ResourceInUseError(
"The target group '{}' is currently in use by a listener or a rule".format(
target_group_arn))
del self.target_groups[target_group_arn]
return target_group
def delete_listener(self, listener_arn): def delete_listener(self, listener_arn):
for load_balancer in self.load_balancers.values(): for load_balancer in self.load_balancers.values():
listener = load_balancer.listeners.pop(listener_arn, None) listener = load_balancer.listeners.pop(listener_arn, None)
@ -539,6 +547,15 @@ class ELBv2Backend(BaseBackend):
modified_rules.append(given_rule) modified_rules.append(given_rule)
return modified_rules return modified_rules
def _any_listener_using(self, target_group_arn):
for load_balancer in self.load_balancers.values():
for listener in load_balancer.listeners.values():
for rule in listener.rules:
for action in rule.actions:
if action.get('target_group_arn') == target_group_arn:
return True
return False
elbv2_backends = {} elbv2_backends = {}
for region in ec2_backends.keys(): for region in ec2_backends.keys():

View File

@ -103,6 +103,12 @@ try: # pragma: no cover
except ImportError: # pragma: no cover except ImportError: # pragma: no cover
ssl = None ssl = None
try: # pragma: no cover
from requests.packages.urllib3.contrib.pyopenssl import inject_into_urllib3, extract_from_urllib3
pyopenssl_override = True
except:
pyopenssl_override = False
DEFAULT_HTTP_PORTS = frozenset([80]) DEFAULT_HTTP_PORTS = frozenset([80])
POTENTIAL_HTTP_PORTS = set(DEFAULT_HTTP_PORTS) POTENTIAL_HTTP_PORTS = set(DEFAULT_HTTP_PORTS)
@ -1013,6 +1019,9 @@ class httpretty(HttpBaseClass):
ssl.sslwrap_simple = old_sslwrap_simple ssl.sslwrap_simple = old_sslwrap_simple
ssl.__dict__['sslwrap_simple'] = old_sslwrap_simple ssl.__dict__['sslwrap_simple'] = old_sslwrap_simple
if pyopenssl_override:
inject_into_urllib3()
@classmethod @classmethod
def is_enabled(cls): def is_enabled(cls):
return cls._is_enabled return cls._is_enabled
@ -1056,6 +1065,9 @@ class httpretty(HttpBaseClass):
ssl.sslwrap_simple = fake_wrap_socket ssl.sslwrap_simple = fake_wrap_socket
ssl.__dict__['sslwrap_simple'] = fake_wrap_socket ssl.__dict__['sslwrap_simple'] = fake_wrap_socket
if pyopenssl_override:
extract_from_urllib3()
def httprettified(test): def httprettified(test):
"A decorator tests that use HTTPretty" "A decorator tests that use HTTPretty"

View File

@ -2,7 +2,8 @@ from __future__ import unicode_literals
import json import json
import dicttoxml import xmltodict
from jinja2 import Template from jinja2 import Template
from six import iteritems from six import iteritems
@ -26,6 +27,24 @@ def convert_json_error_to_xml(json_error):
return template.render(code=code, message=message) return template.render(code=code, message=message)
def itemize(data):
"""
The xmltodict.unparse requires we modify the shape of the input dictionary slightly. Instead of a dict of the form:
{'key': ['value1', 'value2']}
We must provide:
{'key': {'item': ['value1', 'value2']}}
"""
if isinstance(data, dict):
ret = {}
for key in data:
ret[key] = itemize(data[key])
return ret
elif isinstance(data, list):
return {'item': [itemize(value) for value in data]}
else:
return data
class RedshiftResponse(BaseResponse): class RedshiftResponse(BaseResponse):
@property @property
@ -36,8 +55,10 @@ class RedshiftResponse(BaseResponse):
if self.request_json: if self.request_json:
return json.dumps(response) return json.dumps(response)
else: else:
xml = dicttoxml.dicttoxml(response, attr_type=False, root=False) xml = xmltodict.unparse(itemize(response), full_document=False)
return xml.decode("utf-8") if hasattr(xml, 'decode'):
xml = xml.decode('utf-8')
return xml
def call_action(self): def call_action(self):
status, headers, body = super(RedshiftResponse, self).call_action() status, headers, body = super(RedshiftResponse, self).call_action()

View File

@ -13,7 +13,6 @@ install_requires = [
"cryptography>=2.0.0", "cryptography>=2.0.0",
"requests>=2.5", "requests>=2.5",
"xmltodict", "xmltodict",
"dicttoxml",
"six>1.9", "six>1.9",
"werkzeug", "werkzeug",
"pyaml", "pyaml",
@ -37,7 +36,7 @@ else:
setup( setup(
name='moto', name='moto',
version='1.1.20', version='1.1.21',
description='A library that allows your python tests to easily' description='A library that allows your python tests to easily'
' mock out the boto library', ' mock out the boto library',
author='Steve Pulec', author='Steve Pulec',

View File

@ -645,3 +645,74 @@ def test_get_function_created_with_zipfile():
} }
}, },
) )
@mock_lambda
def add_function_permission():
conn = boto3.client('lambda', 'us-west-2')
zip_content = get_test_zip_file1()
result = conn.create_function(
FunctionName='testFunction',
Runtime='python2.7',
Role='test-iam-role',
Handler='lambda_function.handler',
Code={
'ZipFile': zip_content,
},
Description='test lambda function',
Timeout=3,
MemorySize=128,
Publish=True,
)
response = conn.add_permission(
FunctionName='testFunction',
StatementId='1',
Action="lambda:InvokeFunction",
Principal='432143214321',
SourceArn="arn:aws:lambda:us-west-2:account-id:function:helloworld",
SourceAccount='123412341234',
EventSourceToken='blah',
Qualifier='2'
)
assert 'Statement' in response
res = json.loads(response['Statement'])
assert res['Action'] == "lambda:InvokeFunction"
@mock_lambda
def get_function_policy():
conn = boto3.client('lambda', 'us-west-2')
zip_content = get_test_zip_file1()
result = conn.create_function(
FunctionName='testFunction',
Runtime='python2.7',
Role='test-iam-role',
Handler='lambda_function.handler',
Code={
'ZipFile': zip_content,
},
Description='test lambda function',
Timeout=3,
MemorySize=128,
Publish=True,
)
response = conn.add_permission(
FunctionName='testFunction',
StatementId='1',
Action="lambda:InvokeFunction",
Principal='432143214321',
SourceArn="arn:aws:lambda:us-west-2:account-id:function:helloworld",
SourceAccount='123412341234',
EventSourceToken='blah',
Qualifier='2'
)
response = conn.get_policy(
FunctionName='testFunction'
)
assert 'Policy' in response
assert isinstance(response['Policy'], str)
res = json.loads(response['Policy'])
assert res['Statement'][0]['Action'] == 'lambda:InvokeFunction'

View File

@ -3,6 +3,7 @@ from __future__ import unicode_literals, print_function
import six import six
import boto import boto
import boto3 import boto3
from boto3.dynamodb.conditions import Attr
import sure # noqa import sure # noqa
import requests import requests
from moto import mock_dynamodb2, mock_dynamodb2_deprecated from moto import mock_dynamodb2, mock_dynamodb2_deprecated
@ -12,6 +13,10 @@ from botocore.exceptions import ClientError
from boto3.dynamodb.conditions import Key from boto3.dynamodb.conditions import Key
from tests.helpers import requires_boto_gte from tests.helpers import requires_boto_gte
import tests.backport_assert_raises import tests.backport_assert_raises
import moto.dynamodb2.comparisons
import moto.dynamodb2.models
from nose.tools import assert_raises from nose.tools import assert_raises
try: try:
import boto.dynamodb2 import boto.dynamodb2
@ -230,6 +235,7 @@ def test_scan_returns_consumed_capacity():
assert 'CapacityUnits' in response['ConsumedCapacity'] assert 'CapacityUnits' in response['ConsumedCapacity']
assert response['ConsumedCapacity']['TableName'] == name assert response['ConsumedCapacity']['TableName'] == name
@requires_boto_gte("2.9") @requires_boto_gte("2.9")
@mock_dynamodb2 @mock_dynamodb2
def test_query_returns_consumed_capacity(): def test_query_returns_consumed_capacity():
@ -280,6 +286,7 @@ def test_query_returns_consumed_capacity():
assert 'CapacityUnits' in results['ConsumedCapacity'] assert 'CapacityUnits' in results['ConsumedCapacity']
assert results['ConsumedCapacity']['CapacityUnits'] == 1 assert results['ConsumedCapacity']['CapacityUnits'] == 1
@mock_dynamodb2 @mock_dynamodb2
def test_basic_projection_expressions(): def test_basic_projection_expressions():
dynamodb = boto3.resource('dynamodb', region_name='us-east-1') dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
@ -353,6 +360,7 @@ def test_basic_projection_expressions():
assert 'body' in results['Items'][1] assert 'body' in results['Items'][1]
assert results['Items'][1]['body'] == 'yet another test message' assert results['Items'][1]['body'] == 'yet another test message'
@mock_dynamodb2 @mock_dynamodb2
def test_basic_projection_expressions_with_attr_expression_names(): def test_basic_projection_expressions_with_attr_expression_names():
dynamodb = boto3.resource('dynamodb', region_name='us-east-1') dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
@ -419,6 +427,7 @@ def test_basic_projection_expressions_with_attr_expression_names():
assert 'attachment' in results['Items'][0] assert 'attachment' in results['Items'][0]
assert results['Items'][0]['attachment'] == 'something' assert results['Items'][0]['attachment'] == 'something'
@mock_dynamodb2 @mock_dynamodb2
def test_put_item_returns_consumed_capacity(): def test_put_item_returns_consumed_capacity():
dynamodb = boto3.resource('dynamodb', region_name='us-east-1') dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
@ -461,6 +470,7 @@ def test_put_item_returns_consumed_capacity():
assert 'ConsumedCapacity' in response assert 'ConsumedCapacity' in response
@mock_dynamodb2 @mock_dynamodb2
def test_update_item_returns_consumed_capacity(): def test_update_item_returns_consumed_capacity():
dynamodb = boto3.resource('dynamodb', region_name='us-east-1') dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
@ -514,6 +524,7 @@ def test_update_item_returns_consumed_capacity():
assert 'CapacityUnits' in response['ConsumedCapacity'] assert 'CapacityUnits' in response['ConsumedCapacity']
assert 'TableName' in response['ConsumedCapacity'] assert 'TableName' in response['ConsumedCapacity']
@mock_dynamodb2 @mock_dynamodb2
def test_get_item_returns_consumed_capacity(): def test_get_item_returns_consumed_capacity():
dynamodb = boto3.resource('dynamodb', region_name='us-east-1') dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
@ -562,3 +573,206 @@ def test_get_item_returns_consumed_capacity():
assert 'ConsumedCapacity' in response assert 'ConsumedCapacity' in response
assert 'CapacityUnits' in response['ConsumedCapacity'] assert 'CapacityUnits' in response['ConsumedCapacity']
assert 'TableName' in response['ConsumedCapacity'] assert 'TableName' in response['ConsumedCapacity']
def test_filter_expression():
# TODO NOT not yet supported
row1 = moto.dynamodb2.models.Item(None, None, None, None, {'Id': {'N': '8'}, 'Subs': {'N': '5'}, 'Desc': {'S': 'Some description'}, 'KV': {'SS': ['test1', 'test2']}})
row2 = moto.dynamodb2.models.Item(None, None, None, None, {'Id': {'N': '8'}, 'Subs': {'N': '10'}, 'Desc': {'S': 'A description'}, 'KV': {'SS': ['test3', 'test4']}})
# AND test
filter_expr = moto.dynamodb2.comparisons.get_filter_expression('Id > 5 AND Subs < 7', {}, {})
filter_expr.expr(row1).should.be(True)
filter_expr.expr(row2).should.be(False)
# OR test
filter_expr = moto.dynamodb2.comparisons.get_filter_expression('Id = 5 OR Id=8', {}, {})
filter_expr.expr(row1).should.be(True)
# BETWEEN test
filter_expr = moto.dynamodb2.comparisons.get_filter_expression('Id BETWEEN 5 AND 10', {}, {})
filter_expr.expr(row1).should.be(True)
# PAREN test
filter_expr = moto.dynamodb2.comparisons.get_filter_expression('Id = 8 AND (Subs = 8 OR Subs = 5)', {}, {})
filter_expr.expr(row1).should.be(True)
# IN test
filter_expr = moto.dynamodb2.comparisons.get_filter_expression('Id IN (7,8, 9)', {}, {})
filter_expr.expr(row1).should.be(True)
# attribute function tests
filter_expr = moto.dynamodb2.comparisons.get_filter_expression('attribute_exists(Id) AND attribute_not_exists(User)', {}, {})
filter_expr.expr(row1).should.be(True)
filter_expr = moto.dynamodb2.comparisons.get_filter_expression('attribute_type(Id, N)', {}, {})
filter_expr.expr(row1).should.be(True)
# beginswith function test
filter_expr = moto.dynamodb2.comparisons.get_filter_expression('begins_with(Desc, Some)', {}, {})
filter_expr.expr(row1).should.be(True)
filter_expr.expr(row2).should.be(False)
# contains function test
filter_expr = moto.dynamodb2.comparisons.get_filter_expression('contains(KV, test1)', {}, {})
filter_expr.expr(row1).should.be(True)
filter_expr.expr(row2).should.be(False)
# size function test
filter_expr = moto.dynamodb2.comparisons.get_filter_expression('size(Desc) > size(KV)', {}, {})
filter_expr.expr(row1).should.be(True)
@mock_dynamodb2
def test_scan_filter():
client = boto3.client('dynamodb', region_name='us-east-1')
dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
# Create the DynamoDB table.
client.create_table(
TableName='test1',
AttributeDefinitions=[{'AttributeName': 'client', 'AttributeType': 'S'}, {'AttributeName': 'app', 'AttributeType': 'S'}],
KeySchema=[{'AttributeName': 'client', 'KeyType': 'HASH'}, {'AttributeName': 'app', 'KeyType': 'RANGE'}],
ProvisionedThroughput={'ReadCapacityUnits': 123, 'WriteCapacityUnits': 123}
)
client.put_item(
TableName='test1',
Item={
'client': {'S': 'client1'},
'app': {'S': 'app1'}
}
)
table = dynamodb.Table('test1')
response = table.scan(
FilterExpression=Attr('app').eq('app2')
)
assert response['Count'] == 0
response = table.scan(
FilterExpression=Attr('app').eq('app1')
)
assert response['Count'] == 1
@mock_dynamodb2
def test_bad_scan_filter():
client = boto3.client('dynamodb', region_name='us-east-1')
dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
# Create the DynamoDB table.
client.create_table(
TableName='test1',
AttributeDefinitions=[{'AttributeName': 'client', 'AttributeType': 'S'}, {'AttributeName': 'app', 'AttributeType': 'S'}],
KeySchema=[{'AttributeName': 'client', 'KeyType': 'HASH'}, {'AttributeName': 'app', 'KeyType': 'RANGE'}],
ProvisionedThroughput={'ReadCapacityUnits': 123, 'WriteCapacityUnits': 123}
)
table = dynamodb.Table('test1')
# Bad expression
try:
table.scan(
FilterExpression='client test'
)
except ClientError as err:
err.response['Error']['Code'].should.equal('ValidationError')
else:
raise RuntimeError('Should of raised ResourceInUseException')
@mock_dynamodb2
def test_duplicate_create():
client = boto3.client('dynamodb', region_name='us-east-1')
# Create the DynamoDB table.
client.create_table(
TableName='test1',
AttributeDefinitions=[{'AttributeName': 'client', 'AttributeType': 'S'}, {'AttributeName': 'app', 'AttributeType': 'S'}],
KeySchema=[{'AttributeName': 'client', 'KeyType': 'HASH'}, {'AttributeName': 'app', 'KeyType': 'RANGE'}],
ProvisionedThroughput={'ReadCapacityUnits': 123, 'WriteCapacityUnits': 123}
)
try:
client.create_table(
TableName='test1',
AttributeDefinitions=[{'AttributeName': 'client', 'AttributeType': 'S'}, {'AttributeName': 'app', 'AttributeType': 'S'}],
KeySchema=[{'AttributeName': 'client', 'KeyType': 'HASH'}, {'AttributeName': 'app', 'KeyType': 'RANGE'}],
ProvisionedThroughput={'ReadCapacityUnits': 123, 'WriteCapacityUnits': 123}
)
except ClientError as err:
err.response['Error']['Code'].should.equal('ResourceInUseException')
else:
raise RuntimeError('Should of raised ResourceInUseException')
@mock_dynamodb2
def test_delete_table():
client = boto3.client('dynamodb', region_name='us-east-1')
# Create the DynamoDB table.
client.create_table(
TableName='test1',
AttributeDefinitions=[{'AttributeName': 'client', 'AttributeType': 'S'}, {'AttributeName': 'app', 'AttributeType': 'S'}],
KeySchema=[{'AttributeName': 'client', 'KeyType': 'HASH'}, {'AttributeName': 'app', 'KeyType': 'RANGE'}],
ProvisionedThroughput={'ReadCapacityUnits': 123, 'WriteCapacityUnits': 123}
)
client.delete_table(TableName='test1')
resp = client.list_tables()
len(resp['TableNames']).should.equal(0)
try:
client.delete_table(TableName='test1')
except ClientError as err:
err.response['Error']['Code'].should.equal('ResourceNotFoundException')
else:
raise RuntimeError('Should of raised ResourceNotFoundException')
@mock_dynamodb2
def test_delete_item():
client = boto3.client('dynamodb', region_name='us-east-1')
dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
# Create the DynamoDB table.
client.create_table(
TableName='test1',
AttributeDefinitions=[{'AttributeName': 'client', 'AttributeType': 'S'}, {'AttributeName': 'app', 'AttributeType': 'S'}],
KeySchema=[{'AttributeName': 'client', 'KeyType': 'HASH'}, {'AttributeName': 'app', 'KeyType': 'RANGE'}],
ProvisionedThroughput={'ReadCapacityUnits': 123, 'WriteCapacityUnits': 123}
)
client.put_item(
TableName='test1',
Item={
'client': {'S': 'client1'},
'app': {'S': 'app1'}
}
)
client.put_item(
TableName='test1',
Item={
'client': {'S': 'client1'},
'app': {'S': 'app2'}
}
)
table = dynamodb.Table('test1')
response = table.scan()
assert response['Count'] == 2
# Test deletion and returning old value
response = table.delete_item(Key={'client': 'client1', 'app': 'app1'}, ReturnValues='ALL_OLD')
response['Attributes'].should.contain('client')
response['Attributes'].should.contain('app')
response = table.scan()
assert response['Count'] == 1
# Test deletion returning nothing
response = table.delete_item(Key={'client': 'client1', 'app': 'app2'})
len(response['Attributes']).should.equal(0)
response = table.scan()
assert response['Count'] == 0

View File

@ -613,6 +613,20 @@ def test_security_group_tagging_boto3():
tag['Key'].should.equal("Test") tag['Key'].should.equal("Test")
@mock_ec2
def test_security_group_wildcard_tag_filter_boto3():
conn = boto3.client('ec2', region_name='us-east-1')
sg = conn.create_security_group(GroupName="test-sg", Description="Test SG")
conn.create_tags(Resources=[sg['GroupId']], Tags=[
{'Key': 'Test', 'Value': 'Tag'}])
describe = conn.describe_security_groups(
Filters=[{'Name': 'tag-value', 'Values': ['*']}])
tag = describe["SecurityGroups"][0]['Tags'][0]
tag['Value'].should.equal("Tag")
tag['Key'].should.equal("Test")
@mock_ec2 @mock_ec2
def test_authorize_and_revoke_in_bulk(): def test_authorize_and_revoke_in_bulk():
ec2 = boto3.resource('ec2', region_name='us-west-1') ec2 = boto3.resource('ec2', region_name='us-west-1')

View File

@ -164,3 +164,155 @@ def test_cancel_spot_fleet_request():
spot_fleet_requests = conn.describe_spot_fleet_requests( spot_fleet_requests = conn.describe_spot_fleet_requests(
SpotFleetRequestIds=[spot_fleet_id])['SpotFleetRequestConfigs'] SpotFleetRequestIds=[spot_fleet_id])['SpotFleetRequestConfigs']
len(spot_fleet_requests).should.equal(0) len(spot_fleet_requests).should.equal(0)
@mock_ec2
def test_modify_spot_fleet_request_up():
conn = boto3.client("ec2", region_name='us-west-2')
subnet_id = get_subnet_id(conn)
spot_fleet_res = conn.request_spot_fleet(
SpotFleetRequestConfig=spot_config(subnet_id),
)
spot_fleet_id = spot_fleet_res['SpotFleetRequestId']
conn.modify_spot_fleet_request(
SpotFleetRequestId=spot_fleet_id, TargetCapacity=20)
instance_res = conn.describe_spot_fleet_instances(
SpotFleetRequestId=spot_fleet_id)
instances = instance_res['ActiveInstances']
len(instances).should.equal(10)
spot_fleet_config = conn.describe_spot_fleet_requests(
SpotFleetRequestIds=[spot_fleet_id])['SpotFleetRequestConfigs'][0]['SpotFleetRequestConfig']
spot_fleet_config['TargetCapacity'].should.equal(20)
spot_fleet_config['FulfilledCapacity'].should.equal(20.0)
@mock_ec2
def test_modify_spot_fleet_request_up_diversified():
conn = boto3.client("ec2", region_name='us-west-2')
subnet_id = get_subnet_id(conn)
spot_fleet_res = conn.request_spot_fleet(
SpotFleetRequestConfig=spot_config(
subnet_id, allocation_strategy='diversified'),
)
spot_fleet_id = spot_fleet_res['SpotFleetRequestId']
conn.modify_spot_fleet_request(
SpotFleetRequestId=spot_fleet_id, TargetCapacity=19)
instance_res = conn.describe_spot_fleet_instances(
SpotFleetRequestId=spot_fleet_id)
instances = instance_res['ActiveInstances']
len(instances).should.equal(7)
spot_fleet_config = conn.describe_spot_fleet_requests(
SpotFleetRequestIds=[spot_fleet_id])['SpotFleetRequestConfigs'][0]['SpotFleetRequestConfig']
spot_fleet_config['TargetCapacity'].should.equal(19)
spot_fleet_config['FulfilledCapacity'].should.equal(20.0)
@mock_ec2
def test_modify_spot_fleet_request_down_no_terminate():
conn = boto3.client("ec2", region_name='us-west-2')
subnet_id = get_subnet_id(conn)
spot_fleet_res = conn.request_spot_fleet(
SpotFleetRequestConfig=spot_config(subnet_id),
)
spot_fleet_id = spot_fleet_res['SpotFleetRequestId']
conn.modify_spot_fleet_request(
SpotFleetRequestId=spot_fleet_id, TargetCapacity=1, ExcessCapacityTerminationPolicy="noTermination")
instance_res = conn.describe_spot_fleet_instances(
SpotFleetRequestId=spot_fleet_id)
instances = instance_res['ActiveInstances']
len(instances).should.equal(3)
spot_fleet_config = conn.describe_spot_fleet_requests(
SpotFleetRequestIds=[spot_fleet_id])['SpotFleetRequestConfigs'][0]['SpotFleetRequestConfig']
spot_fleet_config['TargetCapacity'].should.equal(1)
spot_fleet_config['FulfilledCapacity'].should.equal(6.0)
@mock_ec2
def test_modify_spot_fleet_request_down_odd():
conn = boto3.client("ec2", region_name='us-west-2')
subnet_id = get_subnet_id(conn)
spot_fleet_res = conn.request_spot_fleet(
SpotFleetRequestConfig=spot_config(subnet_id),
)
spot_fleet_id = spot_fleet_res['SpotFleetRequestId']
conn.modify_spot_fleet_request(
SpotFleetRequestId=spot_fleet_id, TargetCapacity=7)
conn.modify_spot_fleet_request(
SpotFleetRequestId=spot_fleet_id, TargetCapacity=5)
instance_res = conn.describe_spot_fleet_instances(
SpotFleetRequestId=spot_fleet_id)
instances = instance_res['ActiveInstances']
len(instances).should.equal(3)
spot_fleet_config = conn.describe_spot_fleet_requests(
SpotFleetRequestIds=[spot_fleet_id])['SpotFleetRequestConfigs'][0]['SpotFleetRequestConfig']
spot_fleet_config['TargetCapacity'].should.equal(5)
spot_fleet_config['FulfilledCapacity'].should.equal(6.0)
@mock_ec2
def test_modify_spot_fleet_request_down():
conn = boto3.client("ec2", region_name='us-west-2')
subnet_id = get_subnet_id(conn)
spot_fleet_res = conn.request_spot_fleet(
SpotFleetRequestConfig=spot_config(subnet_id),
)
spot_fleet_id = spot_fleet_res['SpotFleetRequestId']
conn.modify_spot_fleet_request(
SpotFleetRequestId=spot_fleet_id, TargetCapacity=1)
instance_res = conn.describe_spot_fleet_instances(
SpotFleetRequestId=spot_fleet_id)
instances = instance_res['ActiveInstances']
len(instances).should.equal(1)
spot_fleet_config = conn.describe_spot_fleet_requests(
SpotFleetRequestIds=[spot_fleet_id])['SpotFleetRequestConfigs'][0]['SpotFleetRequestConfig']
spot_fleet_config['TargetCapacity'].should.equal(1)
spot_fleet_config['FulfilledCapacity'].should.equal(2.0)
@mock_ec2
def test_modify_spot_fleet_request_down_no_terminate_after_custom_terminate():
conn = boto3.client("ec2", region_name='us-west-2')
subnet_id = get_subnet_id(conn)
spot_fleet_res = conn.request_spot_fleet(
SpotFleetRequestConfig=spot_config(subnet_id),
)
spot_fleet_id = spot_fleet_res['SpotFleetRequestId']
instance_res = conn.describe_spot_fleet_instances(
SpotFleetRequestId=spot_fleet_id)
instances = instance_res['ActiveInstances']
conn.terminate_instances(InstanceIds=[i['InstanceId'] for i in instances[1:]])
conn.modify_spot_fleet_request(
SpotFleetRequestId=spot_fleet_id, TargetCapacity=1, ExcessCapacityTerminationPolicy="noTermination")
instance_res = conn.describe_spot_fleet_instances(
SpotFleetRequestId=spot_fleet_id)
instances = instance_res['ActiveInstances']
len(instances).should.equal(1)
spot_fleet_config = conn.describe_spot_fleet_requests(
SpotFleetRequestIds=[spot_fleet_id])['SpotFleetRequestConfigs'][0]['SpotFleetRequestConfig']
spot_fleet_config['TargetCapacity'].should.equal(1)
spot_fleet_config['FulfilledCapacity'].should.equal(2.0)

View File

@ -14,10 +14,17 @@ def test_create_load_balancer():
conn = boto3.client('elbv2', region_name='us-east-1') conn = boto3.client('elbv2', region_name='us-east-1')
ec2 = boto3.resource('ec2', region_name='us-east-1') ec2 = boto3.resource('ec2', region_name='us-east-1')
security_group = ec2.create_security_group(GroupName='a-security-group', Description='First One') security_group = ec2.create_security_group(
GroupName='a-security-group', Description='First One')
vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default') vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default')
subnet1 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone='us-east-1a') subnet1 = ec2.create_subnet(
subnet2 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone='us-east-1b') VpcId=vpc.id,
CidrBlock='172.28.7.192/26',
AvailabilityZone='us-east-1a')
subnet2 = ec2.create_subnet(
VpcId=vpc.id,
CidrBlock='172.28.7.192/26',
AvailabilityZone='us-east-1b')
response = conn.create_load_balancer( response = conn.create_load_balancer(
Name='my-lb', Name='my-lb',
@ -29,7 +36,8 @@ def test_create_load_balancer():
lb = response.get('LoadBalancers')[0] lb = response.get('LoadBalancers')[0]
lb.get('DNSName').should.equal("my-lb-1.us-east-1.elb.amazonaws.com") lb.get('DNSName').should.equal("my-lb-1.us-east-1.elb.amazonaws.com")
lb.get('LoadBalancerArn').should.equal('arn:aws:elasticloadbalancing:us-east-1:1:loadbalancer/my-lb/50dc6c495c0c9188') lb.get('LoadBalancerArn').should.equal(
'arn:aws:elasticloadbalancing:us-east-1:1:loadbalancer/my-lb/50dc6c495c0c9188')
lb.get('SecurityGroups').should.equal([security_group.id]) lb.get('SecurityGroups').should.equal([security_group.id])
lb.get('AvailabilityZones').should.equal([ lb.get('AvailabilityZones').should.equal([
{'SubnetId': subnet1.id, 'ZoneName': 'us-east-1a'}, {'SubnetId': subnet1.id, 'ZoneName': 'us-east-1a'},
@ -37,7 +45,8 @@ def test_create_load_balancer():
# Ensure the tags persisted # Ensure the tags persisted
response = conn.describe_tags(ResourceArns=[lb.get('LoadBalancerArn')]) response = conn.describe_tags(ResourceArns=[lb.get('LoadBalancerArn')])
tags = {d['Key']: d['Value'] for d in response['TagDescriptions'][0]['Tags']} tags = {d['Key']: d['Value']
for d in response['TagDescriptions'][0]['Tags']}
tags.should.equal({'key_name': 'a_value'}) tags.should.equal({'key_name': 'a_value'})
@ -47,10 +56,17 @@ def test_describe_load_balancers():
conn = boto3.client('elbv2', region_name='us-east-1') conn = boto3.client('elbv2', region_name='us-east-1')
ec2 = boto3.resource('ec2', region_name='us-east-1') ec2 = boto3.resource('ec2', region_name='us-east-1')
security_group = ec2.create_security_group(GroupName='a-security-group', Description='First One') security_group = ec2.create_security_group(
GroupName='a-security-group', Description='First One')
vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default') vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default')
subnet1 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone='us-east-1a') subnet1 = ec2.create_subnet(
subnet2 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone='us-east-1b') VpcId=vpc.id,
CidrBlock='172.28.7.192/26',
AvailabilityZone='us-east-1a')
subnet2 = ec2.create_subnet(
VpcId=vpc.id,
CidrBlock='172.28.7.192/26',
AvailabilityZone='us-east-1b')
conn.create_load_balancer( conn.create_load_balancer(
Name='my-lb', Name='my-lb',
@ -65,11 +81,14 @@ def test_describe_load_balancers():
lb = response.get('LoadBalancers')[0] lb = response.get('LoadBalancers')[0]
lb.get('LoadBalancerName').should.equal('my-lb') lb.get('LoadBalancerName').should.equal('my-lb')
response = conn.describe_load_balancers(LoadBalancerArns=[lb.get('LoadBalancerArn')]) response = conn.describe_load_balancers(
response.get('LoadBalancers')[0].get('LoadBalancerName').should.equal('my-lb') LoadBalancerArns=[lb.get('LoadBalancerArn')])
response.get('LoadBalancers')[0].get(
'LoadBalancerName').should.equal('my-lb')
response = conn.describe_load_balancers(Names=['my-lb']) response = conn.describe_load_balancers(Names=['my-lb'])
response.get('LoadBalancers')[0].get('LoadBalancerName').should.equal('my-lb') response.get('LoadBalancers')[0].get(
'LoadBalancerName').should.equal('my-lb')
with assert_raises(ClientError): with assert_raises(ClientError):
conn.describe_load_balancers(LoadBalancerArns=['not-a/real/arn']) conn.describe_load_balancers(LoadBalancerArns=['not-a/real/arn'])
@ -84,10 +103,17 @@ def test_add_remove_tags():
ec2 = boto3.resource('ec2', region_name='us-east-1') ec2 = boto3.resource('ec2', region_name='us-east-1')
security_group = ec2.create_security_group(GroupName='a-security-group', Description='First One') security_group = ec2.create_security_group(
GroupName='a-security-group', Description='First One')
vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default') vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default')
subnet1 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone='us-east-1a') subnet1 = ec2.create_subnet(
subnet2 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone='us-east-1b') VpcId=vpc.id,
CidrBlock='172.28.7.192/26',
AvailabilityZone='us-east-1a')
subnet2 = ec2.create_subnet(
VpcId=vpc.id,
CidrBlock='172.28.7.192/26',
AvailabilityZone='us-east-1b')
conn.create_load_balancer( conn.create_load_balancer(
Name='my-lb', Name='my-lb',
@ -197,10 +223,19 @@ def test_create_elb_in_multiple_region():
conn = boto3.client('elbv2', region_name=region) conn = boto3.client('elbv2', region_name=region)
ec2 = boto3.resource('ec2', region_name=region) ec2 = boto3.resource('ec2', region_name=region)
security_group = ec2.create_security_group(GroupName='a-security-group', Description='First One') security_group = ec2.create_security_group(
vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default') GroupName='a-security-group', Description='First One')
subnet1 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone=region + 'a') vpc = ec2.create_vpc(
subnet2 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone=region + 'b') CidrBlock='172.28.7.0/24',
InstanceTenancy='default')
subnet1 = ec2.create_subnet(
VpcId=vpc.id,
CidrBlock='172.28.7.192/26',
AvailabilityZone=region + 'a')
subnet2 = ec2.create_subnet(
VpcId=vpc.id,
CidrBlock='172.28.7.192/26',
AvailabilityZone=region + 'b')
conn.create_load_balancer( conn.create_load_balancer(
Name='my-lb', Name='my-lb',
@ -210,10 +245,14 @@ def test_create_elb_in_multiple_region():
Tags=[{'Key': 'key_name', 'Value': 'a_value'}]) Tags=[{'Key': 'key_name', 'Value': 'a_value'}])
list( list(
boto3.client('elbv2', region_name='us-west-1').describe_load_balancers().get('LoadBalancers') boto3.client(
'elbv2',
region_name='us-west-1').describe_load_balancers().get('LoadBalancers')
).should.have.length_of(1) ).should.have.length_of(1)
list( list(
boto3.client('elbv2', region_name='us-west-2').describe_load_balancers().get('LoadBalancers') boto3.client(
'elbv2',
region_name='us-west-2').describe_load_balancers().get('LoadBalancers')
).should.have.length_of(1) ).should.have.length_of(1)
@ -223,10 +262,17 @@ def test_create_target_group_and_listeners():
conn = boto3.client('elbv2', region_name='us-east-1') conn = boto3.client('elbv2', region_name='us-east-1')
ec2 = boto3.resource('ec2', region_name='us-east-1') ec2 = boto3.resource('ec2', region_name='us-east-1')
security_group = ec2.create_security_group(GroupName='a-security-group', Description='First One') security_group = ec2.create_security_group(
GroupName='a-security-group', Description='First One')
vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default') vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default')
subnet1 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone='us-east-1a') subnet1 = ec2.create_subnet(
subnet2 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone='us-east-1b') VpcId=vpc.id,
CidrBlock='172.28.7.192/26',
AvailabilityZone='us-east-1a')
subnet2 = ec2.create_subnet(
VpcId=vpc.id,
CidrBlock='172.28.7.192/26',
AvailabilityZone='us-east-1b')
response = conn.create_load_balancer( response = conn.create_load_balancer(
Name='my-lb', Name='my-lb',
@ -254,7 +300,8 @@ def test_create_target_group_and_listeners():
target_group_arn = target_group['TargetGroupArn'] target_group_arn = target_group['TargetGroupArn']
# Add tags to the target group # Add tags to the target group
conn.add_tags(ResourceArns=[target_group_arn], Tags=[{'Key': 'target', 'Value': 'group'}]) conn.add_tags(ResourceArns=[target_group_arn], Tags=[
{'Key': 'target', 'Value': 'group'}])
conn.describe_tags(ResourceArns=[target_group_arn])['TagDescriptions'][0]['Tags'].should.equal( conn.describe_tags(ResourceArns=[target_group_arn])['TagDescriptions'][0]['Tags'].should.equal(
[{'Key': 'target', 'Value': 'group'}]) [{'Key': 'target', 'Value': 'group'}])
@ -281,7 +328,8 @@ def test_create_target_group_and_listeners():
LoadBalancerArn=load_balancer_arn, LoadBalancerArn=load_balancer_arn,
Protocol='HTTPS', Protocol='HTTPS',
Port=443, Port=443,
Certificates=[{'CertificateArn': 'arn:aws:iam:123456789012:server-certificate/test-cert'}], Certificates=[
{'CertificateArn': 'arn:aws:iam:123456789012:server-certificate/test-cert'}],
DefaultActions=[{'Type': 'forward', 'TargetGroupArn': target_group.get('TargetGroupArn')}]) DefaultActions=[{'Type': 'forward', 'TargetGroupArn': target_group.get('TargetGroupArn')}])
listener = response.get('Listeners')[0] listener = response.get('Listeners')[0]
listener.get('Port').should.equal(443) listener.get('Port').should.equal(443)
@ -303,9 +351,20 @@ def test_create_target_group_and_listeners():
listener.get('Port').should.equal(443) listener.get('Port').should.equal(443)
listener.get('Protocol').should.equal('HTTPS') listener.get('Protocol').should.equal('HTTPS')
response = conn.describe_listeners(ListenerArns=[http_listener_arn, https_listener_arn]) response = conn.describe_listeners(
ListenerArns=[
http_listener_arn,
https_listener_arn])
response.get('Listeners').should.have.length_of(2) response.get('Listeners').should.have.length_of(2)
# Try to delete the target group and it fails because there's a
# listener referencing it
with assert_raises(ClientError) as e:
conn.delete_target_group(
TargetGroupArn=target_group.get('TargetGroupArn'))
e.exception.operation_name.should.equal('DeleteTargetGroup')
e.exception.args.should.equal(("An error occurred (ResourceInUse) when calling the DeleteTargetGroup operation: The target group 'arn:aws:elasticloadbalancing:us-east-1:1:targetgroup/a-target/50dc6c495c0c9188' is currently in use by a listener or a rule", )) # NOQA
# Delete one listener # Delete one listener
response = conn.describe_listeners(LoadBalancerArn=load_balancer_arn) response = conn.describe_listeners(LoadBalancerArn=load_balancer_arn)
response.get('Listeners').should.have.length_of(2) response.get('Listeners').should.have.length_of(2)
@ -321,7 +380,10 @@ def test_create_target_group_and_listeners():
response.get('LoadBalancers').should.have.length_of(0) response.get('LoadBalancers').should.have.length_of(0)
# And it deleted the remaining listener # And it deleted the remaining listener
response = conn.describe_listeners(ListenerArns=[http_listener_arn, https_listener_arn]) response = conn.describe_listeners(
ListenerArns=[
http_listener_arn,
https_listener_arn])
response.get('Listeners').should.have.length_of(0) response.get('Listeners').should.have.length_of(0)
# But not the target groups # But not the target groups
@ -359,7 +421,13 @@ def test_create_invalid_target_group():
UnhealthyThresholdCount=2, UnhealthyThresholdCount=2,
Matcher={'HttpCode': '200'}) Matcher={'HttpCode': '200'})
invalid_names = ['-name', 'name-', '-name-', 'example.com', 'test@test', 'Na--me'] invalid_names = [
'-name',
'name-',
'-name-',
'example.com',
'test@test',
'Na--me']
for name in invalid_names: for name in invalid_names:
with assert_raises(ClientError): with assert_raises(ClientError):
conn.create_target_group( conn.create_target_group(
@ -399,10 +467,17 @@ def test_describe_paginated_balancers():
conn = boto3.client('elbv2', region_name='us-east-1') conn = boto3.client('elbv2', region_name='us-east-1')
ec2 = boto3.resource('ec2', region_name='us-east-1') ec2 = boto3.resource('ec2', region_name='us-east-1')
security_group = ec2.create_security_group(GroupName='a-security-group', Description='First One') security_group = ec2.create_security_group(
GroupName='a-security-group', Description='First One')
vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default') vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default')
subnet1 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone='us-east-1a') subnet1 = ec2.create_subnet(
subnet2 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone='us-east-1b') VpcId=vpc.id,
CidrBlock='172.28.7.192/26',
AvailabilityZone='us-east-1a')
subnet2 = ec2.create_subnet(
VpcId=vpc.id,
CidrBlock='172.28.7.192/26',
AvailabilityZone='us-east-1b')
for i in range(51): for i in range(51):
conn.create_load_balancer( conn.create_load_balancer(
@ -414,7 +489,8 @@ def test_describe_paginated_balancers():
resp = conn.describe_load_balancers() resp = conn.describe_load_balancers()
resp['LoadBalancers'].should.have.length_of(50) resp['LoadBalancers'].should.have.length_of(50)
resp['NextMarker'].should.equal(resp['LoadBalancers'][-1]['LoadBalancerName']) resp['NextMarker'].should.equal(
resp['LoadBalancers'][-1]['LoadBalancerName'])
resp2 = conn.describe_load_balancers(Marker=resp['NextMarker']) resp2 = conn.describe_load_balancers(Marker=resp['NextMarker'])
resp2['LoadBalancers'].should.have.length_of(1) resp2['LoadBalancers'].should.have.length_of(1)
assert 'NextToken' not in resp2.keys() assert 'NextToken' not in resp2.keys()
@ -426,10 +502,17 @@ def test_delete_load_balancer():
conn = boto3.client('elbv2', region_name='us-east-1') conn = boto3.client('elbv2', region_name='us-east-1')
ec2 = boto3.resource('ec2', region_name='us-east-1') ec2 = boto3.resource('ec2', region_name='us-east-1')
security_group = ec2.create_security_group(GroupName='a-security-group', Description='First One') security_group = ec2.create_security_group(
GroupName='a-security-group', Description='First One')
vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default') vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default')
subnet1 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone='us-east-1a') subnet1 = ec2.create_subnet(
subnet2 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone='us-east-1b') VpcId=vpc.id,
CidrBlock='172.28.7.192/26',
AvailabilityZone='us-east-1a')
subnet2 = ec2.create_subnet(
VpcId=vpc.id,
CidrBlock='172.28.7.192/26',
AvailabilityZone='us-east-1b')
response = conn.create_load_balancer( response = conn.create_load_balancer(
Name='my-lb', Name='my-lb',
@ -452,10 +535,17 @@ def test_register_targets():
conn = boto3.client('elbv2', region_name='us-east-1') conn = boto3.client('elbv2', region_name='us-east-1')
ec2 = boto3.resource('ec2', region_name='us-east-1') ec2 = boto3.resource('ec2', region_name='us-east-1')
security_group = ec2.create_security_group(GroupName='a-security-group', Description='First One') security_group = ec2.create_security_group(
GroupName='a-security-group', Description='First One')
vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default') vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default')
subnet1 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone='us-east-1a') subnet1 = ec2.create_subnet(
subnet2 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone='us-east-1b') VpcId=vpc.id,
CidrBlock='172.28.7.192/26',
AvailabilityZone='us-east-1a')
subnet2 = ec2.create_subnet(
VpcId=vpc.id,
CidrBlock='172.28.7.192/26',
AvailabilityZone='us-east-1b')
conn.create_load_balancer( conn.create_load_balancer(
Name='my-lb', Name='my-lb',
@ -480,7 +570,8 @@ def test_register_targets():
target_group = response.get('TargetGroups')[0] target_group = response.get('TargetGroups')[0]
# No targets registered yet # No targets registered yet
response = conn.describe_target_health(TargetGroupArn=target_group.get('TargetGroupArn')) response = conn.describe_target_health(
TargetGroupArn=target_group.get('TargetGroupArn'))
response.get('TargetHealthDescriptions').should.have.length_of(0) response.get('TargetHealthDescriptions').should.have.length_of(0)
response = ec2.create_instances( response = ec2.create_instances(
@ -501,14 +592,16 @@ def test_register_targets():
}, },
]) ])
response = conn.describe_target_health(TargetGroupArn=target_group.get('TargetGroupArn')) response = conn.describe_target_health(
TargetGroupArn=target_group.get('TargetGroupArn'))
response.get('TargetHealthDescriptions').should.have.length_of(2) response.get('TargetHealthDescriptions').should.have.length_of(2)
response = conn.deregister_targets( response = conn.deregister_targets(
TargetGroupArn=target_group.get('TargetGroupArn'), TargetGroupArn=target_group.get('TargetGroupArn'),
Targets=[{'Id': instance_id2}]) Targets=[{'Id': instance_id2}])
response = conn.describe_target_health(TargetGroupArn=target_group.get('TargetGroupArn')) response = conn.describe_target_health(
TargetGroupArn=target_group.get('TargetGroupArn'))
response.get('TargetHealthDescriptions').should.have.length_of(1) response.get('TargetHealthDescriptions').should.have.length_of(1)
@ -518,10 +611,17 @@ def test_target_group_attributes():
conn = boto3.client('elbv2', region_name='us-east-1') conn = boto3.client('elbv2', region_name='us-east-1')
ec2 = boto3.resource('ec2', region_name='us-east-1') ec2 = boto3.resource('ec2', region_name='us-east-1')
security_group = ec2.create_security_group(GroupName='a-security-group', Description='First One') security_group = ec2.create_security_group(
GroupName='a-security-group', Description='First One')
vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default') vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default')
subnet1 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone='us-east-1a') subnet1 = ec2.create_subnet(
subnet2 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone='us-east-1b') VpcId=vpc.id,
CidrBlock='172.28.7.192/26',
AvailabilityZone='us-east-1a')
subnet2 = ec2.create_subnet(
VpcId=vpc.id,
CidrBlock='172.28.7.192/26',
AvailabilityZone='us-east-1b')
response = conn.create_load_balancer( response = conn.create_load_balancer(
Name='my-lb', Name='my-lb',
@ -557,9 +657,11 @@ def test_target_group_attributes():
target_group_arn = target_group['TargetGroupArn'] target_group_arn = target_group['TargetGroupArn']
# The attributes should start with the two defaults # The attributes should start with the two defaults
response = conn.describe_target_group_attributes(TargetGroupArn=target_group_arn) response = conn.describe_target_group_attributes(
TargetGroupArn=target_group_arn)
response['Attributes'].should.have.length_of(2) response['Attributes'].should.have.length_of(2)
attributes = {attr['Key']: attr['Value'] for attr in response['Attributes']} attributes = {attr['Key']: attr['Value']
for attr in response['Attributes']}
attributes['deregistration_delay.timeout_seconds'].should.equal('300') attributes['deregistration_delay.timeout_seconds'].should.equal('300')
attributes['stickiness.enabled'].should.equal('false') attributes['stickiness.enabled'].should.equal('false')
@ -579,14 +681,17 @@ def test_target_group_attributes():
# The response should have only the keys updated # The response should have only the keys updated
response['Attributes'].should.have.length_of(2) response['Attributes'].should.have.length_of(2)
attributes = {attr['Key']: attr['Value'] for attr in response['Attributes']} attributes = {attr['Key']: attr['Value']
for attr in response['Attributes']}
attributes['stickiness.type'].should.equal('lb_cookie') attributes['stickiness.type'].should.equal('lb_cookie')
attributes['stickiness.enabled'].should.equal('true') attributes['stickiness.enabled'].should.equal('true')
# These new values should be in the full attribute list # These new values should be in the full attribute list
response = conn.describe_target_group_attributes(TargetGroupArn=target_group_arn) response = conn.describe_target_group_attributes(
TargetGroupArn=target_group_arn)
response['Attributes'].should.have.length_of(3) response['Attributes'].should.have.length_of(3)
attributes = {attr['Key']: attr['Value'] for attr in response['Attributes']} attributes = {attr['Key']: attr['Value']
for attr in response['Attributes']}
attributes['stickiness.type'].should.equal('lb_cookie') attributes['stickiness.type'].should.equal('lb_cookie')
attributes['stickiness.enabled'].should.equal('true') attributes['stickiness.enabled'].should.equal('true')
@ -597,10 +702,17 @@ def test_handle_listener_rules():
conn = boto3.client('elbv2', region_name='us-east-1') conn = boto3.client('elbv2', region_name='us-east-1')
ec2 = boto3.resource('ec2', region_name='us-east-1') ec2 = boto3.resource('ec2', region_name='us-east-1')
security_group = ec2.create_security_group(GroupName='a-security-group', Description='First One') security_group = ec2.create_security_group(
GroupName='a-security-group', Description='First One')
vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default') vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default')
subnet1 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone='us-east-1a') subnet1 = ec2.create_subnet(
subnet2 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone='us-east-1b') VpcId=vpc.id,
CidrBlock='172.28.7.192/26',
AvailabilityZone='us-east-1a')
subnet2 = ec2.create_subnet(
VpcId=vpc.id,
CidrBlock='172.28.7.192/26',
AvailabilityZone='us-east-1b')
response = conn.create_load_balancer( response = conn.create_load_balancer(
Name='my-lb', Name='my-lb',
@ -684,9 +796,8 @@ def test_handle_listener_rules():
) )
# test for PriorityInUse # test for PriorityInUse
host2 = 'yyy.example.com'
with assert_raises(ClientError): with assert_raises(ClientError):
r = conn.create_rule( conn.create_rule(
ListenerArn=http_listener_arn, ListenerArn=http_listener_arn,
Priority=priority, Priority=priority,
Conditions=[{ Conditions=[{
@ -703,7 +814,6 @@ def test_handle_listener_rules():
}] }]
) )
# test for describe listeners # test for describe listeners
obtained_rules = conn.describe_rules(ListenerArn=http_listener_arn) obtained_rules = conn.describe_rules(ListenerArn=http_listener_arn)
len(obtained_rules['Rules']).should.equal(3) len(obtained_rules['Rules']).should.equal(3)
@ -716,15 +826,20 @@ def test_handle_listener_rules():
obtained_rules['Rules'].should.equal([first_rule]) obtained_rules['Rules'].should.equal([first_rule])
# test for pagination # test for pagination
obtained_rules = conn.describe_rules(ListenerArn=http_listener_arn, PageSize=1) obtained_rules = conn.describe_rules(
ListenerArn=http_listener_arn, PageSize=1)
len(obtained_rules['Rules']).should.equal(1) len(obtained_rules['Rules']).should.equal(1)
obtained_rules.should.have.key('NextMarker') obtained_rules.should.have.key('NextMarker')
next_marker = obtained_rules['NextMarker'] next_marker = obtained_rules['NextMarker']
following_rules = conn.describe_rules(ListenerArn=http_listener_arn, PageSize=1, Marker=next_marker) following_rules = conn.describe_rules(
ListenerArn=http_listener_arn,
PageSize=1,
Marker=next_marker)
len(following_rules['Rules']).should.equal(1) len(following_rules['Rules']).should.equal(1)
following_rules.should.have.key('NextMarker') following_rules.should.have.key('NextMarker')
following_rules['Rules'][0]['RuleArn'].should_not.equal(obtained_rules['Rules'][0]['RuleArn']) following_rules['Rules'][0]['RuleArn'].should_not.equal(
obtained_rules['Rules'][0]['RuleArn'])
# test for invalid describe rule request # test for invalid describe rule request
with assert_raises(ClientError): with assert_raises(ClientError):
@ -757,12 +872,14 @@ def test_handle_listener_rules():
modified_rule.should.equal(obtained_rule) modified_rule.should.equal(obtained_rule)
obtained_rule['Conditions'][0]['Values'][0].should.equal(new_host) obtained_rule['Conditions'][0]['Values'][0].should.equal(new_host)
obtained_rule['Conditions'][1]['Values'][0].should.equal(new_path_pattern) obtained_rule['Conditions'][1]['Values'][0].should.equal(new_path_pattern)
obtained_rule['Actions'][0]['TargetGroupArn'].should.equal(target_group.get('TargetGroupArn')) obtained_rule['Actions'][0]['TargetGroupArn'].should.equal(
target_group.get('TargetGroupArn'))
# modify priority # modify priority
conn.set_rule_priorities( conn.set_rule_priorities(
RulePriorities=[ RulePriorities=[
{'RuleArn': first_rule['RuleArn'], 'Priority': int(first_rule['Priority']) - 1} {'RuleArn': first_rule['RuleArn'],
'Priority': int(first_rule['Priority']) - 1}
] ]
) )
with assert_raises(ClientError): with assert_raises(ClientError):
@ -782,7 +899,7 @@ def test_handle_listener_rules():
# test for invalid action type # test for invalid action type
safe_priority = 2 safe_priority = 2
with assert_raises(ClientError): with assert_raises(ClientError):
r = conn.create_rule( conn.create_rule(
ListenerArn=http_listener_arn, ListenerArn=http_listener_arn,
Priority=safe_priority, Priority=safe_priority,
Conditions=[{ Conditions=[{
@ -803,7 +920,7 @@ def test_handle_listener_rules():
safe_priority = 2 safe_priority = 2
invalid_target_group_arn = target_group.get('TargetGroupArn') + 'x' invalid_target_group_arn = target_group.get('TargetGroupArn') + 'x'
with assert_raises(ClientError): with assert_raises(ClientError):
r = conn.create_rule( conn.create_rule(
ListenerArn=http_listener_arn, ListenerArn=http_listener_arn,
Priority=safe_priority, Priority=safe_priority,
Conditions=[{ Conditions=[{
@ -823,7 +940,7 @@ def test_handle_listener_rules():
# test for invalid condition field_name # test for invalid condition field_name
safe_priority = 2 safe_priority = 2
with assert_raises(ClientError): with assert_raises(ClientError):
r = conn.create_rule( conn.create_rule(
ListenerArn=http_listener_arn, ListenerArn=http_listener_arn,
Priority=safe_priority, Priority=safe_priority,
Conditions=[{ Conditions=[{
@ -839,7 +956,7 @@ def test_handle_listener_rules():
# test for emptry condition value # test for emptry condition value
safe_priority = 2 safe_priority = 2
with assert_raises(ClientError): with assert_raises(ClientError):
r = conn.create_rule( conn.create_rule(
ListenerArn=http_listener_arn, ListenerArn=http_listener_arn,
Priority=safe_priority, Priority=safe_priority,
Conditions=[{ Conditions=[{
@ -855,7 +972,7 @@ def test_handle_listener_rules():
# test for multiple condition value # test for multiple condition value
safe_priority = 2 safe_priority = 2
with assert_raises(ClientError): with assert_raises(ClientError):
r = conn.create_rule( conn.create_rule(
ListenerArn=http_listener_arn, ListenerArn=http_listener_arn,
Priority=safe_priority, Priority=safe_priority,
Conditions=[{ Conditions=[{
@ -875,10 +992,17 @@ def test_describe_invalid_target_group():
conn = boto3.client('elbv2', region_name='us-east-1') conn = boto3.client('elbv2', region_name='us-east-1')
ec2 = boto3.resource('ec2', region_name='us-east-1') ec2 = boto3.resource('ec2', region_name='us-east-1')
security_group = ec2.create_security_group(GroupName='a-security-group', Description='First One') security_group = ec2.create_security_group(
GroupName='a-security-group', Description='First One')
vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default') vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default')
subnet1 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone='us-east-1a') subnet1 = ec2.create_subnet(
subnet2 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone='us-east-1b') VpcId=vpc.id,
CidrBlock='172.28.7.192/26',
AvailabilityZone='us-east-1a')
subnet2 = ec2.create_subnet(
VpcId=vpc.id,
CidrBlock='172.28.7.192/26',
AvailabilityZone='us-east-1b')
response = conn.create_load_balancer( response = conn.create_load_balancer(
Name='my-lb', Name='my-lb',
@ -887,7 +1011,7 @@ def test_describe_invalid_target_group():
Scheme='internal', Scheme='internal',
Tags=[{'Key': 'key_name', 'Value': 'a_value'}]) Tags=[{'Key': 'key_name', 'Value': 'a_value'}])
load_balancer_arn = response.get('LoadBalancers')[0].get('LoadBalancerArn') response.get('LoadBalancers')[0].get('LoadBalancerArn')
response = conn.create_target_group( response = conn.create_target_group(
Name='a-target', Name='a-target',
@ -902,7 +1026,6 @@ def test_describe_invalid_target_group():
HealthyThresholdCount=5, HealthyThresholdCount=5,
UnhealthyThresholdCount=2, UnhealthyThresholdCount=2,
Matcher={'HttpCode': '200'}) Matcher={'HttpCode': '200'})
target_group = response.get('TargetGroups')[0]
# Check error raises correctly # Check error raises correctly
with assert_raises(ClientError): with assert_raises(ClientError):