This commit is contained in:
Steve Pulec 2017-05-10 21:58:42 -04:00
parent 408a70992c
commit 0adebeed24
36 changed files with 669 additions and 58 deletions

View File

@ -1,5 +1,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from boto.ec2.blockdevicemapping import BlockDeviceType, BlockDeviceMapping from boto.ec2.blockdevicemapping import BlockDeviceType, BlockDeviceMapping
from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel from moto.core import BaseBackend, BaseModel
from moto.ec2 import ec2_backends from moto.ec2 import ec2_backends
from moto.elb import elb_backends from moto.elb import elb_backends
@ -284,8 +285,8 @@ class FakeAutoScalingGroup(BaseModel):
class AutoScalingBackend(BaseBackend): class AutoScalingBackend(BaseBackend):
def __init__(self, ec2_backend, elb_backend): def __init__(self, ec2_backend, elb_backend):
self.autoscaling_groups = {} self.autoscaling_groups = OrderedDict()
self.launch_configurations = {} self.launch_configurations = OrderedDict()
self.policies = {} self.policies = {}
self.ec2_backend = ec2_backend self.ec2_backend = ec2_backend
self.elb_backend = elb_backend self.elb_backend = elb_backend

View File

@ -40,11 +40,22 @@ class AutoScalingResponse(BaseResponse):
def describe_launch_configurations(self): def describe_launch_configurations(self):
names = self._get_multi_param('LaunchConfigurationNames.member') names = self._get_multi_param('LaunchConfigurationNames.member')
launch_configurations = self.autoscaling_backend.describe_launch_configurations( all_launch_configurations = self.autoscaling_backend.describe_launch_configurations(names)
names) marker = self._get_param('NextToken')
all_names = [lc.name for lc in all_launch_configurations]
if marker:
start = all_names.index(marker) + 1
else:
start = 0
max_records = self._get_param('MaxRecords', 50) # the default is 100, but using 50 to make testing easier
launch_configurations_resp = all_launch_configurations[start:start + max_records]
next_token = None
if len(all_launch_configurations) > start + max_records:
next_token = launch_configurations_resp[-1].name
template = self.response_template( template = self.response_template(
DESCRIBE_LAUNCH_CONFIGURATIONS_TEMPLATE) DESCRIBE_LAUNCH_CONFIGURATIONS_TEMPLATE)
return template.render(launch_configurations=launch_configurations) return template.render(launch_configurations=launch_configurations_resp, next_token=next_token)
def delete_launch_configuration(self): def delete_launch_configuration(self):
launch_configurations_name = self.querystring.get( launch_configurations_name = self.querystring.get(
@ -78,9 +89,22 @@ class AutoScalingResponse(BaseResponse):
def describe_auto_scaling_groups(self): def describe_auto_scaling_groups(self):
names = self._get_multi_param("AutoScalingGroupNames.member") names = self._get_multi_param("AutoScalingGroupNames.member")
groups = self.autoscaling_backend.describe_autoscaling_groups(names) token = self._get_param("NextToken")
all_groups = self.autoscaling_backend.describe_autoscaling_groups(names)
all_names = [group.name for group in all_groups]
if token:
start = all_names.index(token) + 1
else:
start = 0
max_records = self._get_param("MaxRecords", 50)
if max_records > 100:
raise ValueError
groups = all_groups[start:start + max_records]
next_token = None
if max_records and len(all_groups) > start + max_records:
next_token = groups[-1].name
template = self.response_template(DESCRIBE_AUTOSCALING_GROUPS_TEMPLATE) template = self.response_template(DESCRIBE_AUTOSCALING_GROUPS_TEMPLATE)
return template.render(groups=groups) return template.render(groups=groups, next_token=next_token)
def update_auto_scaling_group(self): def update_auto_scaling_group(self):
self.autoscaling_backend.update_autoscaling_group( self.autoscaling_backend.update_autoscaling_group(
@ -239,6 +263,9 @@ DESCRIBE_LAUNCH_CONFIGURATIONS_TEMPLATE = """<DescribeLaunchConfigurationsRespon
</member> </member>
{% endfor %} {% endfor %}
</LaunchConfigurations> </LaunchConfigurations>
{% if next_token %}
<NextToken>{{ next_token }}</NextToken>
{% endif %}
</DescribeLaunchConfigurationsResult> </DescribeLaunchConfigurationsResult>
<ResponseMetadata> <ResponseMetadata>
<RequestId>d05a22f8-b690-11e2-bf8e-2113fEXAMPLE</RequestId> <RequestId>d05a22f8-b690-11e2-bf8e-2113fEXAMPLE</RequestId>
@ -331,6 +358,9 @@ DESCRIBE_AUTOSCALING_GROUPS_TEMPLATE = """<DescribeAutoScalingGroupsResponse xml
</member> </member>
{% endfor %} {% endfor %}
</AutoScalingGroups> </AutoScalingGroups>
{% if next_token %}
<NextToken>{{ next_token }}</NextToken>
{% endif %}
</DescribeAutoScalingGroupsResult> </DescribeAutoScalingGroupsResult>
<ResponseMetadata> <ResponseMetadata>
<RequestId>0f02a07d-b677-11e2-9eb0-dd50EXAMPLE</RequestId> <RequestId>0f02a07d-b677-11e2-9eb0-dd50EXAMPLE</RequestId>

View File

@ -4,6 +4,7 @@ import json
import uuid import uuid
import boto.cloudformation import boto.cloudformation
from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel from moto.core import BaseBackend, BaseModel
from .parsing import ResourceMap, OutputMap from .parsing import ResourceMap, OutputMap
@ -121,7 +122,7 @@ class FakeEvent(BaseModel):
class CloudFormationBackend(BaseBackend): class CloudFormationBackend(BaseBackend):
def __init__(self): def __init__(self):
self.stacks = {} self.stacks = OrderedDict()
self.deleted_stacks = {} self.deleted_stacks = {}
def create_stack(self, name, template, parameters, region_name, notification_arns=None, tags=None, role_arn=None): def create_stack(self, name, template, parameters, region_name, notification_arns=None, tags=None, role_arn=None):
@ -152,7 +153,7 @@ class CloudFormationBackend(BaseBackend):
return [stack] return [stack]
raise ValidationError(name_or_stack_id) raise ValidationError(name_or_stack_id)
else: else:
return stacks return list(stacks)
def list_stacks(self): def list_stacks(self):
return self.stacks.values() return self.stacks.values()

View File

@ -72,10 +72,20 @@ class CloudFormationResponse(BaseResponse):
stack_name_or_id = None stack_name_or_id = None
if self._get_param('StackName'): if self._get_param('StackName'):
stack_name_or_id = self.querystring.get('StackName')[0] stack_name_or_id = self.querystring.get('StackName')[0]
token = self._get_param('NextToken')
stacks = self.cloudformation_backend.describe_stacks(stack_name_or_id) stacks = self.cloudformation_backend.describe_stacks(stack_name_or_id)
stack_ids = [stack.stack_id for stack in stacks]
if token:
start = stack_ids.index(token) + 1
else:
start = 0
max_results = 50 # using this to mske testing of paginated stacks more convenient than default 1 MB
stacks_resp = stacks[start:start + max_results]
next_token = None
if len(stacks) > (start + max_results):
next_token = stacks_resp[-1].stack_id
template = self.response_template(DESCRIBE_STACKS_TEMPLATE) template = self.response_template(DESCRIBE_STACKS_TEMPLATE)
return template.render(stacks=stacks) return template.render(stacks=stacks_resp, next_token=next_token)
def describe_stack_resource(self): def describe_stack_resource(self):
stack_name = self._get_param('StackName') stack_name = self._get_param('StackName')
@ -270,6 +280,9 @@ DESCRIBE_STACKS_TEMPLATE = """<DescribeStacksResponse>
</member> </member>
{% endfor %} {% endfor %}
</Stacks> </Stacks>
{% if next_token %}
<NextToken>{{ next_token }}</NextToken>
{% endif %}
</DescribeStacksResult> </DescribeStacksResult>
</DescribeStacksResponse>""" </DescribeStacksResponse>"""

View File

@ -2,6 +2,7 @@ from __future__ import unicode_literals
import datetime import datetime
import boto.datapipeline import boto.datapipeline
from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel from moto.core import BaseBackend, BaseModel
from .utils import get_random_pipeline_id, remove_capitalization_of_dict_keys from .utils import get_random_pipeline_id, remove_capitalization_of_dict_keys
@ -111,7 +112,7 @@ class Pipeline(BaseModel):
class DataPipelineBackend(BaseBackend): class DataPipelineBackend(BaseBackend):
def __init__(self): def __init__(self):
self.pipelines = {} self.pipelines = OrderedDict()
def create_pipeline(self, name, unique_id, **kwargs): def create_pipeline(self, name, unique_id, **kwargs):
pipeline = Pipeline(name, unique_id, **kwargs) pipeline = Pipeline(name, unique_id, **kwargs)

View File

@ -31,12 +31,25 @@ class DataPipelineResponse(BaseResponse):
}) })
def list_pipelines(self): def list_pipelines(self):
pipelines = self.datapipeline_backend.list_pipelines() pipelines = list(self.datapipeline_backend.list_pipelines())
pipeline_ids = [pipeline.pipeline_id for pipeline in pipelines]
max_pipelines = 50
marker = self.parameters.get('marker')
if marker:
start = pipeline_ids.index(marker) + 1
else:
start = 0
pipelines_resp = pipelines[start:start + max_pipelines]
has_more_results = False
marker = None
if start + max_pipelines < len(pipeline_ids) - 1:
has_more_results = True
marker = pipelines_resp[-1].pipeline_id
return json.dumps({ return json.dumps({
"hasMoreResults": False, "hasMoreResults": has_more_results,
"marker": None, "marker": marker,
"pipelineIdList": [ "pipelineIdList": [
pipeline.to_meta_json() for pipeline in pipelines pipeline.to_meta_json() for pipeline in pipelines_resp
] ]
}) })

View File

@ -200,6 +200,11 @@ class Table(BaseModel):
self.global_indexes = global_indexes if global_indexes else [] self.global_indexes = global_indexes if global_indexes else []
self.created_at = datetime.datetime.utcnow() self.created_at = datetime.datetime.utcnow()
self.items = defaultdict(dict) self.items = defaultdict(dict)
self.table_arn = self._generate_arn(table_name)
self.tags = []
def _generate_arn(self, name):
return 'arn:aws:dynamodb:us-east-1:123456789011:table/' + name
def describe(self, base_key='TableDescription'): def describe(self, base_key='TableDescription'):
results = { results = {
@ -209,11 +214,12 @@ class Table(BaseModel):
'TableSizeBytes': 0, 'TableSizeBytes': 0,
'TableName': self.name, 'TableName': self.name,
'TableStatus': 'ACTIVE', 'TableStatus': 'ACTIVE',
'TableArn': self.table_arn,
'KeySchema': self.schema, 'KeySchema': self.schema,
'ItemCount': len(self), 'ItemCount': len(self),
'CreationDateTime': unix_time(self.created_at), 'CreationDateTime': unix_time(self.created_at),
'GlobalSecondaryIndexes': [index for index in self.global_indexes], 'GlobalSecondaryIndexes': [index for index in self.global_indexes],
'LocalSecondaryIndexes': [index for index in self.indexes] 'LocalSecondaryIndexes': [index for index in self.indexes],
} }
} }
return results return results
@ -505,6 +511,18 @@ class DynamoDBBackend(BaseBackend):
def delete_table(self, name): def delete_table(self, name):
return self.tables.pop(name, None) return self.tables.pop(name, None)
def tag_resource(self, table_arn, tags):
for table in self.tables:
if self.tables[table].table_arn == table_arn:
self.tables[table].tags.extend(tags)
def list_tags_of_resource(self, table_arn):
required_table = None
for table in self.tables:
if self.tables[table].table_arn == table_arn:
required_table = self.tables[table]
return required_table.tags
def update_table_throughput(self, name, throughput): def update_table_throughput(self, name, throughput):
table = self.tables[name] table = self.tables[name]
table.throughput = throughput table.throughput = throughput

View File

@ -73,7 +73,7 @@ class DynamoHandler(BaseResponse):
def list_tables(self): def list_tables(self):
body = self.body body = self.body
limit = body.get('Limit') limit = body.get('Limit', 100)
if body.get("ExclusiveStartTableName"): if body.get("ExclusiveStartTableName"):
last = body.get("ExclusiveStartTableName") last = body.get("ExclusiveStartTableName")
start = list(dynamodb_backend2.tables.keys()).index(last) + 1 start = list(dynamodb_backend2.tables.keys()).index(last) + 1
@ -124,6 +124,35 @@ class DynamoHandler(BaseResponse):
er = 'com.amazonaws.dynamodb.v20111205#ResourceNotFoundException' er = 'com.amazonaws.dynamodb.v20111205#ResourceNotFoundException'
return self.error(er) return self.error(er)
def tag_resource(self):
tags = self.body['Tags']
table_arn = self.body['ResourceArn']
dynamodb_backend2.tag_resource(table_arn, tags)
return json.dumps({})
def list_tags_of_resource(self):
try:
table_arn = self.body['ResourceArn']
all_tags = dynamodb_backend2.list_tags_of_resource(table_arn)
all_tag_keys = [tag['Key'] for tag in all_tags]
marker = self.body.get('NextToken')
if marker:
start = all_tag_keys.index(marker) + 1
else:
start = 0
max_items = 10 # there is no default, but using 10 to make testing easier
tags_resp = all_tags[start:start + max_items]
next_marker = None
if len(all_tags) > start + max_items:
next_marker = tags_resp[-1]['Key']
if next_marker:
return json.dumps({'Tags': tags_resp,
'NextToken': next_marker})
return json.dumps({'Tags': tags_resp})
except AttributeError:
er = 'com.amazonaws.dynamodb.v20111205#ResourceNotFoundException'
return self.error(er)
def update_table(self): def update_table(self):
name = self.body['TableName'] name = self.body['TableName']
if 'GlobalSecondaryIndexUpdates' in self.body: if 'GlobalSecondaryIndexUpdates' in self.body:

View File

@ -12,6 +12,7 @@ from boto.ec2.blockdevicemapping import BlockDeviceMapping, BlockDeviceType
from boto.ec2.spotinstancerequest import SpotInstanceRequest as BotoSpotRequest from boto.ec2.spotinstancerequest import SpotInstanceRequest as BotoSpotRequest
from boto.ec2.launchspecification import LaunchSpecification from boto.ec2.launchspecification import LaunchSpecification
from moto.compat import OrderedDict
from moto.core import BaseBackend from moto.core import BaseBackend
from moto.core.models import Model, BaseModel from moto.core.models import Model, BaseModel
from moto.core.utils import iso_8601_datetime_with_milliseconds, camelcase_to_underscores from moto.core.utils import iso_8601_datetime_with_milliseconds, camelcase_to_underscores
@ -618,7 +619,7 @@ class Instance(TaggedEC2Resource, BotoInstance):
class InstanceBackend(object): class InstanceBackend(object):
def __init__(self): def __init__(self):
self.reservations = {} self.reservations = OrderedDict()
super(InstanceBackend, self).__init__() super(InstanceBackend, self).__init__()
def get_instance(self, instance_id): def get_instance(self, instance_id):
@ -1049,12 +1050,22 @@ class AmiBackend(object):
self.amis[ami_id] = ami self.amis[ami_id] = ami
return ami return ami
def describe_images(self, ami_ids=(), filters=None): def describe_images(self, ami_ids=(), filters=None, exec_users=None):
images = []
if exec_users:
for ami_id in self.amis:
found = False
for user_id in exec_users:
if user_id in self.amis[ami_id].launch_permission_users:
found = True
if found:
images.append(self.amis[ami_id])
if images == []:
return images
if filters: if filters:
images = self.amis.values() images = images or self.amis.values()
return generic_filter(filters, images) return generic_filter(filters, images)
else: else:
images = []
for ami_id in ami_ids: for ami_id in ami_ids:
if ami_id in self.amis: if ami_id in self.amis:
images.append(self.amis[ami_id]) images.append(self.amis[ami_id])
@ -1766,6 +1777,9 @@ class Snapshot(TaggedEC2Resource):
if filter_name == 'encrypted': if filter_name == 'encrypted':
return str(self.encrypted).lower() return str(self.encrypted).lower()
if filter_name == 'status':
return self.status
filter_value = super(Snapshot, self).get_filter_value(filter_name) filter_value = super(Snapshot, self).get_filter_value(filter_name)
if filter_value is None: if filter_value is None:

View File

@ -1,7 +1,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from moto.core.responses import BaseResponse from moto.core.responses import BaseResponse
from moto.ec2.utils import instance_ids_from_querystring, image_ids_from_querystring, \ from moto.ec2.utils import instance_ids_from_querystring, image_ids_from_querystring, \
filters_from_querystring, sequence_from_querystring filters_from_querystring, sequence_from_querystring, executable_users_from_querystring
class AmisResponse(BaseResponse): class AmisResponse(BaseResponse):
@ -43,8 +43,9 @@ class AmisResponse(BaseResponse):
def describe_images(self): def describe_images(self):
ami_ids = image_ids_from_querystring(self.querystring) ami_ids = image_ids_from_querystring(self.querystring)
filters = filters_from_querystring(self.querystring) filters = filters_from_querystring(self.querystring)
exec_users = executable_users_from_querystring(self.querystring)
images = self.ec2_backend.describe_images( images = self.ec2_backend.describe_images(
ami_ids=ami_ids, filters=filters) ami_ids=ami_ids, filters=filters, exec_users=exec_users)
template = self.response_template(DESCRIBE_IMAGES_RESPONSE) template = self.response_template(DESCRIBE_IMAGES_RESPONSE)
return template.render(images=images) return template.render(images=images)

View File

@ -11,6 +11,7 @@ class InstanceResponse(BaseResponse):
def describe_instances(self): def describe_instances(self):
filter_dict = filters_from_querystring(self.querystring) filter_dict = filters_from_querystring(self.querystring)
instance_ids = instance_ids_from_querystring(self.querystring) instance_ids = instance_ids_from_querystring(self.querystring)
token = self._get_param("NextToken")
if instance_ids: if instance_ids:
reservations = self.ec2_backend.get_reservations_by_instance_ids( reservations = self.ec2_backend.get_reservations_by_instance_ids(
instance_ids, filters=filter_dict) instance_ids, filters=filter_dict)
@ -18,8 +19,18 @@ class InstanceResponse(BaseResponse):
reservations = self.ec2_backend.all_reservations( reservations = self.ec2_backend.all_reservations(
make_copy=True, filters=filter_dict) make_copy=True, filters=filter_dict)
reservation_ids = [reservation.id for reservation in reservations]
if token:
start = reservation_ids.index(token) + 1
else:
start = 0
max_results = int(self._get_param('MaxResults', 100))
reservations_resp = reservations[start:start + max_results]
next_token = None
if max_results and len(reservations) > (start + max_results):
next_token = reservations_resp[-1].id
template = self.response_template(EC2_DESCRIBE_INSTANCES) template = self.response_template(EC2_DESCRIBE_INSTANCES)
return template.render(reservations=reservations) return template.render(reservations=reservations_resp, next_token=next_token)
def run_instances(self): def run_instances(self):
min_count = int(self.querystring.get('MinCount', ['1'])[0]) min_count = int(self.querystring.get('MinCount', ['1'])[0])
@ -492,6 +503,9 @@ EC2_DESCRIBE_INSTANCES = """<DescribeInstancesResponse xmlns="http://ec2.amazona
</item> </item>
{% endfor %} {% endfor %}
</reservationSet> </reservationSet>
{% if next_token %}
<nextToken>{{ next_token }}</nextToken>
{% endif %}
</DescribeInstancesResponse>""" </DescribeInstancesResponse>"""
EC2_TERMINATE_INSTANCES = """ EC2_TERMINATE_INSTANCES = """

View File

@ -190,6 +190,14 @@ def image_ids_from_querystring(querystring_dict):
return image_ids return image_ids
def executable_users_from_querystring(querystring_dict):
user_ids = []
for key, value in querystring_dict.items():
if 'ExecutableBy' in key:
user_ids.append(value[0])
return user_ids
def route_table_ids_from_querystring(querystring_dict): def route_table_ids_from_querystring(querystring_dict):
route_table_ids = [] route_table_ids = []
for key, value in querystring_dict.items(): for key, value in querystring_dict.items():
@ -383,7 +391,8 @@ filter_dict_attribute_mapping = {
'private-ip-address': 'private_ip', 'private-ip-address': 'private_ip',
'ip-address': 'public_ip', 'ip-address': 'public_ip',
'availability-zone': 'placement', 'availability-zone': 'placement',
'architecture': 'architecture' 'architecture': 'architecture',
'image-id': 'image_id'
} }
@ -461,6 +470,9 @@ def filter_internet_gateways(igws, filter_dict):
def is_filter_matching(obj, filter, filter_value): def is_filter_matching(obj, filter, filter_value):
value = obj.get_filter_value(filter) value = obj.get_filter_value(filter)
if not filter_value:
return False
if isinstance(value, six.string_types): if isinstance(value, six.string_types):
if not isinstance(filter_value, list): if not isinstance(filter_value, list):
filter_value = [filter_value] filter_value = [filter_value]

View File

@ -12,6 +12,7 @@ from boto.ec2.elb.policies import (
Policies, Policies,
OtherPolicy, OtherPolicy,
) )
from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel from moto.core import BaseBackend, BaseModel
from moto.ec2.models import ec2_backends from moto.ec2.models import ec2_backends
from .exceptions import ( from .exceptions import (
@ -223,7 +224,7 @@ class ELBBackend(BaseBackend):
def __init__(self, region_name=None): def __init__(self, region_name=None):
self.region_name = region_name self.region_name = region_name
self.load_balancers = {} self.load_balancers = OrderedDict()
def reset(self): def reset(self):
region_name = self.region_name region_name = self.region_name

View File

@ -52,9 +52,21 @@ class ELBResponse(BaseResponse):
def describe_load_balancers(self): def describe_load_balancers(self):
names = self._get_multi_param("LoadBalancerNames.member") names = self._get_multi_param("LoadBalancerNames.member")
load_balancers = self.elb_backend.describe_load_balancers(names) all_load_balancers = list(self.elb_backend.describe_load_balancers(names))
marker = self._get_param('Marker')
all_names = [balancer.name for balancer in all_load_balancers]
if marker:
start = all_names.index(marker) + 1
else:
start = 0
page_size = self._get_param('PageSize', 50) # the default is 400, but using 50 to make testing easier
load_balancers_resp = all_load_balancers[start:start + page_size]
next_marker = None
if len(all_load_balancers) > start + page_size:
next_marker = load_balancers_resp[-1].name
template = self.response_template(DESCRIBE_LOAD_BALANCERS_TEMPLATE) template = self.response_template(DESCRIBE_LOAD_BALANCERS_TEMPLATE)
return template.render(load_balancers=load_balancers) return template.render(load_balancers=load_balancers_resp, marker=next_marker)
def delete_load_balancer_listeners(self): def delete_load_balancer_listeners(self):
load_balancer_name = self._get_param('LoadBalancerName') load_balancer_name = self._get_param('LoadBalancerName')
@ -493,6 +505,9 @@ DESCRIBE_LOAD_BALANCERS_TEMPLATE = """<DescribeLoadBalancersResponse xmlns="http
</member> </member>
{% endfor %} {% endfor %}
</LoadBalancerDescriptions> </LoadBalancerDescriptions>
{% if marker %}
<NextMarker>{{ marker }}</NextMarker>
{% endif %}
</DescribeLoadBalancersResult> </DescribeLoadBalancersResult>
<ResponseMetadata> <ResponseMetadata>
<RequestId>f9880f01-7852-629d-a6c3-3ae2-666a409287e6dc0c</RequestId> <RequestId>f9880f01-7852-629d-a6c3-3ae2-666a409287e6dc0c</RequestId>

View File

@ -6,7 +6,7 @@ import boto.emr
import pytz import pytz
from dateutil.parser import parse as dtparse from dateutil.parser import parse as dtparse
from moto.core import BaseBackend, BaseModel from moto.core import BaseBackend, BaseModel
from moto.emr.exceptions import EmrError
from .utils import random_instance_group_id, random_cluster_id, random_step_id from .utils import random_instance_group_id, random_cluster_id, random_step_id
@ -324,7 +324,9 @@ class ElasticMapReduceBackend(BaseBackend):
return step return step
def get_cluster(self, cluster_id): def get_cluster(self, cluster_id):
return self.clusters[cluster_id] if cluster_id in self.clusters:
return self.clusters[cluster_id]
raise EmrError('ResourceNotFoundException', '', 'error_json')
def get_instance_groups(self, instance_group_ids): def get_instance_groups(self, instance_group_ids):
return [ return [

View File

@ -269,7 +269,7 @@ class DeliveryStream(BaseModel):
class KinesisBackend(BaseBackend): class KinesisBackend(BaseBackend):
def __init__(self): def __init__(self):
self.streams = {} self.streams = OrderedDict()
self.delivery_streams = {} self.delivery_streams = {}
def create_stream(self, stream_name, shard_count, region): def create_stream(self, stream_name, shard_count, region):

View File

@ -35,10 +35,24 @@ class KinesisResponse(BaseResponse):
def list_streams(self): def list_streams(self):
streams = self.kinesis_backend.list_streams() streams = self.kinesis_backend.list_streams()
stream_names = [stream.stream_name for stream in streams]
max_streams = self._get_param('Limit', 10)
try:
token = self.parameters.get('ExclusiveStartStreamName')
except ValueError:
token = self._get_param('ExclusiveStartStreamName')
if token:
start = stream_names.index(token) + 1
else:
start = 0
streams_resp = stream_names[start:start + max_streams]
has_more_streams = False
if start + max_streams < len(stream_names):
has_more_streams = True
return json.dumps({ return json.dumps({
"HasMoreStreams": False, "HasMoreStreams": has_more_streams,
"StreamNames": [stream.stream_name for stream in streams], "StreamNames": streams_resp
}) })
def delete_stream(self): def delete_stream(self):

View File

@ -88,9 +88,21 @@ class RDSResponse(BaseResponse):
def describe_db_instances(self): def describe_db_instances(self):
db_instance_identifier = self._get_param('DBInstanceIdentifier') db_instance_identifier = self._get_param('DBInstanceIdentifier')
databases = self.backend.describe_databases(db_instance_identifier) all_instances = list(self.backend.describe_databases(db_instance_identifier))
marker = self._get_param('Marker')
all_ids = [instance.db_instance_identifier for instance in all_instances]
if marker:
start = all_ids.index(marker) + 1
else:
start = 0
page_size = self._get_param('MaxRecords', 50) # the default is 100, but using 50 to make testing easier
instances_resp = all_instances[start:start + page_size]
next_marker = None
if len(all_instances) > start + page_size:
next_marker = instances_resp[-1].db_instance_identifier
template = self.response_template(DESCRIBE_DATABASES_TEMPLATE) template = self.response_template(DESCRIBE_DATABASES_TEMPLATE)
return template.render(databases=databases) return template.render(databases=instances_resp, marker=next_marker)
def modify_db_instance(self): def modify_db_instance(self):
db_instance_identifier = self._get_param('DBInstanceIdentifier') db_instance_identifier = self._get_param('DBInstanceIdentifier')
@ -187,6 +199,9 @@ DESCRIBE_DATABASES_TEMPLATE = """<DescribeDBInstancesResponse xmlns="http://rds.
{{ database.to_xml() }} {{ database.to_xml() }}
{% endfor %} {% endfor %}
</DBInstances> </DBInstances>
{% if marker %}
<Marker>{{ marker }}</Marker>
{% endif %}
</DescribeDBInstancesResult> </DescribeDBInstancesResult>
<ResponseMetadata> <ResponseMetadata>
<RequestId>01b2685a-b978-11d3-f272-7cd6cce12cc5</RequestId> <RequestId>01b2685a-b978-11d3-f272-7cd6cce12cc5</RequestId>

View File

@ -7,6 +7,7 @@ import boto.rds2
from jinja2 import Template from jinja2 import Template
from re import compile as re_compile from re import compile as re_compile
from moto.cloudformation.exceptions import UnformattedGetAttTemplateException from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel from moto.core import BaseBackend, BaseModel
from moto.core.utils import get_random_hex from moto.core.utils import get_random_hex
from moto.ec2.models import ec2_backends from moto.ec2.models import ec2_backends
@ -586,7 +587,7 @@ class RDS2Backend(BaseBackend):
self.region = region self.region = region
self.arn_regex = re_compile( self.arn_regex = re_compile(
r'^arn:aws:rds:.*:[0-9]*:(db|es|og|pg|ri|secgrp|snapshot|subgrp):.*$') r'^arn:aws:rds:.*:[0-9]*:(db|es|og|pg|ri|secgrp|snapshot|subgrp):.*$')
self.databases = {} self.databases = OrderedDict()
self.db_parameter_groups = {} self.db_parameter_groups = {}
self.option_groups = {} self.option_groups = {}
self.security_groups = {} self.security_groups = {}

View File

@ -114,9 +114,21 @@ class RDS2Response(BaseResponse):
def describe_db_instances(self): def describe_db_instances(self):
db_instance_identifier = self._get_param('DBInstanceIdentifier') db_instance_identifier = self._get_param('DBInstanceIdentifier')
databases = self.backend.describe_databases(db_instance_identifier) all_instances = list(self.backend.describe_databases(db_instance_identifier))
marker = self._get_param('Marker')
all_ids = [instance.db_instance_identifier for instance in all_instances]
if marker:
start = all_ids.index(marker) + 1
else:
start = 0
page_size = self._get_param('MaxRecords', 50) # the default is 100, but using 50 to make testing easier
instances_resp = all_instances[start:start + page_size]
next_marker = None
if len(all_instances) > start + page_size:
next_marker = instances_resp[-1].db_instance_identifier
template = self.response_template(DESCRIBE_DATABASES_TEMPLATE) template = self.response_template(DESCRIBE_DATABASES_TEMPLATE)
return template.render(databases=databases) return template.render(databases=instances_resp, marker=next_marker)
def modify_db_instance(self): def modify_db_instance(self):
db_instance_identifier = self._get_param('DBInstanceIdentifier') db_instance_identifier = self._get_param('DBInstanceIdentifier')
@ -348,6 +360,9 @@ DESCRIBE_DATABASES_TEMPLATE = """<DescribeDBInstancesResponse xmlns="http://rds.
{{ database.to_xml() }} {{ database.to_xml() }}
{%- endfor -%} {%- endfor -%}
</DBInstances> </DBInstances>
{% if marker %}
<Marker>{{ marker }}</Marker>
{% endif %}
</DescribeDBInstancesResult> </DescribeDBInstancesResult>
<ResponseMetadata> <ResponseMetadata>
<RequestId>523e3218-afc7-11c3-90f5-f90431260ab4</RequestId> <RequestId>523e3218-afc7-11c3-90f5-f90431260ab4</RequestId>

View File

@ -5,6 +5,7 @@ from setuptools import setup, find_packages
install_requires = [ install_requires = [
"Jinja2>=2.8", "Jinja2>=2.8",
"boto>=2.36.0", "boto>=2.36.0",
"boto3>=1.2.1",
"cookies", "cookies",
"requests>=2.0", "requests>=2.0",
"xmltodict", "xmltodict",

View File

@ -115,6 +115,30 @@ def test_create_autoscaling_groups_defaults():
list(group.tags).should.equal([]) list(group.tags).should.equal([])
@mock_autoscaling
def test_list_many_autoscaling_groups():
conn = boto3.client('autoscaling', region_name='us-east-1')
conn.create_launch_configuration(LaunchConfigurationName='TestLC')
for i in range(51):
conn.create_auto_scaling_group(AutoScalingGroupName='TestGroup%d' % i,
MinSize=1,
MaxSize=2,
LaunchConfigurationName='TestLC')
response = conn.describe_auto_scaling_groups()
groups = response["AutoScalingGroups"]
marker = response["NextToken"]
groups.should.have.length_of(50)
marker.should.equal(groups[-1]['AutoScalingGroupName'])
response2 = conn.describe_auto_scaling_groups(NextToken=marker)
groups.extend(response2["AutoScalingGroups"])
groups.should.have.length_of(51)
assert 'NextToken' not in response2.keys()
@mock_autoscaling_deprecated @mock_autoscaling_deprecated
def test_autoscaling_group_describe_filter(): def test_autoscaling_group_describe_filter():
conn = boto.connect_autoscale() conn = boto.connect_autoscale()

View File

@ -1,11 +1,13 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import boto import boto
import boto3
from boto.ec2.autoscale.launchconfig import LaunchConfiguration from boto.ec2.autoscale.launchconfig import LaunchConfiguration
from boto.ec2.blockdevicemapping import BlockDeviceType, BlockDeviceMapping from boto.ec2.blockdevicemapping import BlockDeviceType, BlockDeviceMapping
import sure # noqa import sure # noqa
from moto import mock_autoscaling_deprecated from moto import mock_autoscaling_deprecated
from moto import mock_autoscaling
from tests.helpers import requires_boto_gte from tests.helpers import requires_boto_gte
@ -208,6 +210,25 @@ def test_launch_configuration_describe_filter():
conn.get_all_launch_configurations().should.have.length_of(3) conn.get_all_launch_configurations().should.have.length_of(3)
@mock_autoscaling
def test_launch_configuration_describe_paginated():
conn = boto3.client('autoscaling', region_name='us-east-1')
for i in range(51):
conn.create_launch_configuration(LaunchConfigurationName='TestLC%d' % i)
response = conn.describe_launch_configurations()
lcs = response["LaunchConfigurations"]
marker = response["NextToken"]
lcs.should.have.length_of(50)
marker.should.equal(lcs[-1]['LaunchConfigurationName'])
response2 = conn.describe_launch_configurations(NextToken=marker)
lcs.extend(response2["LaunchConfigurations"])
lcs.should.have.length_of(51)
assert 'NextToken' not in response2.keys()
@mock_autoscaling_deprecated @mock_autoscaling_deprecated
def test_launch_configuration_delete(): def test_launch_configuration_delete():
conn = boto.connect_autoscale() conn = boto.connect_autoscale()

View File

@ -144,6 +144,26 @@ def test_create_stack_from_s3_url():
'TemplateBody'].should.equal(dummy_template) 'TemplateBody'].should.equal(dummy_template)
@mock_cloudformation
def test_describe_stack_pagination():
conn = boto3.client('cloudformation', region_name='us-east-1')
for i in range(100):
conn.create_stack(
StackName="test_stack",
TemplateBody=dummy_template_json,
)
resp = conn.describe_stacks()
stacks = resp['Stacks']
stacks.should.have.length_of(50)
next_token = resp['NextToken']
next_token.should_not.be.none
resp2 = conn.describe_stacks(NextToken=next_token)
stacks.extend(resp2['Stacks'])
stacks.should.have.length_of(100)
assert 'NextToken' not in resp2.keys()
@mock_cloudformation @mock_cloudformation
def test_describe_stack_resources(): def test_describe_stack_resources():
cf_conn = boto3.client('cloudformation', region_name='us-east-1') cf_conn = boto3.client('cloudformation', region_name='us-east-1')

View File

@ -170,6 +170,19 @@ def test_listing_pipelines():
}) })
@mock_datapipeline_deprecated
def test_listing_paginated_pipelines():
conn = boto.datapipeline.connect_to_region("us-west-2")
for i in range(100):
conn.create_pipeline("mypipeline%d" % i, "some-unique-id%d" % i)
response = conn.list_pipelines()
response["hasMoreResults"].should.be(True)
response["marker"].should.equal(response["pipelineIdList"][-1]['id'])
response["pipelineIdList"].should.have.length_of(50)
# testing a helper function # testing a helper function
def test_remove_capitalization_of_dict_keys(): def test_remove_capitalization_of_dict_keys():
result = remove_capitalization_of_dict_keys( result = remove_capitalization_of_dict_keys(

View File

@ -2,11 +2,13 @@ from __future__ import unicode_literals, print_function
import six import six
import boto import boto
import boto3
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
from moto.dynamodb2 import dynamodb_backend2 from moto.dynamodb2 import dynamodb_backend2
from boto.exception import JSONResponseError from boto.exception import JSONResponseError
from botocore.exceptions import ClientError
from tests.helpers import requires_boto_gte from tests.helpers import requires_boto_gte
import tests.backport_assert_raises import tests.backport_assert_raises
from nose.tools import assert_raises from nose.tools import assert_raises
@ -64,3 +66,86 @@ def test_describe_missing_table():
aws_secret_access_key="sk") aws_secret_access_key="sk")
with assert_raises(JSONResponseError): with assert_raises(JSONResponseError):
conn.describe_table('messages') conn.describe_table('messages')
@requires_boto_gte("2.9")
@mock_dynamodb2
def test_list_table_tags():
name = 'TestTable'
conn = boto3.client('dynamodb',
region_name='us-west-2',
aws_access_key_id="ak",
aws_secret_access_key="sk")
conn.create_table(TableName=name,
KeySchema=[{'AttributeName':'id','KeyType':'HASH'}],
AttributeDefinitions=[{'AttributeName':'id','AttributeType':'S'}],
ProvisionedThroughput={'ReadCapacityUnits':5,'WriteCapacityUnits':5})
table_description = conn.describe_table(TableName=name)
arn = table_description['Table']['TableArn']
tags = [{'Key':'TestTag', 'Value': 'TestValue'}]
conn.tag_resource(ResourceArn=arn,
Tags=tags)
resp = conn.list_tags_of_resource(ResourceArn=arn)
assert resp["Tags"] == tags
@requires_boto_gte("2.9")
@mock_dynamodb2
def test_list_table_tags_empty():
name = 'TestTable'
conn = boto3.client('dynamodb',
region_name='us-west-2',
aws_access_key_id="ak",
aws_secret_access_key="sk")
conn.create_table(TableName=name,
KeySchema=[{'AttributeName':'id','KeyType':'HASH'}],
AttributeDefinitions=[{'AttributeName':'id','AttributeType':'S'}],
ProvisionedThroughput={'ReadCapacityUnits':5,'WriteCapacityUnits':5})
table_description = conn.describe_table(TableName=name)
arn = table_description['Table']['TableArn']
tags = [{'Key':'TestTag', 'Value': 'TestValue'}]
# conn.tag_resource(ResourceArn=arn,
# Tags=tags)
resp = conn.list_tags_of_resource(ResourceArn=arn)
assert resp["Tags"] == []
@requires_boto_gte("2.9")
@mock_dynamodb2
def test_list_table_tags_paginated():
name = 'TestTable'
conn = boto3.client('dynamodb',
region_name='us-west-2',
aws_access_key_id="ak",
aws_secret_access_key="sk")
conn.create_table(TableName=name,
KeySchema=[{'AttributeName':'id','KeyType':'HASH'}],
AttributeDefinitions=[{'AttributeName':'id','AttributeType':'S'}],
ProvisionedThroughput={'ReadCapacityUnits':5,'WriteCapacityUnits':5})
table_description = conn.describe_table(TableName=name)
arn = table_description['Table']['TableArn']
for i in range(11):
tags = [{'Key':'TestTag%d' % i, 'Value': 'TestValue'}]
conn.tag_resource(ResourceArn=arn,
Tags=tags)
resp = conn.list_tags_of_resource(ResourceArn=arn)
assert len(resp["Tags"]) == 10
assert 'NextToken' in resp.keys()
resp2 = conn.list_tags_of_resource(ResourceArn=arn,
NextToken=resp['NextToken'])
assert len(resp2["Tags"]) == 1
assert 'NextToken' not in resp2.keys()
@requires_boto_gte("2.9")
@mock_dynamodb2
def test_list_not_found_table_tags():
conn = boto3.client('dynamodb',
region_name='us-west-2',
aws_access_key_id="ak",
aws_secret_access_key="sk")
arn = 'DymmyArn'
try:
conn.list_tags_of_resource(ResourceArn=arn)
except ClientError as exception:
assert exception.response['Error']['Code'] == "ResourceNotFoundException"

View File

@ -77,13 +77,14 @@ def test_create_table():
'TableSizeBytes': 0, 'TableSizeBytes': 0,
'TableName': 'messages', 'TableName': 'messages',
'TableStatus': 'ACTIVE', 'TableStatus': 'ACTIVE',
'TableArn': 'arn:aws:dynamodb:us-east-1:123456789011:table/messages',
'KeySchema': [ 'KeySchema': [
{'KeyType': 'HASH', 'AttributeName': 'forum_name'}, {'KeyType': 'HASH', 'AttributeName': 'forum_name'},
{'KeyType': 'RANGE', 'AttributeName': 'subject'} {'KeyType': 'RANGE', 'AttributeName': 'subject'}
], ],
'LocalSecondaryIndexes': [], 'LocalSecondaryIndexes': [],
'ItemCount': 0, 'CreationDateTime': 1326499200.0, 'ItemCount': 0, 'CreationDateTime': 1326499200.0,
'GlobalSecondaryIndexes': [], 'GlobalSecondaryIndexes': []
} }
} }
table.describe().should.equal(expected) table.describe().should.equal(expected)
@ -109,6 +110,7 @@ def test_create_table_with_local_index():
'TableSizeBytes': 0, 'TableSizeBytes': 0,
'TableName': 'messages', 'TableName': 'messages',
'TableStatus': 'ACTIVE', 'TableStatus': 'ACTIVE',
'TableArn': 'arn:aws:dynamodb:us-east-1:123456789011:table/messages',
'KeySchema': [ 'KeySchema': [
{'KeyType': 'HASH', 'AttributeName': 'forum_name'}, {'KeyType': 'HASH', 'AttributeName': 'forum_name'},
{'KeyType': 'RANGE', 'AttributeName': 'subject'} {'KeyType': 'RANGE', 'AttributeName': 'subject'}
@ -125,7 +127,7 @@ def test_create_table_with_local_index():
], ],
'ItemCount': 0, 'ItemCount': 0,
'CreationDateTime': 1326499200.0, 'CreationDateTime': 1326499200.0,
'GlobalSecondaryIndexes': [], 'GlobalSecondaryIndexes': []
} }
} }
table.describe().should.equal(expected) table.describe().should.equal(expected)

View File

@ -44,6 +44,7 @@ def test_create_table():
'TableSizeBytes': 0, 'TableSizeBytes': 0,
'TableName': 'messages', 'TableName': 'messages',
'TableStatus': 'ACTIVE', 'TableStatus': 'ACTIVE',
'TableArn': 'arn:aws:dynamodb:us-east-1:123456789011:table/messages',
'KeySchema': [ 'KeySchema': [
{'KeyType': 'HASH', 'AttributeName': 'forum_name'} {'KeyType': 'HASH', 'AttributeName': 'forum_name'}
], ],

View File

@ -4,17 +4,18 @@ import tests.backport_assert_raises # noqa
from nose.tools import assert_raises from nose.tools import assert_raises
import boto import boto
import boto3
import boto.ec2 import boto.ec2
import boto3 import boto3
from boto.exception import EC2ResponseError, EC2ResponseError from boto.exception import EC2ResponseError, EC2ResponseError
import sure # noqa import sure # noqa
from moto import mock_emr_deprecated, mock_ec2 from moto import mock_ec2_deprecated, mock_ec2
from tests.helpers import requires_boto_gte from tests.helpers import requires_boto_gte
@mock_emr_deprecated @mock_ec2_deprecated
def test_ami_create_and_delete(): def test_ami_create_and_delete():
conn = boto.connect_ec2('the_key', 'the_secret') conn = boto.connect_ec2('the_key', 'the_secret')
reservation = conn.run_instances('ami-1234abcd') reservation = conn.run_instances('ami-1234abcd')
@ -75,7 +76,7 @@ def test_ami_create_and_delete():
@requires_boto_gte("2.14.0") @requires_boto_gte("2.14.0")
@mock_emr_deprecated @mock_ec2_deprecated
def test_ami_copy(): def test_ami_copy():
conn = boto.ec2.connect_to_region("us-west-1") conn = boto.ec2.connect_to_region("us-west-1")
reservation = conn.run_instances('ami-1234abcd') reservation = conn.run_instances('ami-1234abcd')
@ -134,7 +135,7 @@ def test_ami_copy():
cm.exception.request_id.should_not.be.none cm.exception.request_id.should_not.be.none
@mock_emr_deprecated @mock_ec2_deprecated
def test_ami_tagging(): def test_ami_tagging():
conn = boto.connect_vpc('the_key', 'the_secret') conn = boto.connect_vpc('the_key', 'the_secret')
reservation = conn.run_instances('ami-1234abcd') reservation = conn.run_instances('ami-1234abcd')
@ -161,7 +162,7 @@ def test_ami_tagging():
image.tags["a key"].should.equal("some value") image.tags["a key"].should.equal("some value")
@mock_emr_deprecated @mock_ec2_deprecated
def test_ami_create_from_missing_instance(): def test_ami_create_from_missing_instance():
conn = boto.connect_ec2('the_key', 'the_secret') conn = boto.connect_ec2('the_key', 'the_secret')
args = ["i-abcdefg", "test-ami", "this is a test ami"] args = ["i-abcdefg", "test-ami", "this is a test ami"]
@ -173,7 +174,7 @@ def test_ami_create_from_missing_instance():
cm.exception.request_id.should_not.be.none cm.exception.request_id.should_not.be.none
@mock_emr_deprecated @mock_ec2_deprecated
def test_ami_pulls_attributes_from_instance(): def test_ami_pulls_attributes_from_instance():
conn = boto.connect_ec2('the_key', 'the_secret') conn = boto.connect_ec2('the_key', 'the_secret')
reservation = conn.run_instances('ami-1234abcd') reservation = conn.run_instances('ami-1234abcd')
@ -185,7 +186,7 @@ def test_ami_pulls_attributes_from_instance():
image.kernel_id.should.equal('test-kernel') image.kernel_id.should.equal('test-kernel')
@mock_emr_deprecated @mock_ec2_deprecated
def test_ami_filters(): def test_ami_filters():
conn = boto.connect_ec2('the_key', 'the_secret') conn = boto.connect_ec2('the_key', 'the_secret')
@ -242,7 +243,7 @@ def test_ami_filters():
set([ami.id for ami in amis_by_nonpublic]).should.equal(set([imageA.id])) set([ami.id for ami in amis_by_nonpublic]).should.equal(set([imageA.id]))
@mock_emr_deprecated @mock_ec2_deprecated
def test_ami_filtering_via_tag(): def test_ami_filtering_via_tag():
conn = boto.connect_vpc('the_key', 'the_secret') conn = boto.connect_vpc('the_key', 'the_secret')
@ -268,7 +269,7 @@ def test_ami_filtering_via_tag():
set([ami.id for ami in amis_by_tagB]).should.equal(set([imageB.id])) set([ami.id for ami in amis_by_tagB]).should.equal(set([imageB.id]))
@mock_emr_deprecated @mock_ec2_deprecated
def test_getting_missing_ami(): def test_getting_missing_ami():
conn = boto.connect_ec2('the_key', 'the_secret') conn = boto.connect_ec2('the_key', 'the_secret')
@ -279,7 +280,7 @@ def test_getting_missing_ami():
cm.exception.request_id.should_not.be.none cm.exception.request_id.should_not.be.none
@mock_emr_deprecated @mock_ec2_deprecated
def test_getting_malformed_ami(): def test_getting_malformed_ami():
conn = boto.connect_ec2('the_key', 'the_secret') conn = boto.connect_ec2('the_key', 'the_secret')
@ -290,7 +291,7 @@ def test_getting_malformed_ami():
cm.exception.request_id.should_not.be.none cm.exception.request_id.should_not.be.none
@mock_emr_deprecated @mock_ec2_deprecated
def test_ami_attribute_group_permissions(): def test_ami_attribute_group_permissions():
conn = boto.connect_ec2('the_key', 'the_secret') conn = boto.connect_ec2('the_key', 'the_secret')
reservation = conn.run_instances('ami-1234abcd') reservation = conn.run_instances('ami-1234abcd')
@ -350,7 +351,7 @@ def test_ami_attribute_group_permissions():
**REMOVE_GROUP_ARGS).should_not.throw(EC2ResponseError) **REMOVE_GROUP_ARGS).should_not.throw(EC2ResponseError)
@mock_emr_deprecated @mock_ec2_deprecated
def test_ami_attribute_user_permissions(): def test_ami_attribute_user_permissions():
conn = boto.connect_ec2('the_key', 'the_secret') conn = boto.connect_ec2('the_key', 'the_secret')
reservation = conn.run_instances('ami-1234abcd') reservation = conn.run_instances('ami-1234abcd')
@ -422,7 +423,107 @@ def test_ami_attribute_user_permissions():
**REMOVE_USERS_ARGS).should_not.throw(EC2ResponseError) **REMOVE_USERS_ARGS).should_not.throw(EC2ResponseError)
@mock_emr_deprecated @mock_ec2_deprecated
def test_ami_describe_executable_users():
conn = boto3.client('ec2', region_name='us-east-1')
ec2 = boto3.resource('ec2', 'us-east-1')
ec2.create_instances(ImageId='',
MinCount=1,
MaxCount=1)
response = conn.describe_instances(Filters=[{'Name': 'instance-state-name','Values': ['running']}])
instance_id = response['Reservations'][0]['Instances'][0]['InstanceId']
image_id = conn.create_image(InstanceId=instance_id,
Name='TestImage',)['ImageId']
USER1 = '123456789011'
ADD_USER_ARGS = {'ImageId': image_id,
'Attribute': 'launchPermission',
'OperationType': 'add',
'UserIds': [USER1]}
# Add users and get no images
conn.modify_image_attribute(**ADD_USER_ARGS)
attributes = conn.describe_image_attribute(ImageId=image_id,
Attribute='LaunchPermissions',
DryRun=False)
attributes['LaunchPermissions'].should.have.length_of(1)
attributes['LaunchPermissions'][0]['UserId'].should.equal(USER1)
images = conn.describe_images(ExecutableUsers=[USER1])['Images']
images.should.have.length_of(1)
images[0]['ImageId'].should.equal(image_id)
@mock_ec2_deprecated
def test_ami_describe_executable_users_negative():
conn = boto3.client('ec2', region_name='us-east-1')
ec2 = boto3.resource('ec2', 'us-east-1')
ec2.create_instances(ImageId='',
MinCount=1,
MaxCount=1)
response = conn.describe_instances(Filters=[{'Name': 'instance-state-name','Values': ['running']}])
instance_id = response['Reservations'][0]['Instances'][0]['InstanceId']
image_id = conn.create_image(InstanceId=instance_id,
Name='TestImage')['ImageId']
USER1 = '123456789011'
USER2 = '113355789012'
ADD_USER_ARGS = {'ImageId': image_id,
'Attribute': 'launchPermission',
'OperationType': 'add',
'UserIds': [USER1]}
# Add users and get no images
conn.modify_image_attribute(**ADD_USER_ARGS)
attributes = conn.describe_image_attribute(ImageId=image_id,
Attribute='LaunchPermissions',
DryRun=False)
attributes['LaunchPermissions'].should.have.length_of(1)
attributes['LaunchPermissions'][0]['UserId'].should.equal(USER1)
images = conn.describe_images(ExecutableUsers=[USER2])['Images']
images.should.have.length_of(0)
@mock_ec2_deprecated
def test_ami_describe_executable_users_and_filter():
conn = boto3.client('ec2', region_name='us-east-1')
ec2 = boto3.resource('ec2', 'us-east-1')
ec2.create_instances(ImageId='',
MinCount=1,
MaxCount=1)
response = conn.describe_instances(Filters=[{'Name': 'instance-state-name','Values': ['running']}])
instance_id = response['Reservations'][0]['Instances'][0]['InstanceId']
image_id = conn.create_image(InstanceId=instance_id,
Name='ImageToDelete',)['ImageId']
USER1 = '123456789011'
ADD_USER_ARGS = {'ImageId': image_id,
'Attribute': 'launchPermission',
'OperationType': 'add',
'UserIds': [USER1]}
# Add users and get no images
conn.modify_image_attribute(**ADD_USER_ARGS)
attributes = conn.describe_image_attribute(ImageId=image_id,
Attribute='LaunchPermissions',
DryRun=False)
attributes['LaunchPermissions'].should.have.length_of(1)
attributes['LaunchPermissions'][0]['UserId'].should.equal(USER1)
images = conn.describe_images(ExecutableUsers=[USER1],
Filters=[{'Name': 'state', 'Values': ['available']}])['Images']
images.should.have.length_of(1)
images[0]['ImageId'].should.equal(image_id)
@mock_ec2_deprecated
def test_ami_attribute_user_and_group_permissions(): def test_ami_attribute_user_and_group_permissions():
""" """
Boto supports adding/removing both users and groups at the same time. Boto supports adding/removing both users and groups at the same time.
@ -477,7 +578,7 @@ def test_ami_attribute_user_and_group_permissions():
image.is_public.should.equal(False) image.is_public.should.equal(False)
@mock_emr_deprecated @mock_ec2_deprecated
def test_ami_attribute_error_cases(): def test_ami_attribute_error_cases():
conn = boto.connect_ec2('the_key', 'the_secret') conn = boto.connect_ec2('the_key', 'the_secret')
reservation = conn.run_instances('ami-1234abcd') reservation = conn.run_instances('ami-1234abcd')

View File

@ -336,6 +336,11 @@ def test_snapshot_filters():
set([snap.id for snap in snapshots_by_volume_id] set([snap.id for snap in snapshots_by_volume_id]
).should.equal(set([snapshot1.id, snapshot2.id])) ).should.equal(set([snapshot1.id, snapshot2.id]))
snapshots_by_status = conn.get_all_snapshots(
filters={'status': 'completed'})
set([snap.id for snap in snapshots_by_status]
).should.equal(set([snapshot1.id, snapshot2.id, snapshot3.id]))
snapshots_by_volume_size = conn.get_all_snapshots( snapshots_by_volume_size = conn.get_all_snapshots(
filters={'volume-size': volume1.size}) filters={'volume-size': volume1.size})
set([snap.id for snap in snapshots_by_volume_size] set([snap.id for snap in snapshots_by_volume_size]

View File

@ -7,12 +7,13 @@ import base64
import datetime import datetime
import boto import boto
import boto3
from boto.ec2.instance import Reservation, InstanceAttribute from boto.ec2.instance import Reservation, InstanceAttribute
from boto.exception import EC2ResponseError, EC2ResponseError from boto.exception import EC2ResponseError, EC2ResponseError
from freezegun import freeze_time from freezegun import freeze_time
import sure # noqa import sure # noqa
from moto import mock_ec2_deprecated from moto import mock_ec2_deprecated, mock_ec2
from tests.helpers import requires_boto_gte from tests.helpers import requires_boto_gte
@ -157,6 +158,26 @@ def test_get_instances_by_id():
cm.exception.request_id.should_not.be.none cm.exception.request_id.should_not.be.none
@mock_ec2
def test_get_paginated_instances():
image_id = 'ami-1234abcd'
client = boto3.client('ec2', region_name='us-east-1')
conn = boto3.resource('ec2', 'us-east-1')
for i in range(100):
conn.create_instances(ImageId=image_id,
MinCount=1,
MaxCount=1)
resp = client.describe_instances(MaxResults=50)
reservations = resp['Reservations']
reservations.should.have.length_of(50)
next_token = resp['NextToken']
next_token.should_not.be.none
resp2 = client.describe_instances(NextToken=next_token)
reservations.extend(resp2['Reservations'])
reservations.should.have.length_of(100)
assert 'NextToken' not in resp2.keys()
@mock_ec2_deprecated @mock_ec2_deprecated
def test_get_instances_filtering_by_state(): def test_get_instances_filtering_by_state():
conn = boto.connect_ec2() conn = boto.connect_ec2()
@ -337,6 +358,20 @@ def test_get_instances_filtering_by_architecture():
reservations[0].instances.should.have.length_of(1) reservations[0].instances.should.have.length_of(1)
@mock_ec2
def test_get_instances_filtering_by_image_id():
image_id = 'ami-1234abcd'
client = boto3.client('ec2', region_name='us-east-1')
conn = boto3.resource('ec2', 'us-east-1')
conn.create_instances(ImageId=image_id,
MinCount=1,
MaxCount=1)
reservations = client.describe_instances(Filters=[{'Name': 'image-id',
'Values': [image_id]}])['Reservations']
reservations[0]['Instances'].should.have.length_of(1)
@mock_ec2_deprecated @mock_ec2_deprecated
def test_get_instances_filtering_by_tag(): def test_get_instances_filtering_by_tag():
conn = boto.connect_ec2() conn = boto.connect_ec2()

View File

@ -109,6 +109,27 @@ def test_create_and_delete_boto3_support():
'LoadBalancerDescriptions']).should.have.length_of(0) 'LoadBalancerDescriptions']).should.have.length_of(0)
@mock_elb
def test_describe_paginated_balancers():
client = boto3.client('elb', region_name='us-east-1')
for i in range(51):
client.create_load_balancer(
LoadBalancerName='my-lb%d' % i,
Listeners=[
{'Protocol': 'tcp', 'LoadBalancerPort': 80, 'InstancePort': 8080}],
AvailabilityZones=['us-east-1a', 'us-east-1b']
)
resp = client.describe_load_balancers()
resp['LoadBalancerDescriptions'].should.have.length_of(50)
resp['NextMarker'].should.equal(resp['LoadBalancerDescriptions'][-1]['LoadBalancerName'])
resp2 = client.describe_load_balancers(Marker=resp['NextMarker'])
resp2['LoadBalancerDescriptions'].should.have.length_of(1)
assert 'NextToken' not in resp2.keys()
@mock_elb_deprecated @mock_elb_deprecated
def test_add_listener(): def test_add_listener():
conn = boto.connect_elb() conn = boto.connect_elb()

View File

@ -127,6 +127,18 @@ def test_describe_cluster():
cl['VisibleToAllUsers'].should.equal(True) cl['VisibleToAllUsers'].should.equal(True)
@mock_emr
def test_describe_cluster_not_found():
conn = boto3.client('emr', region_name='us-east-1')
raised = False
try:
cluster = conn.describe_cluster(ClusterId='DummyId')
except ClientError as e:
if e.response['Error']['Code'] == "ResourceNotFoundException":
raised = True
raised.should.equal(True)
@mock_emr @mock_emr
def test_describe_job_flows(): def test_describe_job_flows():
client = boto3.client('emr', region_name='us-east-1') client = boto3.client('emr', region_name='us-east-1')

View File

@ -2,9 +2,10 @@ from __future__ import unicode_literals
import boto.kinesis import boto.kinesis
from boto.kinesis.exceptions import ResourceNotFoundException, InvalidArgumentException from boto.kinesis.exceptions import ResourceNotFoundException, InvalidArgumentException
import boto3
import sure # noqa import sure # noqa
from moto import mock_kinesis_deprecated from moto import mock_kinesis, mock_kinesis_deprecated
@mock_kinesis_deprecated @mock_kinesis_deprecated
@ -51,6 +52,25 @@ def test_list_and_delete_stream():
"not-a-stream").should.throw(ResourceNotFoundException) "not-a-stream").should.throw(ResourceNotFoundException)
@mock_kinesis
def test_list_many_streams():
conn = boto3.client('kinesis', region_name="us-west-2")
for i in range(11):
conn.create_stream(StreamName="stream%d" % i, ShardCount=1)
resp = conn.list_streams()
stream_names = resp["StreamNames"]
has_more_streams = resp["HasMoreStreams"]
stream_names.should.have.length_of(10)
has_more_streams.should.be(True)
resp2 = conn.list_streams(ExclusiveStartStreamName=stream_names[-1])
stream_names = resp2["StreamNames"]
has_more_streams = resp2["HasMoreStreams"]
stream_names.should.have.length_of(1)
has_more_streams.should.equal(False)
@mock_kinesis_deprecated @mock_kinesis_deprecated
def test_basic_shard_iterator(): def test_basic_shard_iterator():
conn = boto.kinesis.connect_to_region("us-west-2") conn = boto.kinesis.connect_to_region("us-west-2")

View File

@ -1,11 +1,12 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import boto3
import boto.rds import boto.rds
import boto.vpc import boto.vpc
from boto.exception import BotoServerError from boto.exception import BotoServerError
import sure # noqa import sure # noqa
from moto import mock_ec2_deprecated, mock_rds_deprecated from moto import mock_ec2_deprecated, mock_rds_deprecated, mock_rds
from tests.helpers import disable_on_py3 from tests.helpers import disable_on_py3
@ -45,6 +46,26 @@ def test_get_databases():
databases[0].id.should.equal("db-master-1") databases[0].id.should.equal("db-master-1")
@disable_on_py3()
@mock_rds
def test_get_databases_paginated():
conn = boto3.client('rds', region_name="us-west-2")
for i in range(51):
conn.create_db_instance(AllocatedStorage=5,
Port=5432,
DBInstanceIdentifier='rds%d' % i,
DBInstanceClass='db.t1.micro',
Engine='postgres')
resp = conn.describe_db_instances()
resp["DBInstances"].should.have.length_of(50)
resp["Marker"].should.equal(resp["DBInstances"][-1]['DBInstanceIdentifier'])
resp2 = conn.describe_db_instances(Marker=resp["Marker"])
resp2["DBInstances"].should.have.length_of(1)
@mock_rds_deprecated @mock_rds_deprecated
def test_describe_non_existant_database(): def test_describe_non_existant_database():
conn = boto.rds.connect_to_region("us-west-2") conn = boto.rds.connect_to_region("us-west-2")

View File

@ -65,6 +65,25 @@ def test_get_databases():
'arn:aws:rds:us-west-2:1234567890:db:db-master-1') 'arn:aws:rds:us-west-2:1234567890:db:db-master-1')
@disable_on_py3()
@mock_rds2
def test_get_databases_paginated():
conn = boto3.client('rds', region_name="us-west-2")
for i in range(51):
conn.create_db_instance(AllocatedStorage=5,
Port=5432,
DBInstanceIdentifier='rds%d' % i,
DBInstanceClass='db.t1.micro',
Engine='postgres')
resp = conn.describe_db_instances()
resp["DBInstances"].should.have.length_of(50)
resp["Marker"].should.equal(resp["DBInstances"][-1]['DBInstanceIdentifier'])
resp2 = conn.describe_db_instances(Marker=resp["Marker"])
resp2["DBInstances"].should.have.length_of(1)
@disable_on_py3() @disable_on_py3()
@mock_rds2 @mock_rds2
def test_describe_non_existant_database(): def test_describe_non_existant_database():