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

View file

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

View file

@ -40,11 +40,22 @@ class AutoScalingResponse(BaseResponse):
def describe_launch_configurations(self):
names = self._get_multi_param('LaunchConfigurationNames.member')
launch_configurations = self.autoscaling_backend.describe_launch_configurations(
names)
all_launch_configurations = self.autoscaling_backend.describe_launch_configurations(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(
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):
launch_configurations_name = self.querystring.get(
@ -78,9 +89,22 @@ class AutoScalingResponse(BaseResponse):
def describe_auto_scaling_groups(self):
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)
return template.render(groups=groups)
return template.render(groups=groups, next_token=next_token)
def update_auto_scaling_group(self):
self.autoscaling_backend.update_autoscaling_group(
@ -239,6 +263,9 @@ DESCRIBE_LAUNCH_CONFIGURATIONS_TEMPLATE = """<DescribeLaunchConfigurationsRespon
</member>
{% endfor %}
</LaunchConfigurations>
{% if next_token %}
<NextToken>{{ next_token }}</NextToken>
{% endif %}
</DescribeLaunchConfigurationsResult>
<ResponseMetadata>
<RequestId>d05a22f8-b690-11e2-bf8e-2113fEXAMPLE</RequestId>
@ -331,6 +358,9 @@ DESCRIBE_AUTOSCALING_GROUPS_TEMPLATE = """<DescribeAutoScalingGroupsResponse xml
</member>
{% endfor %}
</AutoScalingGroups>
{% if next_token %}
<NextToken>{{ next_token }}</NextToken>
{% endif %}
</DescribeAutoScalingGroupsResult>
<ResponseMetadata>
<RequestId>0f02a07d-b677-11e2-9eb0-dd50EXAMPLE</RequestId>

View file

@ -4,6 +4,7 @@ import json
import uuid
import boto.cloudformation
from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel
from .parsing import ResourceMap, OutputMap
@ -121,7 +122,7 @@ class FakeEvent(BaseModel):
class CloudFormationBackend(BaseBackend):
def __init__(self):
self.stacks = {}
self.stacks = OrderedDict()
self.deleted_stacks = {}
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]
raise ValidationError(name_or_stack_id)
else:
return stacks
return list(stacks)
def list_stacks(self):
return self.stacks.values()

View file

@ -72,10 +72,20 @@ class CloudFormationResponse(BaseResponse):
stack_name_or_id = None
if self._get_param('StackName'):
stack_name_or_id = self.querystring.get('StackName')[0]
token = self._get_param('NextToken')
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)
return template.render(stacks=stacks)
return template.render(stacks=stacks_resp, next_token=next_token)
def describe_stack_resource(self):
stack_name = self._get_param('StackName')
@ -270,6 +280,9 @@ DESCRIBE_STACKS_TEMPLATE = """<DescribeStacksResponse>
</member>
{% endfor %}
</Stacks>
{% if next_token %}
<NextToken>{{ next_token }}</NextToken>
{% endif %}
</DescribeStacksResult>
</DescribeStacksResponse>"""

View file

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

View file

@ -31,12 +31,25 @@ class DataPipelineResponse(BaseResponse):
})
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({
"hasMoreResults": False,
"marker": None,
"hasMoreResults": has_more_results,
"marker": marker,
"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.created_at = datetime.datetime.utcnow()
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'):
results = {
@ -209,11 +214,12 @@ class Table(BaseModel):
'TableSizeBytes': 0,
'TableName': self.name,
'TableStatus': 'ACTIVE',
'TableArn': self.table_arn,
'KeySchema': self.schema,
'ItemCount': len(self),
'CreationDateTime': unix_time(self.created_at),
'GlobalSecondaryIndexes': [index for index in self.global_indexes],
'LocalSecondaryIndexes': [index for index in self.indexes]
'LocalSecondaryIndexes': [index for index in self.indexes],
}
}
return results
@ -505,6 +511,18 @@ class DynamoDBBackend(BaseBackend):
def delete_table(self, name):
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):
table = self.tables[name]
table.throughput = throughput

View file

@ -73,7 +73,7 @@ class DynamoHandler(BaseResponse):
def list_tables(self):
body = self.body
limit = body.get('Limit')
limit = body.get('Limit', 100)
if body.get("ExclusiveStartTableName"):
last = body.get("ExclusiveStartTableName")
start = list(dynamodb_backend2.tables.keys()).index(last) + 1
@ -124,6 +124,35 @@ class DynamoHandler(BaseResponse):
er = 'com.amazonaws.dynamodb.v20111205#ResourceNotFoundException'
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):
name = self.body['TableName']
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.launchspecification import LaunchSpecification
from moto.compat import OrderedDict
from moto.core import BaseBackend
from moto.core.models import Model, BaseModel
from moto.core.utils import iso_8601_datetime_with_milliseconds, camelcase_to_underscores
@ -618,7 +619,7 @@ class Instance(TaggedEC2Resource, BotoInstance):
class InstanceBackend(object):
def __init__(self):
self.reservations = {}
self.reservations = OrderedDict()
super(InstanceBackend, self).__init__()
def get_instance(self, instance_id):
@ -1049,12 +1050,22 @@ class AmiBackend(object):
self.amis[ami_id] = 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:
images = self.amis.values()
images = images or self.amis.values()
return generic_filter(filters, images)
else:
images = []
for ami_id in ami_ids:
if ami_id in self.amis:
images.append(self.amis[ami_id])
@ -1766,6 +1777,9 @@ class Snapshot(TaggedEC2Resource):
if filter_name == 'encrypted':
return str(self.encrypted).lower()
if filter_name == 'status':
return self.status
filter_value = super(Snapshot, self).get_filter_value(filter_name)
if filter_value is None:

View file

@ -1,7 +1,7 @@
from __future__ import unicode_literals
from moto.core.responses import BaseResponse
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):
@ -43,8 +43,9 @@ class AmisResponse(BaseResponse):
def describe_images(self):
ami_ids = image_ids_from_querystring(self.querystring)
filters = filters_from_querystring(self.querystring)
exec_users = executable_users_from_querystring(self.querystring)
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)
return template.render(images=images)

View file

@ -11,6 +11,7 @@ class InstanceResponse(BaseResponse):
def describe_instances(self):
filter_dict = filters_from_querystring(self.querystring)
instance_ids = instance_ids_from_querystring(self.querystring)
token = self._get_param("NextToken")
if instance_ids:
reservations = self.ec2_backend.get_reservations_by_instance_ids(
instance_ids, filters=filter_dict)
@ -18,8 +19,18 @@ class InstanceResponse(BaseResponse):
reservations = self.ec2_backend.all_reservations(
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)
return template.render(reservations=reservations)
return template.render(reservations=reservations_resp, next_token=next_token)
def run_instances(self):
min_count = int(self.querystring.get('MinCount', ['1'])[0])
@ -492,6 +503,9 @@ EC2_DESCRIBE_INSTANCES = """<DescribeInstancesResponse xmlns="http://ec2.amazona
</item>
{% endfor %}
</reservationSet>
{% if next_token %}
<nextToken>{{ next_token }}</nextToken>
{% endif %}
</DescribeInstancesResponse>"""
EC2_TERMINATE_INSTANCES = """

View file

@ -190,6 +190,14 @@ def image_ids_from_querystring(querystring_dict):
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):
route_table_ids = []
for key, value in querystring_dict.items():
@ -383,7 +391,8 @@ filter_dict_attribute_mapping = {
'private-ip-address': 'private_ip',
'ip-address': 'public_ip',
'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):
value = obj.get_filter_value(filter)
if not filter_value:
return False
if isinstance(value, six.string_types):
if not isinstance(filter_value, list):
filter_value = [filter_value]

View file

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

View file

@ -52,9 +52,21 @@ class ELBResponse(BaseResponse):
def describe_load_balancers(self):
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)
return template.render(load_balancers=load_balancers)
return template.render(load_balancers=load_balancers_resp, marker=next_marker)
def delete_load_balancer_listeners(self):
load_balancer_name = self._get_param('LoadBalancerName')
@ -493,6 +505,9 @@ DESCRIBE_LOAD_BALANCERS_TEMPLATE = """<DescribeLoadBalancersResponse xmlns="http
</member>
{% endfor %}
</LoadBalancerDescriptions>
{% if marker %}
<NextMarker>{{ marker }}</NextMarker>
{% endif %}
</DescribeLoadBalancersResult>
<ResponseMetadata>
<RequestId>f9880f01-7852-629d-a6c3-3ae2-666a409287e6dc0c</RequestId>

View file

@ -6,7 +6,7 @@ import boto.emr
import pytz
from dateutil.parser import parse as dtparse
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
@ -324,7 +324,9 @@ class ElasticMapReduceBackend(BaseBackend):
return step
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):
return [

View file

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

View file

@ -35,10 +35,24 @@ class KinesisResponse(BaseResponse):
def list_streams(self):
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({
"HasMoreStreams": False,
"StreamNames": [stream.stream_name for stream in streams],
"HasMoreStreams": has_more_streams,
"StreamNames": streams_resp
})
def delete_stream(self):

View file

@ -88,9 +88,21 @@ class RDSResponse(BaseResponse):
def describe_db_instances(self):
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)
return template.render(databases=databases)
return template.render(databases=instances_resp, marker=next_marker)
def modify_db_instance(self):
db_instance_identifier = self._get_param('DBInstanceIdentifier')
@ -187,6 +199,9 @@ DESCRIBE_DATABASES_TEMPLATE = """<DescribeDBInstancesResponse xmlns="http://rds.
{{ database.to_xml() }}
{% endfor %}
</DBInstances>
{% if marker %}
<Marker>{{ marker }}</Marker>
{% endif %}
</DescribeDBInstancesResult>
<ResponseMetadata>
<RequestId>01b2685a-b978-11d3-f272-7cd6cce12cc5</RequestId>

View file

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

View file

@ -114,9 +114,21 @@ class RDS2Response(BaseResponse):
def describe_db_instances(self):
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)
return template.render(databases=databases)
return template.render(databases=instances_resp, marker=next_marker)
def modify_db_instance(self):
db_instance_identifier = self._get_param('DBInstanceIdentifier')
@ -348,6 +360,9 @@ DESCRIBE_DATABASES_TEMPLATE = """<DescribeDBInstancesResponse xmlns="http://rds.
{{ database.to_xml() }}
{%- endfor -%}
</DBInstances>
{% if marker %}
<Marker>{{ marker }}</Marker>
{% endif %}
</DescribeDBInstancesResult>
<ResponseMetadata>
<RequestId>523e3218-afc7-11c3-90f5-f90431260ab4</RequestId>