Merge branch 'master' of https://github.com/2mf/moto
This commit is contained in:
commit
0270c68e0d
11
Dockerfile
Normal file
11
Dockerfile
Normal file
@ -0,0 +1,11 @@
|
||||
FROM python:2
|
||||
|
||||
ADD . /moto/
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
|
||||
WORKDIR /moto/
|
||||
RUN python setup.py install
|
||||
|
||||
CMD ["moto_server"]
|
||||
|
||||
EXPOSE 5000
|
@ -61,6 +61,8 @@ It gets even better! Moto isn't just S3. Here's the status of the other AWS serv
|
||||
|------------------------------------------------------------------------------|
|
||||
| Cloudwatch | @mock_cloudwatch | basic endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| Data Pipeline | @mock_datapipeline| basic endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| DynamoDB | @mock_dynamodb | core endpoints done |
|
||||
| DynamoDB2 | @mock_dynamodb2 | core endpoints done - no indexes |
|
||||
|------------------------------------------------------------------------------|
|
||||
|
@ -3,11 +3,12 @@ import logging
|
||||
logging.getLogger('boto').setLevel(logging.CRITICAL)
|
||||
|
||||
__title__ = 'moto'
|
||||
__version__ = '0.4.12'
|
||||
__version__ = '0.4.18'
|
||||
|
||||
from .autoscaling import mock_autoscaling # flake8: noqa
|
||||
from .cloudformation import mock_cloudformation # flake8: noqa
|
||||
from .cloudwatch import mock_cloudwatch # flake8: noqa
|
||||
from .datapipeline import mock_datapipeline # flake8: noqa
|
||||
from .dynamodb import mock_dynamodb # flake8: noqa
|
||||
from .dynamodb2 import mock_dynamodb2 # flake8: noqa
|
||||
from .ec2 import mock_ec2 # flake8: noqa
|
||||
|
@ -113,7 +113,8 @@ class FakeAutoScalingGroup(object):
|
||||
def __init__(self, name, availability_zones, desired_capacity, max_size,
|
||||
min_size, launch_config_name, vpc_zone_identifier,
|
||||
default_cooldown, health_check_period, health_check_type,
|
||||
load_balancers, placement_group, termination_policies, autoscaling_backend):
|
||||
load_balancers, placement_group, termination_policies,
|
||||
autoscaling_backend, tags):
|
||||
self.autoscaling_backend = autoscaling_backend
|
||||
self.name = name
|
||||
self.availability_zones = availability_zones
|
||||
@ -133,6 +134,7 @@ class FakeAutoScalingGroup(object):
|
||||
|
||||
self.instance_states = []
|
||||
self.set_desired_capacity(desired_capacity)
|
||||
self.tags = tags if tags else []
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
||||
@ -156,6 +158,7 @@ class FakeAutoScalingGroup(object):
|
||||
load_balancers=load_balancer_names,
|
||||
placement_group=None,
|
||||
termination_policies=properties.get("TerminationPolicies", []),
|
||||
tags=properties.get("Tags", []),
|
||||
)
|
||||
return group
|
||||
|
||||
@ -261,7 +264,7 @@ class AutoScalingBackend(BaseBackend):
|
||||
launch_config_name, vpc_zone_identifier,
|
||||
default_cooldown, health_check_period,
|
||||
health_check_type, load_balancers,
|
||||
placement_group, termination_policies):
|
||||
placement_group, termination_policies, tags):
|
||||
|
||||
def make_int(value):
|
||||
return int(value) if value is not None else value
|
||||
@ -286,6 +289,7 @@ class AutoScalingBackend(BaseBackend):
|
||||
placement_group=placement_group,
|
||||
termination_policies=termination_policies,
|
||||
autoscaling_backend=self,
|
||||
tags=tags,
|
||||
)
|
||||
self.autoscaling_groups[name] = group
|
||||
return group
|
||||
|
@ -60,6 +60,7 @@ class AutoScalingResponse(BaseResponse):
|
||||
load_balancers=self._get_multi_param('LoadBalancerNames.member'),
|
||||
placement_group=self._get_param('PlacementGroup'),
|
||||
termination_policies=self._get_multi_param('TerminationPolicies.member'),
|
||||
tags=self._get_list_prefix('Tags.member'),
|
||||
)
|
||||
template = self.response_template(CREATE_AUTOSCALING_GROUP_TEMPLATE)
|
||||
return template.render()
|
||||
@ -235,7 +236,17 @@ DESCRIBE_AUTOSCALING_GROUPS_TEMPLATE = """<DescribeAutoScalingGroupsResponse xml
|
||||
<AutoScalingGroups>
|
||||
{% for group in groups %}
|
||||
<member>
|
||||
<Tags/>
|
||||
<Tags>
|
||||
{% for tag in group.tags %}
|
||||
<member>
|
||||
<ResourceType>{{ tag.resource_type }}</ResourceType>
|
||||
<ResourceId>{{ tag.resource_id }}</ResourceId>
|
||||
<PropagateAtLaunch>{{ tag.propagate_at_launch }}</PropagateAtLaunch>
|
||||
<Key>{{ tag.key }}</Key>
|
||||
<Value>{{ tag.value }}</Value>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</Tags>
|
||||
<SuspendedProcesses/>
|
||||
<AutoScalingGroupName>{{ group.name }}</AutoScalingGroupName>
|
||||
<HealthCheckType>{{ group.health_check_type }}</HealthCheckType>
|
||||
|
@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
||||
from moto.autoscaling import autoscaling_backend
|
||||
from moto.cloudwatch import cloudwatch_backend
|
||||
from moto.cloudformation import cloudformation_backend
|
||||
from moto.datapipeline import datapipeline_backend
|
||||
from moto.dynamodb import dynamodb_backend
|
||||
from moto.dynamodb2 import dynamodb_backend2
|
||||
from moto.ec2 import ec2_backend
|
||||
@ -25,6 +26,7 @@ BACKENDS = {
|
||||
'autoscaling': autoscaling_backend,
|
||||
'cloudformation': cloudformation_backend,
|
||||
'cloudwatch': cloudwatch_backend,
|
||||
'datapipeline': datapipeline_backend,
|
||||
'dynamodb': dynamodb_backend,
|
||||
'dynamodb2': dynamodb_backend2,
|
||||
'ec2': ec2_backend,
|
||||
|
@ -4,6 +4,7 @@ import functools
|
||||
import logging
|
||||
|
||||
from moto.autoscaling import models as autoscaling_models
|
||||
from moto.datapipeline import models as datapipeline_models
|
||||
from moto.ec2 import models as ec2_models
|
||||
from moto.elb import models as elb_models
|
||||
from moto.iam import models as iam_models
|
||||
@ -36,6 +37,7 @@ MODEL_MAP = {
|
||||
"AWS::EC2::VPCGatewayAttachment": ec2_models.VPCGatewayAttachment,
|
||||
"AWS::EC2::VPCPeeringConnection": ec2_models.VPCPeeringConnection,
|
||||
"AWS::ElasticLoadBalancing::LoadBalancer": elb_models.FakeLoadBalancer,
|
||||
"AWS::DataPipeline::Pipeline": datapipeline_models.Pipeline,
|
||||
"AWS::IAM::InstanceProfile": iam_models.InstanceProfile,
|
||||
"AWS::IAM::Role": iam_models.Role,
|
||||
"AWS::RDS::DBInstance": rds_models.Database,
|
||||
|
@ -86,9 +86,19 @@ class CloudFormationResponse(BaseResponse):
|
||||
|
||||
def get_template(self):
|
||||
name_or_stack_id = self.querystring.get('StackName')[0]
|
||||
|
||||
stack = self.cloudformation_backend.get_stack(name_or_stack_id)
|
||||
return stack.template
|
||||
|
||||
response = {
|
||||
"GetTemplateResponse": {
|
||||
"GetTemplateResult": {
|
||||
"TemplateBody": stack.template,
|
||||
"ResponseMetadata": {
|
||||
"RequestId": "2d06e36c-ac1d-11e0-a958-f9382b6eb86bEXAMPLE"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return json.dumps(response)
|
||||
|
||||
def update_stack(self):
|
||||
stack_name = self._get_param('StackName')
|
||||
|
@ -63,8 +63,16 @@ class DynamicDictLoader(DictLoader):
|
||||
|
||||
|
||||
class _TemplateEnvironmentMixin(object):
|
||||
loader = DynamicDictLoader({})
|
||||
environment = Environment(loader=loader)
|
||||
|
||||
def __init__(self):
|
||||
super(_TemplateEnvironmentMixin, self).__init__()
|
||||
self.loader = DynamicDictLoader({})
|
||||
self.environment = Environment(loader=self.loader, autoescape=self.should_autoescape)
|
||||
|
||||
@property
|
||||
def should_autoescape(self):
|
||||
# Allow for subclass to overwrite
|
||||
return False
|
||||
|
||||
def contains_template(self, template_id):
|
||||
return self.loader.contains(template_id)
|
||||
@ -73,7 +81,7 @@ class _TemplateEnvironmentMixin(object):
|
||||
template_id = id(source)
|
||||
if not self.contains_template(template_id):
|
||||
self.loader.update({template_id: source})
|
||||
self.environment = Environment(loader=self.loader)
|
||||
self.environment = Environment(loader=self.loader, autoescape=self.should_autoescape)
|
||||
return self.environment.get_template(template_id)
|
||||
|
||||
|
||||
|
12
moto/datapipeline/__init__.py
Normal file
12
moto/datapipeline/__init__.py
Normal file
@ -0,0 +1,12 @@
|
||||
from __future__ import unicode_literals
|
||||
from .models import datapipeline_backends
|
||||
from ..core.models import MockAWS
|
||||
|
||||
datapipeline_backend = datapipeline_backends['us-east-1']
|
||||
|
||||
|
||||
def mock_datapipeline(func=None):
|
||||
if func:
|
||||
return MockAWS(datapipeline_backends)(func)
|
||||
else:
|
||||
return MockAWS(datapipeline_backends)
|
149
moto/datapipeline/models.py
Normal file
149
moto/datapipeline/models.py
Normal file
@ -0,0 +1,149 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
import boto.datapipeline
|
||||
from moto.core import BaseBackend
|
||||
from .utils import get_random_pipeline_id, remove_capitalization_of_dict_keys
|
||||
|
||||
|
||||
class PipelineObject(object):
|
||||
def __init__(self, object_id, name, fields):
|
||||
self.object_id = object_id
|
||||
self.name = name
|
||||
self.fields = fields
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
"fields": self.fields,
|
||||
"id": self.object_id,
|
||||
"name": self.name,
|
||||
}
|
||||
|
||||
|
||||
class Pipeline(object):
|
||||
def __init__(self, name, unique_id):
|
||||
self.name = name
|
||||
self.unique_id = unique_id
|
||||
self.description = ""
|
||||
self.pipeline_id = get_random_pipeline_id()
|
||||
self.creation_time = datetime.datetime.utcnow()
|
||||
self.objects = []
|
||||
self.status = "PENDING"
|
||||
|
||||
@property
|
||||
def physical_resource_id(self):
|
||||
return self.pipeline_id
|
||||
|
||||
def to_meta_json(self):
|
||||
return {
|
||||
"id": self.pipeline_id,
|
||||
"name": self.name,
|
||||
}
|
||||
|
||||
def to_json(self):
|
||||
return {
|
||||
"description": self.description,
|
||||
"fields": [{
|
||||
"key": "@pipelineState",
|
||||
"stringValue": self.status,
|
||||
}, {
|
||||
"key": "description",
|
||||
"stringValue": self.description
|
||||
}, {
|
||||
"key": "name",
|
||||
"stringValue": self.name
|
||||
}, {
|
||||
"key": "@creationTime",
|
||||
"stringValue": datetime.datetime.strftime(self.creation_time, '%Y-%m-%dT%H-%M-%S'),
|
||||
}, {
|
||||
"key": "@id",
|
||||
"stringValue": self.pipeline_id,
|
||||
}, {
|
||||
"key": "@sphere",
|
||||
"stringValue": "PIPELINE"
|
||||
}, {
|
||||
"key": "@version",
|
||||
"stringValue": "1"
|
||||
}, {
|
||||
"key": "@userId",
|
||||
"stringValue": "924374875933"
|
||||
}, {
|
||||
"key": "@accountId",
|
||||
"stringValue": "924374875933"
|
||||
}, {
|
||||
"key": "uniqueId",
|
||||
"stringValue": self.unique_id
|
||||
}],
|
||||
"name": self.name,
|
||||
"pipelineId": self.pipeline_id,
|
||||
"tags": [
|
||||
]
|
||||
}
|
||||
|
||||
def set_pipeline_objects(self, pipeline_objects):
|
||||
self.objects = [
|
||||
PipelineObject(pipeline_object['id'], pipeline_object['name'], pipeline_object['fields'])
|
||||
for pipeline_object in remove_capitalization_of_dict_keys(pipeline_objects)
|
||||
]
|
||||
|
||||
def activate(self):
|
||||
self.status = "SCHEDULED"
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
||||
datapipeline_backend = datapipeline_backends[region_name]
|
||||
properties = cloudformation_json["Properties"]
|
||||
|
||||
cloudformation_unique_id = "cf-" + properties["Name"]
|
||||
pipeline = datapipeline_backend.create_pipeline(properties["Name"], cloudformation_unique_id)
|
||||
datapipeline_backend.put_pipeline_definition(pipeline.pipeline_id, properties["PipelineObjects"])
|
||||
|
||||
if properties["Activate"]:
|
||||
pipeline.activate()
|
||||
return pipeline
|
||||
|
||||
|
||||
class DataPipelineBackend(BaseBackend):
|
||||
|
||||
def __init__(self):
|
||||
self.pipelines = {}
|
||||
|
||||
def create_pipeline(self, name, unique_id):
|
||||
pipeline = Pipeline(name, unique_id)
|
||||
self.pipelines[pipeline.pipeline_id] = pipeline
|
||||
return pipeline
|
||||
|
||||
def list_pipelines(self):
|
||||
return self.pipelines.values()
|
||||
|
||||
def describe_pipelines(self, pipeline_ids):
|
||||
pipelines = [pipeline for pipeline in self.pipelines.values() if pipeline.pipeline_id in pipeline_ids]
|
||||
return pipelines
|
||||
|
||||
def get_pipeline(self, pipeline_id):
|
||||
return self.pipelines[pipeline_id]
|
||||
|
||||
def put_pipeline_definition(self, pipeline_id, pipeline_objects):
|
||||
pipeline = self.get_pipeline(pipeline_id)
|
||||
pipeline.set_pipeline_objects(pipeline_objects)
|
||||
|
||||
def get_pipeline_definition(self, pipeline_id):
|
||||
pipeline = self.get_pipeline(pipeline_id)
|
||||
return pipeline.objects
|
||||
|
||||
def describe_objects(self, object_ids, pipeline_id):
|
||||
pipeline = self.get_pipeline(pipeline_id)
|
||||
pipeline_objects = [
|
||||
pipeline_object for pipeline_object in pipeline.objects
|
||||
if pipeline_object.object_id in object_ids
|
||||
]
|
||||
return pipeline_objects
|
||||
|
||||
def activate_pipeline(self, pipeline_id):
|
||||
pipeline = self.get_pipeline(pipeline_id)
|
||||
pipeline.activate()
|
||||
|
||||
|
||||
datapipeline_backends = {}
|
||||
for region in boto.datapipeline.regions():
|
||||
datapipeline_backends[region.name] = DataPipelineBackend()
|
81
moto/datapipeline/responses.py
Normal file
81
moto/datapipeline/responses.py
Normal file
@ -0,0 +1,81 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
|
||||
from moto.core.responses import BaseResponse
|
||||
from .models import datapipeline_backends
|
||||
|
||||
|
||||
class DataPipelineResponse(BaseResponse):
|
||||
|
||||
@property
|
||||
def parameters(self):
|
||||
# TODO this should really be moved to core/responses.py
|
||||
if self.body:
|
||||
return json.loads(self.body.decode("utf-8"))
|
||||
else:
|
||||
return self.querystring
|
||||
|
||||
@property
|
||||
def datapipeline_backend(self):
|
||||
return datapipeline_backends[self.region]
|
||||
|
||||
def create_pipeline(self):
|
||||
name = self.parameters['name']
|
||||
unique_id = self.parameters['uniqueId']
|
||||
pipeline = self.datapipeline_backend.create_pipeline(name, unique_id)
|
||||
return json.dumps({
|
||||
"pipelineId": pipeline.pipeline_id,
|
||||
})
|
||||
|
||||
def list_pipelines(self):
|
||||
pipelines = self.datapipeline_backend.list_pipelines()
|
||||
return json.dumps({
|
||||
"hasMoreResults": False,
|
||||
"marker": None,
|
||||
"pipelineIdList": [
|
||||
pipeline.to_meta_json() for pipeline in pipelines
|
||||
]
|
||||
})
|
||||
|
||||
def describe_pipelines(self):
|
||||
pipeline_ids = self.parameters["pipelineIds"]
|
||||
pipelines = self.datapipeline_backend.describe_pipelines(pipeline_ids)
|
||||
|
||||
return json.dumps({
|
||||
"pipelineDescriptionList": [
|
||||
pipeline.to_json() for pipeline in pipelines
|
||||
]
|
||||
})
|
||||
|
||||
def put_pipeline_definition(self):
|
||||
pipeline_id = self.parameters["pipelineId"]
|
||||
pipeline_objects = self.parameters["pipelineObjects"]
|
||||
|
||||
self.datapipeline_backend.put_pipeline_definition(pipeline_id, pipeline_objects)
|
||||
return json.dumps({"errored": False})
|
||||
|
||||
def get_pipeline_definition(self):
|
||||
pipeline_id = self.parameters["pipelineId"]
|
||||
pipeline_definition = self.datapipeline_backend.get_pipeline_definition(pipeline_id)
|
||||
return json.dumps({
|
||||
"pipelineObjects": [pipeline_object.to_json() for pipeline_object in pipeline_definition]
|
||||
})
|
||||
|
||||
def describe_objects(self):
|
||||
pipeline_id = self.parameters["pipelineId"]
|
||||
object_ids = self.parameters["objectIds"]
|
||||
|
||||
pipeline_objects = self.datapipeline_backend.describe_objects(object_ids, pipeline_id)
|
||||
return json.dumps({
|
||||
"hasMoreResults": False,
|
||||
"marker": None,
|
||||
"pipelineObjects": [
|
||||
pipeline_object.to_json() for pipeline_object in pipeline_objects
|
||||
]
|
||||
})
|
||||
|
||||
def activate_pipeline(self):
|
||||
pipeline_id = self.parameters["pipelineId"]
|
||||
self.datapipeline_backend.activate_pipeline(pipeline_id)
|
||||
return json.dumps({})
|
10
moto/datapipeline/urls.py
Normal file
10
moto/datapipeline/urls.py
Normal file
@ -0,0 +1,10 @@
|
||||
from __future__ import unicode_literals
|
||||
from .responses import DataPipelineResponse
|
||||
|
||||
url_bases = [
|
||||
"https?://datapipeline.(.+).amazonaws.com",
|
||||
]
|
||||
|
||||
url_paths = {
|
||||
'{0}/$': DataPipelineResponse.dispatch,
|
||||
}
|
23
moto/datapipeline/utils.py
Normal file
23
moto/datapipeline/utils.py
Normal file
@ -0,0 +1,23 @@
|
||||
import collections
|
||||
import six
|
||||
from moto.core.utils import get_random_hex
|
||||
|
||||
|
||||
def get_random_pipeline_id():
|
||||
return "df-{0}".format(get_random_hex(length=19))
|
||||
|
||||
|
||||
def remove_capitalization_of_dict_keys(obj):
|
||||
if isinstance(obj, collections.Mapping):
|
||||
result = obj.__class__()
|
||||
for key, value in obj.items():
|
||||
normalized_key = key[:1].lower() + key[1:]
|
||||
result[normalized_key] = remove_capitalization_of_dict_keys(value)
|
||||
return result
|
||||
elif isinstance(obj, collections.Iterable) and not isinstance(obj, six.string_types):
|
||||
result = obj.__class__()
|
||||
for item in obj:
|
||||
result += (remove_capitalization_of_dict_keys(item),)
|
||||
return result
|
||||
else:
|
||||
return obj
|
@ -121,6 +121,17 @@ class Item(object):
|
||||
# TODO deal with other types
|
||||
self.attrs[key] = DynamoType({"S": value})
|
||||
|
||||
def update_with_attribute_updates(self, attribute_updates):
|
||||
for attribute_name, update_action in attribute_updates.items():
|
||||
action = update_action['Action']
|
||||
new_value = list(update_action['Value'].values())[0]
|
||||
if action == 'PUT':
|
||||
# TODO deal with other types
|
||||
if isinstance(new_value, list) or isinstance(new_value, set):
|
||||
self.attrs[attribute_name] = DynamoType({"SS": new_value})
|
||||
else:
|
||||
self.attrs[attribute_name] = DynamoType({"S": new_value})
|
||||
|
||||
|
||||
class Table(object):
|
||||
|
||||
@ -411,12 +422,19 @@ class DynamoDBBackend(BaseBackend):
|
||||
|
||||
return table.scan(scan_filters)
|
||||
|
||||
def update_item(self, table_name, key, update_expression):
|
||||
def update_item(self, table_name, key, update_expression, attribute_updates):
|
||||
table = self.get_table(table_name)
|
||||
|
||||
if table.hash_key_attr in key:
|
||||
# Sometimes the key is wrapped in a dict with the key name
|
||||
key = key[table.hash_key_attr]
|
||||
|
||||
hash_value = DynamoType(key)
|
||||
item = table.get_item(hash_value)
|
||||
item.update(update_expression)
|
||||
if update_expression:
|
||||
item.update(update_expression)
|
||||
else:
|
||||
item.update_with_attribute_updates(attribute_updates)
|
||||
return item
|
||||
|
||||
def delete_item(self, table_name, keys):
|
||||
|
@ -373,8 +373,9 @@ class DynamoHandler(BaseResponse):
|
||||
def update_item(self):
|
||||
name = self.body['TableName']
|
||||
key = self.body['Key']
|
||||
update_expression = self.body['UpdateExpression']
|
||||
item = dynamodb_backend2.update_item(name, key, update_expression)
|
||||
update_expression = self.body.get('UpdateExpression')
|
||||
attribute_updates = self.body.get('AttributeUpdates')
|
||||
item = dynamodb_backend2.update_item(name, key, update_expression, attribute_updates)
|
||||
|
||||
item_dict = item.to_json()
|
||||
item_dict['ConsumedCapacityUnits'] = 0.5
|
||||
|
@ -98,7 +98,7 @@ from .utils import (
|
||||
|
||||
|
||||
def utc_date_and_time():
|
||||
return datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||
return datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.000Z')
|
||||
|
||||
|
||||
def validate_resource_ids(resource_ids):
|
||||
@ -710,6 +710,13 @@ class KeyPairBackend(object):
|
||||
|
||||
return results
|
||||
|
||||
def import_key_pair(self, key_name, public_key_material):
|
||||
if key_name in self.keypairs:
|
||||
raise InvalidKeyPairDuplicateError(key_name)
|
||||
self.keypairs[key_name] = keypair = random_key_pair()
|
||||
keypair['name'] = key_name
|
||||
return keypair
|
||||
|
||||
|
||||
class TagBackend(object):
|
||||
|
||||
@ -1381,12 +1388,13 @@ class VolumeAttachment(object):
|
||||
|
||||
|
||||
class Volume(TaggedEC2Resource):
|
||||
def __init__(self, ec2_backend, volume_id, size, zone):
|
||||
def __init__(self, ec2_backend, volume_id, size, zone, snapshot_id=None):
|
||||
self.id = volume_id
|
||||
self.size = size
|
||||
self.zone = zone
|
||||
self.create_time = utc_date_and_time()
|
||||
self.attachment = None
|
||||
self.snapshot_id = snapshot_id
|
||||
self.ec2_backend = ec2_backend
|
||||
|
||||
@classmethod
|
||||
@ -1429,10 +1437,14 @@ class EBSBackend(object):
|
||||
self.snapshots = {}
|
||||
super(EBSBackend, self).__init__()
|
||||
|
||||
def create_volume(self, size, zone_name):
|
||||
def create_volume(self, size, zone_name, snapshot_id=None):
|
||||
volume_id = random_volume_id()
|
||||
zone = self.get_zone_by_name(zone_name)
|
||||
volume = Volume(self, volume_id, size, zone)
|
||||
if snapshot_id:
|
||||
snapshot = self.get_snapshot(snapshot_id)
|
||||
if size is None:
|
||||
size = snapshot.volume.size
|
||||
volume = Volume(self, volume_id, size, zone, snapshot_id)
|
||||
self.volumes[volume_id] = volume
|
||||
return volume
|
||||
|
||||
|
@ -66,3 +66,7 @@ class EC2Response(
|
||||
def ec2_backend(self):
|
||||
from moto.ec2.models import ec2_backends
|
||||
return ec2_backends[self.region]
|
||||
|
||||
@property
|
||||
def should_autoescape(self):
|
||||
return True
|
||||
|
@ -25,9 +25,10 @@ class ElasticBlockStore(BaseResponse):
|
||||
return template.render(snapshot=snapshot)
|
||||
|
||||
def create_volume(self):
|
||||
size = self.querystring.get('Size')[0]
|
||||
zone = self.querystring.get('AvailabilityZone')[0]
|
||||
volume = self.ec2_backend.create_volume(size, zone)
|
||||
size = self._get_param('Size')
|
||||
zone = self._get_param('AvailabilityZone')
|
||||
snapshot_id = self._get_param('SnapshotId')
|
||||
volume = self.ec2_backend.create_volume(size, zone, snapshot_id)
|
||||
template = self.response_template(CREATE_VOLUME_RESPONSE)
|
||||
return template.render(volume=volume)
|
||||
|
||||
@ -110,7 +111,11 @@ CREATE_VOLUME_RESPONSE = """<CreateVolumeResponse xmlns="http://ec2.amazonaws.co
|
||||
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
|
||||
<volumeId>{{ volume.id }}</volumeId>
|
||||
<size>{{ volume.size }}</size>
|
||||
<snapshotId/>
|
||||
{% if volume.snapshot_id %}
|
||||
<snapshotId>{{ volume.snapshot_id }}</snapshotId>
|
||||
{% else %}
|
||||
<snapshotId/>
|
||||
{% endif %}
|
||||
<availabilityZone>{{ volume.zone.name }}</availabilityZone>
|
||||
<status>creating</status>
|
||||
<createTime>{{ volume.create_time}}</createTime>
|
||||
@ -124,7 +129,11 @@ DESCRIBE_VOLUMES_RESPONSE = """<DescribeVolumesResponse xmlns="http://ec2.amazon
|
||||
<item>
|
||||
<volumeId>{{ volume.id }}</volumeId>
|
||||
<size>{{ volume.size }}</size>
|
||||
<snapshotId/>
|
||||
{% if volume.snapshot_id %}
|
||||
<snapshotId>{{ volume.snapshot_id }}</snapshotId>
|
||||
{% else %}
|
||||
<snapshotId/>
|
||||
{% endif %}
|
||||
<availabilityZone>{{ volume.zone.name }}</availabilityZone>
|
||||
<status>{{ volume.status }}</status>
|
||||
<createTime>{{ volume.create_time}}</createTime>
|
||||
@ -198,9 +207,9 @@ DESCRIBE_SNAPSHOTS_RESPONSE = """<DescribeSnapshotsResponse xmlns="http://ec2.am
|
||||
<item>
|
||||
<snapshotId>{{ snapshot.id }}</snapshotId>
|
||||
<volumeId>{{ snapshot.volume.id }}</volumeId>
|
||||
<status>pending</status>
|
||||
<status>completed</status>
|
||||
<startTime>{{ snapshot.start_time}}</startTime>
|
||||
<progress>30%</progress>
|
||||
<progress>100%</progress>
|
||||
<ownerId>111122223333</ownerId>
|
||||
<volumeSize>{{ snapshot.volume.size }}</volumeSize>
|
||||
<description>{{ snapshot.description }}</description>
|
||||
|
@ -1,4 +1,5 @@
|
||||
from __future__ import unicode_literals
|
||||
from boto.ec2.instancetype import InstanceType
|
||||
from moto.core.responses import BaseResponse
|
||||
from moto.core.utils import camelcase_to_underscores
|
||||
from moto.ec2.utils import instance_ids_from_querystring, filters_from_querystring, \
|
||||
@ -78,6 +79,11 @@ class InstanceResponse(BaseResponse):
|
||||
template = self.response_template(EC2_INSTANCE_STATUS)
|
||||
return template.render(instances=instances)
|
||||
|
||||
def describe_instance_types(self):
|
||||
instance_types = [InstanceType(name='t1.micro', cores=1, memory=644874240, disk=0)]
|
||||
template = self.response_template(EC2_DESCRIBE_INSTANCE_TYPES)
|
||||
return template.render(instance_types=instance_types)
|
||||
|
||||
def describe_instance_attribute(self):
|
||||
# TODO this and modify below should raise IncorrectInstanceState if
|
||||
# instance not in stopped state
|
||||
@ -586,3 +592,21 @@ EC2_INSTANCE_STATUS = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
{% endfor %}
|
||||
</instanceStatusSet>
|
||||
</DescribeInstanceStatusResponse>"""
|
||||
|
||||
EC2_DESCRIBE_INSTANCE_TYPES = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<DescribeInstanceTypesResponse xmlns="http://api.outscale.com/wsdl/fcuext/2014-04-15/">
|
||||
<requestId>f8b86168-d034-4e65-b48d-3b84c78e64af</requestId>
|
||||
<instanceTypeSet>
|
||||
{% for instance_type in instance_types %}
|
||||
<item>
|
||||
<name>{{ instance_type.name }}</name>
|
||||
<vcpu>{{ instance_type.cores }}</vcpu>
|
||||
<memory>{{ instance_type.memory }}</memory>
|
||||
<storageSize>{{ instance_type.disk }}</storageSize>
|
||||
<storageCount>{{ instance_type.storageCount }}</storageCount>
|
||||
<maxIpAddresses>{{ instance_type.maxIpAddresses }}</maxIpAddresses>
|
||||
<ebsOptimizedAvailable>{{ instance_type.ebsOptimizedAvailable }}</ebsOptimizedAvailable>
|
||||
</item>
|
||||
{% endfor %}
|
||||
</instanceTypeSet>
|
||||
</DescribeInstanceTypesResponse>"""
|
||||
|
@ -28,7 +28,11 @@ class KeyPairs(BaseResponse):
|
||||
return template.render(keypairs=keypairs)
|
||||
|
||||
def import_key_pair(self):
|
||||
raise NotImplementedError('KeyPairs.import_key_pair is not yet implemented')
|
||||
name = self.querystring.get('KeyName')[0]
|
||||
material = self.querystring.get('PublicKeyMaterial')[0]
|
||||
keypair = self.ec2_backend.import_key_pair(name, material)
|
||||
template = self.response_template(IMPORT_KEYPAIR_RESPONSE)
|
||||
return template.render(**keypair)
|
||||
|
||||
|
||||
DESCRIBE_KEY_PAIRS_RESPONSE = """<DescribeKeyPairsResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
|
||||
@ -58,3 +62,10 @@ DELETE_KEY_PAIR_RESPONSE = """<DeleteKeyPairResponse xmlns="http://ec2.amazonaws
|
||||
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
|
||||
<return>{{ success }}</return>
|
||||
</DeleteKeyPairResponse>"""
|
||||
|
||||
IMPORT_KEYPAIR_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ImportKeyPairResponse xmlns="http://ec2.amazonaws.com/doc/2014-06-15/">
|
||||
<requestId>471f9fdd-8fe2-4a84-86b0-bd3d3e350979</requestId>
|
||||
<keyName>{{ name }}</keyName>
|
||||
<keyFingerprint>{{ fingerprint }}</keyFingerprint>
|
||||
</ImportKeyPairResponse>"""
|
||||
|
@ -9,9 +9,9 @@ def process_rules_from_querystring(querystring):
|
||||
except:
|
||||
group_name_or_id = querystring.get('GroupId')[0]
|
||||
|
||||
ip_protocol = querystring.get('IpPermissions.1.IpProtocol')[0]
|
||||
from_port = querystring.get('IpPermissions.1.FromPort')[0]
|
||||
to_port = querystring.get('IpPermissions.1.ToPort')[0]
|
||||
ip_protocol = querystring.get('IpPermissions.1.IpProtocol', [None])[0]
|
||||
from_port = querystring.get('IpPermissions.1.FromPort', [None])[0]
|
||||
to_port = querystring.get('IpPermissions.1.ToPort', [None])[0]
|
||||
ip_ranges = []
|
||||
for key, value in querystring.items():
|
||||
if 'IpPermissions.1.IpRanges' in key:
|
||||
|
@ -1,6 +1,5 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from xml.sax.saxutils import escape
|
||||
from moto.core.responses import BaseResponse
|
||||
from moto.ec2.models import validate_resource_ids
|
||||
from moto.ec2.utils import sequence_from_querystring, tags_from_query_string, filters_from_querystring
|
||||
@ -26,8 +25,6 @@ class TagResponse(BaseResponse):
|
||||
def describe_tags(self):
|
||||
filters = filters_from_querystring(querystring_dict=self.querystring)
|
||||
tags = self.ec2_backend.describe_tags(filters=filters)
|
||||
for tag in tags:
|
||||
tag['value'] = escape(tag['value'])
|
||||
template = self.response_template(DESCRIBE_RESPONSE)
|
||||
return template.render(tags=tags)
|
||||
|
||||
|
@ -453,27 +453,22 @@ def simple_aws_filter_to_re(filter_string):
|
||||
return tmp_filter
|
||||
|
||||
|
||||
# not really random ( http://xkcd.com/221/ )
|
||||
def random_key_pair():
|
||||
def random_hex():
|
||||
return chr(random.choice(list(range(48, 58)) + list(range(97, 102))))
|
||||
def random_fingerprint():
|
||||
return ':'.join([random_hex()+random_hex() for i in range(20)])
|
||||
def random_material():
|
||||
return ''.join([
|
||||
chr(random.choice(list(range(65, 91)) + list(range(48, 58)) +
|
||||
list(range(97, 102))))
|
||||
for i in range(1000)
|
||||
])
|
||||
material = "---- BEGIN RSA PRIVATE KEY ----" + random_material() + \
|
||||
"-----END RSA PRIVATE KEY-----"
|
||||
return {
|
||||
'fingerprint': ('1f:51:ae:28:bf:89:e9:d8:1f:25:5d:37:2d:'
|
||||
'7d:b8:ca:9f:f5:f1:6f'),
|
||||
'material': """---- BEGIN RSA PRIVATE KEY ----
|
||||
MIICiTCCAfICCQD6m7oRw0uXOjANBgkqhkiG9w0BAQUFADCBiDELMAkGA1UEBhMC
|
||||
VVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZBbWF6
|
||||
b24xFDASBgNVBAsTC0lBTSBDb25zb2xlMRIwEAYDVQQDEwlUZXN0Q2lsYWMxHzAd
|
||||
BgkqhkiG9w0BCQEWEG5vb25lQGFtYXpvbi5jb20wHhcNMTEwNDI1MjA0NTIxWhcN
|
||||
MTIwNDI0MjA0NTIxWjCBiDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAldBMRAwDgYD
|
||||
VQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZBbWF6b24xFDASBgNVBAsTC0lBTSBDb25z
|
||||
b2xlMRIwEAYDVQQDEwlUZXN0Q2lsYWMxHzAdBgkqhkiG9w0BCQEWEG5vb25lQGFt
|
||||
YXpvbi5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMaK0dn+a4GmWIWJ
|
||||
21uUSfwfEvySWtC2XADZ4nB+BLYgVIk60CpiwsZ3G93vUEIO3IyNoH/f0wYK8m9T
|
||||
rDHudUZg3qX4waLG5M43q7Wgc/MbQITxOUSQv7c7ugFFDzQGBzZswY6786m86gpE
|
||||
Ibb3OhjZnzcvQAaRHhdlQWIMm2nrAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAtCu4
|
||||
nUhVVxYUntneD9+h8Mg9q6q+auNKyExzyLwaxlAoo7TJHidbtS4J5iNmZgXL0Fkb
|
||||
FFBjvSfpJIlJ00zbhNYS5f6GuoEDmFJl0ZxBHjJnyp378OD8uTs7fLvjx79LjSTb
|
||||
NYiytVbZPQUQ5Yaxu2jXnimvw3rrszlaEXAMPLE
|
||||
-----END RSA PRIVATE KEY-----"""
|
||||
'fingerprint': random_fingerprint(),
|
||||
'material': material
|
||||
}
|
||||
|
||||
|
||||
|
@ -11,6 +11,7 @@ from .utils import region_from_glacier_url, vault_from_glacier_url
|
||||
class GlacierResponse(_TemplateEnvironmentMixin):
|
||||
|
||||
def __init__(self, backend):
|
||||
super(GlacierResponse, self).__init__()
|
||||
self.backend = backend
|
||||
|
||||
@classmethod
|
||||
|
@ -13,6 +13,15 @@ class ResourceNotFoundError(BadRequest):
|
||||
})
|
||||
|
||||
|
||||
class ResourceInUseError(BadRequest):
|
||||
def __init__(self, message):
|
||||
super(ResourceNotFoundError, self).__init__()
|
||||
self.description = json.dumps({
|
||||
"message": message,
|
||||
'__type': 'ResourceInUseException',
|
||||
})
|
||||
|
||||
|
||||
class StreamNotFoundError(ResourceNotFoundError):
|
||||
def __init__(self, stream_name):
|
||||
super(StreamNotFoundError, self).__init__(
|
||||
|
@ -1,9 +1,12 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
import time
|
||||
|
||||
import boto.kinesis
|
||||
from moto.compat import OrderedDict
|
||||
from moto.core import BaseBackend
|
||||
from .exceptions import StreamNotFoundError, ShardNotFoundError
|
||||
from .exceptions import StreamNotFoundError, ShardNotFoundError, ResourceInUseError
|
||||
from .utils import compose_shard_iterator, compose_new_shard_iterator, decompose_shard_iterator
|
||||
|
||||
|
||||
@ -124,12 +127,82 @@ class Stream(object):
|
||||
}
|
||||
|
||||
|
||||
class FirehoseRecord(object):
|
||||
def __init__(self, record_data):
|
||||
self.record_id = 12345678
|
||||
self.record_data = record_data
|
||||
|
||||
|
||||
class DeliveryStream(object):
|
||||
def __init__(self, stream_name, **stream_kwargs):
|
||||
self.name = stream_name
|
||||
self.redshift_username = stream_kwargs['redshift_username']
|
||||
self.redshift_password = stream_kwargs['redshift_password']
|
||||
self.redshift_jdbc_url = stream_kwargs['redshift_jdbc_url']
|
||||
self.redshift_role_arn = stream_kwargs['redshift_role_arn']
|
||||
self.redshift_copy_command = stream_kwargs['redshift_copy_command']
|
||||
|
||||
self.redshift_s3_role_arn = stream_kwargs['redshift_s3_role_arn']
|
||||
self.redshift_s3_bucket_arn = stream_kwargs['redshift_s3_bucket_arn']
|
||||
self.redshift_s3_prefix = stream_kwargs['redshift_s3_prefix']
|
||||
self.redshift_s3_compression_format = stream_kwargs.get('redshift_s3_compression_format', 'UNCOMPRESSED')
|
||||
self.redshift_s3_buffering_hings = stream_kwargs['redshift_s3_buffering_hings']
|
||||
|
||||
self.records = []
|
||||
self.status = 'ACTIVE'
|
||||
self.create_at = datetime.datetime.utcnow()
|
||||
self.last_updated = datetime.datetime.utcnow()
|
||||
|
||||
@property
|
||||
def arn(self):
|
||||
return 'arn:aws:firehose:us-east-1:123456789012:deliverystream/{0}'.format(self.name)
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"DeliveryStreamDescription": {
|
||||
"CreateTimestamp": time.mktime(self.create_at.timetuple()),
|
||||
"DeliveryStreamARN": self.arn,
|
||||
"DeliveryStreamName": self.name,
|
||||
"DeliveryStreamStatus": self.status,
|
||||
"Destinations": [
|
||||
{
|
||||
"DestinationId": "string",
|
||||
"RedshiftDestinationDescription": {
|
||||
"ClusterJDBCURL": self.redshift_jdbc_url,
|
||||
"CopyCommand": self.redshift_copy_command,
|
||||
"RoleARN": self.redshift_role_arn,
|
||||
"S3DestinationDescription": {
|
||||
"BucketARN": self.redshift_s3_bucket_arn,
|
||||
"BufferingHints": self.redshift_s3_buffering_hings,
|
||||
"CompressionFormat": self.redshift_s3_compression_format,
|
||||
"Prefix": self.redshift_s3_prefix,
|
||||
"RoleARN": self.redshift_s3_role_arn
|
||||
},
|
||||
"Username": self.redshift_username,
|
||||
},
|
||||
}
|
||||
],
|
||||
"HasMoreDestinations": False,
|
||||
"LastUpdateTimestamp": time.mktime(self.last_updated.timetuple()),
|
||||
"VersionId": "string",
|
||||
}
|
||||
}
|
||||
|
||||
def put_record(self, record_data):
|
||||
record = FirehoseRecord(record_data)
|
||||
self.records.append(record)
|
||||
return record
|
||||
|
||||
|
||||
class KinesisBackend(BaseBackend):
|
||||
|
||||
def __init__(self):
|
||||
self.streams = {}
|
||||
self.delivery_streams = {}
|
||||
|
||||
def create_stream(self, stream_name, shard_count, region):
|
||||
if stream_name in self.streams:
|
||||
return ResourceInUseError(stream_name)
|
||||
stream = Stream(stream_name, shard_count, region)
|
||||
self.streams[stream_name] = stream
|
||||
return stream
|
||||
@ -180,6 +253,52 @@ class KinesisBackend(BaseBackend):
|
||||
|
||||
return sequence_number, shard_id
|
||||
|
||||
def put_records(self, stream_name, records):
|
||||
stream = self.describe_stream(stream_name)
|
||||
|
||||
response = {
|
||||
"FailedRecordCount": 0,
|
||||
"Records" : []
|
||||
}
|
||||
|
||||
for record in records:
|
||||
partition_key = record.get("PartitionKey")
|
||||
explicit_hash_key = record.get("ExplicitHashKey")
|
||||
data = record.get("data")
|
||||
|
||||
sequence_number, shard_id = stream.put_record(
|
||||
partition_key, explicit_hash_key, None, data
|
||||
)
|
||||
response['Records'].append({
|
||||
"SequenceNumber": sequence_number,
|
||||
"ShardId": shard_id
|
||||
})
|
||||
|
||||
return response
|
||||
|
||||
''' Firehose '''
|
||||
def create_delivery_stream(self, stream_name, **stream_kwargs):
|
||||
stream = DeliveryStream(stream_name, **stream_kwargs)
|
||||
self.delivery_streams[stream_name] = stream
|
||||
return stream
|
||||
|
||||
def get_delivery_stream(self, stream_name):
|
||||
if stream_name in self.delivery_streams:
|
||||
return self.delivery_streams[stream_name]
|
||||
else:
|
||||
raise StreamNotFoundError(stream_name)
|
||||
|
||||
def list_delivery_streams(self):
|
||||
return self.delivery_streams.values()
|
||||
|
||||
def delete_delivery_stream(self, stream_name):
|
||||
self.delivery_streams.pop(stream_name)
|
||||
|
||||
def put_firehose_record(self, stream_name, record_data):
|
||||
stream = self.get_delivery_stream(stream_name)
|
||||
record = stream.put_record(record_data)
|
||||
return record
|
||||
|
||||
kinesis_backends = {}
|
||||
for region in boto.kinesis.regions():
|
||||
kinesis_backends[region.name] = KinesisBackend()
|
||||
|
@ -16,6 +16,11 @@ class KinesisResponse(BaseResponse):
|
||||
def kinesis_backend(self):
|
||||
return kinesis_backends[self.region]
|
||||
|
||||
@property
|
||||
def is_firehose(self):
|
||||
host = self.headers.get('host') or self.headers['Host']
|
||||
return host.startswith('firehose')
|
||||
|
||||
def create_stream(self):
|
||||
stream_name = self.parameters.get('StreamName')
|
||||
shard_count = self.parameters.get('ShardCount')
|
||||
@ -67,6 +72,8 @@ class KinesisResponse(BaseResponse):
|
||||
})
|
||||
|
||||
def put_record(self):
|
||||
if self.is_firehose:
|
||||
return self.firehose_put_record()
|
||||
stream_name = self.parameters.get("StreamName")
|
||||
partition_key = self.parameters.get("PartitionKey")
|
||||
explicit_hash_key = self.parameters.get("ExplicitHashKey")
|
||||
@ -81,3 +88,83 @@ class KinesisResponse(BaseResponse):
|
||||
"SequenceNumber": sequence_number,
|
||||
"ShardId": shard_id,
|
||||
})
|
||||
|
||||
def put_records(self):
|
||||
if self.is_firehose:
|
||||
return self.firehose_put_record()
|
||||
stream_name = self.parameters.get("StreamName")
|
||||
records = self.parameters.get("Records")
|
||||
|
||||
response = self.kinesis_backend.put_records(
|
||||
stream_name, records
|
||||
)
|
||||
|
||||
return json.dumps(response)
|
||||
|
||||
''' Firehose '''
|
||||
def create_delivery_stream(self):
|
||||
stream_name = self.parameters['DeliveryStreamName']
|
||||
redshift_config = self.parameters.get('RedshiftDestinationConfiguration')
|
||||
|
||||
if redshift_config:
|
||||
redshift_s3_config = redshift_config['S3Configuration']
|
||||
stream_kwargs = {
|
||||
'redshift_username': redshift_config['Username'],
|
||||
'redshift_password': redshift_config['Password'],
|
||||
'redshift_jdbc_url': redshift_config['ClusterJDBCURL'],
|
||||
'redshift_role_arn': redshift_config['RoleARN'],
|
||||
'redshift_copy_command': redshift_config['CopyCommand'],
|
||||
|
||||
'redshift_s3_role_arn': redshift_s3_config['RoleARN'],
|
||||
'redshift_s3_bucket_arn': redshift_s3_config['BucketARN'],
|
||||
'redshift_s3_prefix': redshift_s3_config['Prefix'],
|
||||
'redshift_s3_compression_format': redshift_s3_config.get('CompressionFormat'),
|
||||
'redshift_s3_buffering_hings': redshift_s3_config['BufferingHints'],
|
||||
}
|
||||
stream = self.kinesis_backend.create_delivery_stream(stream_name, **stream_kwargs)
|
||||
return json.dumps({
|
||||
'DeliveryStreamARN': stream.arn
|
||||
})
|
||||
|
||||
def describe_delivery_stream(self):
|
||||
stream_name = self.parameters["DeliveryStreamName"]
|
||||
stream = self.kinesis_backend.get_delivery_stream(stream_name)
|
||||
return json.dumps(stream.to_dict())
|
||||
|
||||
def list_delivery_streams(self):
|
||||
streams = self.kinesis_backend.list_delivery_streams()
|
||||
return json.dumps({
|
||||
"DeliveryStreamNames": [
|
||||
stream.name for stream in streams
|
||||
],
|
||||
"HasMoreDeliveryStreams": False
|
||||
})
|
||||
|
||||
def delete_delivery_stream(self):
|
||||
stream_name = self.parameters['DeliveryStreamName']
|
||||
self.kinesis_backend.delete_delivery_stream(stream_name)
|
||||
return json.dumps({})
|
||||
|
||||
def firehose_put_record(self):
|
||||
stream_name = self.parameters['DeliveryStreamName']
|
||||
record_data = self.parameters['Record']['Data']
|
||||
|
||||
record = self.kinesis_backend.put_firehose_record(stream_name, record_data)
|
||||
return json.dumps({
|
||||
"RecordId": record.record_id,
|
||||
})
|
||||
|
||||
def put_record_batch(self):
|
||||
stream_name = self.parameters['DeliveryStreamName']
|
||||
records = self.parameters['Records']
|
||||
|
||||
request_responses = []
|
||||
for record in records:
|
||||
record_response = self.kinesis_backend.put_firehose_record(stream_name, record['Data'])
|
||||
request_responses.append({
|
||||
"RecordId": record_response.record_id
|
||||
})
|
||||
return json.dumps({
|
||||
"FailedPutCount": 0,
|
||||
"RequestResponses": request_responses,
|
||||
})
|
||||
|
@ -3,6 +3,7 @@ from .responses import KinesisResponse
|
||||
|
||||
url_bases = [
|
||||
"https?://kinesis.(.+).amazonaws.com",
|
||||
"https?://firehose.(.+).amazonaws.com",
|
||||
]
|
||||
|
||||
url_paths = {
|
||||
|
@ -135,7 +135,7 @@ class Database(object):
|
||||
"engine": properties.get("Engine"),
|
||||
"engine_version": properties.get("EngineVersion"),
|
||||
"iops": properties.get("Iops"),
|
||||
"master_password": properties.get('MasterUserPassword'),
|
||||
"master_user_password": properties.get('MasterUserPassword'),
|
||||
"master_username": properties.get('MasterUsername'),
|
||||
"multi_az": properties.get("MultiAZ"),
|
||||
"port": properties.get('Port', 3306),
|
||||
|
@ -27,7 +27,7 @@ class RDS2Response(BaseResponse):
|
||||
"engine": self._get_param("Engine"),
|
||||
"engine_version": self._get_param("EngineVersion"),
|
||||
"iops": self._get_int_param("Iops"),
|
||||
"master_password": self._get_param('MasterUserPassword'),
|
||||
"master_user_password": self._get_param('MasterUserPassword'),
|
||||
"master_username": self._get_param('MasterUsername'),
|
||||
"multi_az": self._get_bool_param("MultiAZ"),
|
||||
# OptionGroupName
|
||||
@ -504,4 +504,4 @@ ADD_TAGS_TO_RESOURCE_TEMPLATE = \
|
||||
|
||||
REMOVE_TAGS_FROM_RESOURCE_TEMPLATE = \
|
||||
"""{"RemoveTagsFromResourceResponse": {"ResponseMetadata": {"RequestId": "c6499a01-a664-11e4-8069-fb454b71a80e"}}}
|
||||
"""
|
||||
"""
|
||||
|
@ -68,6 +68,7 @@ class RecordSet(object):
|
||||
self.records = kwargs.get('ResourceRecords', [])
|
||||
self.set_identifier = kwargs.get('SetIdentifier')
|
||||
self.weight = kwargs.get('Weight')
|
||||
self.region = kwargs.get('Region')
|
||||
self.health_check = kwargs.get('HealthCheckId')
|
||||
|
||||
@classmethod
|
||||
@ -89,6 +90,9 @@ class RecordSet(object):
|
||||
{% if record_set.weight %}
|
||||
<Weight>{{ record_set.weight }}</Weight>
|
||||
{% endif %}
|
||||
{% if record_set.region %}
|
||||
<Region>{{ record_set.region }}</Region>
|
||||
{% endif %}
|
||||
<TTL>{{ record_set.ttl }}</TTL>
|
||||
<ResourceRecords>
|
||||
{% for record in record_set.records %}
|
||||
|
@ -24,6 +24,7 @@ class FakeKey(object):
|
||||
self.name = name
|
||||
self.value = value
|
||||
self.last_modified = datetime.datetime.utcnow()
|
||||
self.acl = get_canned_acl('private')
|
||||
self._storage_class = storage
|
||||
self._metadata = {}
|
||||
self._expiry = None
|
||||
@ -45,6 +46,9 @@ class FakeKey(object):
|
||||
def set_storage_class(self, storage_class):
|
||||
self._storage_class = storage_class
|
||||
|
||||
def set_acl(self, acl):
|
||||
self.acl = acl
|
||||
|
||||
def append_to_value(self, value):
|
||||
self.value += value
|
||||
self.last_modified = datetime.datetime.utcnow()
|
||||
@ -161,6 +165,61 @@ class FakeMultipart(object):
|
||||
yield self.parts[part_id]
|
||||
|
||||
|
||||
class FakeGrantee(object):
|
||||
def __init__(self, id='', uri='', display_name=''):
|
||||
self.id = id
|
||||
self.uri = uri
|
||||
self.display_name = display_name
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return 'Group' if self.uri else 'CanonicalUser'
|
||||
|
||||
|
||||
ALL_USERS_GRANTEE = FakeGrantee(uri='http://acs.amazonaws.com/groups/global/AllUsers')
|
||||
AUTHENTICATED_USERS_GRANTEE = FakeGrantee(uri='http://acs.amazonaws.com/groups/global/AuthenticatedUsers')
|
||||
LOG_DELIVERY_GRANTEE = FakeGrantee(uri='http://acs.amazonaws.com/groups/s3/LogDelivery')
|
||||
|
||||
PERMISSION_FULL_CONTROL = 'FULL_CONTROL'
|
||||
PERMISSION_WRITE = 'WRITE'
|
||||
PERMISSION_READ = 'READ'
|
||||
PERMISSION_WRITE_ACP = 'WRITE_ACP'
|
||||
PERMISSION_READ_ACP = 'READ_ACP'
|
||||
|
||||
|
||||
class FakeGrant(object):
|
||||
def __init__(self, grantees, permissions):
|
||||
self.grantees = grantees
|
||||
self.permissions = permissions
|
||||
|
||||
|
||||
class FakeAcl(object):
|
||||
def __init__(self, grants=[]):
|
||||
self.grants = grants
|
||||
|
||||
|
||||
def get_canned_acl(acl):
|
||||
owner_grantee = FakeGrantee(id='75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a')
|
||||
grants = [FakeGrant([owner_grantee], [PERMISSION_FULL_CONTROL])]
|
||||
if acl == 'private':
|
||||
pass # no other permissions
|
||||
elif acl == 'public-read':
|
||||
grants.append(FakeGrant([ALL_USERS_GRANTEE], [PERMISSION_READ]))
|
||||
elif acl == 'public-read-write':
|
||||
grants.append(FakeGrant([ALL_USERS_GRANTEE], [PERMISSION_READ, PERMISSION_WRITE]))
|
||||
elif acl == 'authenticated-read':
|
||||
grants.append(FakeGrant([AUTHENTICATED_USERS_GRANTEE], [PERMISSION_READ]))
|
||||
elif acl == 'bucket-owner-read':
|
||||
pass # TODO: bucket owner ACL
|
||||
elif acl == 'bucket-owner-full-control':
|
||||
pass # TODO: bucket owner ACL
|
||||
elif acl == 'log-delivery-write':
|
||||
grants.append(FakeGrant([LOG_DELIVERY_GRANTEE], [PERMISSION_READ_ACP, PERMISSION_WRITE]))
|
||||
else:
|
||||
assert False, 'Unknown canned acl: %s' % (acl,)
|
||||
return FakeAcl(grants=grants)
|
||||
|
||||
|
||||
class LifecycleRule(object):
|
||||
def __init__(self, id=None, prefix=None, status=None, expiration_days=None,
|
||||
expiration_date=None, transition_days=None,
|
||||
@ -185,6 +244,8 @@ class FakeBucket(object):
|
||||
self.versioning_status = None
|
||||
self.rules = []
|
||||
self.policy = None
|
||||
self.website_configuration = None
|
||||
self.acl = get_canned_acl('private')
|
||||
|
||||
@property
|
||||
def location(self):
|
||||
@ -213,6 +274,9 @@ class FakeBucket(object):
|
||||
def delete_lifecycle(self):
|
||||
self.rules = []
|
||||
|
||||
def set_website_configuration(self, website_configuration):
|
||||
self.website_configuration = website_configuration
|
||||
|
||||
def get_cfn_attribute(self, attribute_name):
|
||||
from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
|
||||
if attribute_name == 'DomainName':
|
||||
@ -221,6 +285,9 @@ class FakeBucket(object):
|
||||
raise NotImplementedError('"Fn::GetAtt" : [ "{0}" , "WebsiteURL" ]"')
|
||||
raise UnformattedGetAttTemplateException()
|
||||
|
||||
def set_acl(self, acl):
|
||||
self.acl = acl
|
||||
|
||||
|
||||
class S3Backend(BaseBackend):
|
||||
|
||||
@ -284,6 +351,14 @@ class S3Backend(BaseBackend):
|
||||
bucket = self.get_bucket(bucket_name)
|
||||
bucket.set_lifecycle(rules)
|
||||
|
||||
def set_bucket_website_configuration(self, bucket_name, website_configuration):
|
||||
bucket = self.get_bucket(bucket_name)
|
||||
bucket.set_website_configuration(website_configuration)
|
||||
|
||||
def get_bucket_website_configuration(self, bucket_name):
|
||||
bucket = self.get_bucket(bucket_name)
|
||||
return bucket.website_configuration
|
||||
|
||||
def set_key(self, bucket_name, key_name, value, storage=None, etag=None):
|
||||
key_name = clean_key_name(key_name)
|
||||
|
||||
@ -399,7 +474,7 @@ class S3Backend(BaseBackend):
|
||||
bucket = self.get_bucket(bucket_name)
|
||||
return bucket.keys.pop(key_name)
|
||||
|
||||
def copy_key(self, src_bucket_name, src_key_name, dest_bucket_name, dest_key_name, storage=None):
|
||||
def copy_key(self, src_bucket_name, src_key_name, dest_bucket_name, dest_key_name, storage=None, acl=None):
|
||||
src_key_name = clean_key_name(src_key_name)
|
||||
dest_key_name = clean_key_name(dest_key_name)
|
||||
src_bucket = self.get_bucket(src_bucket_name)
|
||||
@ -409,6 +484,17 @@ class S3Backend(BaseBackend):
|
||||
key = key.copy(dest_key_name)
|
||||
dest_bucket.keys[dest_key_name] = key
|
||||
if storage is not None:
|
||||
dest_bucket.keys[dest_key_name].set_storage_class(storage)
|
||||
key.set_storage_class(storage)
|
||||
if acl is not None:
|
||||
key.set_acl(acl)
|
||||
|
||||
def set_bucket_acl(self, bucket_name, acl):
|
||||
bucket = self.get_bucket(bucket_name)
|
||||
bucket.set_acl(acl)
|
||||
|
||||
def get_bucket_acl(self, bucket_name):
|
||||
bucket = self.get_bucket(bucket_name)
|
||||
return bucket.acl
|
||||
|
||||
|
||||
s3_backend = S3Backend()
|
||||
|
@ -9,7 +9,7 @@ import xmltodict
|
||||
from moto.core.responses import _TemplateEnvironmentMixin
|
||||
|
||||
from .exceptions import BucketAlreadyExists, S3ClientError, InvalidPartOrder
|
||||
from .models import s3_backend
|
||||
from .models import s3_backend, get_canned_acl, FakeGrantee, FakeGrant, FakeAcl
|
||||
from .utils import bucket_name_from_url, metadata_from_headers
|
||||
from xml.dom import minidom
|
||||
|
||||
@ -22,10 +22,18 @@ def parse_key_name(pth):
|
||||
|
||||
|
||||
class ResponseObject(_TemplateEnvironmentMixin):
|
||||
def __init__(self, backend, bucket_name_from_url, parse_key_name):
|
||||
def __init__(self, backend, bucket_name_from_url, parse_key_name,
|
||||
is_delete_keys=None):
|
||||
super(ResponseObject, self).__init__()
|
||||
self.backend = backend
|
||||
self.bucket_name_from_url = bucket_name_from_url
|
||||
self.parse_key_name = parse_key_name
|
||||
if is_delete_keys:
|
||||
self.is_delete_keys = is_delete_keys
|
||||
|
||||
@staticmethod
|
||||
def is_delete_keys(path, bucket_name):
|
||||
return path == u'/?delete'
|
||||
|
||||
def all_buckets(self):
|
||||
# No bucket specified. Listing all buckets
|
||||
@ -72,7 +80,7 @@ class ResponseObject(_TemplateEnvironmentMixin):
|
||||
elif method == 'GET':
|
||||
return self._bucket_response_get(bucket_name, querystring, headers)
|
||||
elif method == 'PUT':
|
||||
return self._bucket_response_put(body, region_name, bucket_name, querystring, headers)
|
||||
return self._bucket_response_put(request, body, region_name, bucket_name, querystring, headers)
|
||||
elif method == 'DELETE':
|
||||
return self._bucket_response_delete(body, bucket_name, querystring, headers)
|
||||
elif method == 'POST':
|
||||
@ -94,29 +102,36 @@ class ResponseObject(_TemplateEnvironmentMixin):
|
||||
prefix = querystring.get('prefix', [None])[0]
|
||||
multiparts = [upload for upload in multiparts if upload.key_name.startswith(prefix)]
|
||||
template = self.response_template(S3_ALL_MULTIPARTS)
|
||||
return 200, headers, template.render(
|
||||
return template.render(
|
||||
bucket_name=bucket_name,
|
||||
uploads=multiparts)
|
||||
elif 'location' in querystring:
|
||||
bucket = self.backend.get_bucket(bucket_name)
|
||||
template = self.response_template(S3_BUCKET_LOCATION)
|
||||
return 200, headers, template.render(location=bucket.location)
|
||||
return template.render(location=bucket.location)
|
||||
elif 'lifecycle' in querystring:
|
||||
bucket = self.backend.get_bucket(bucket_name)
|
||||
if not bucket.rules:
|
||||
return 404, headers, "NoSuchLifecycleConfiguration"
|
||||
template = self.response_template(S3_BUCKET_LIFECYCLE_CONFIGURATION)
|
||||
return 200, headers, template.render(rules=bucket.rules)
|
||||
return template.render(rules=bucket.rules)
|
||||
elif 'versioning' in querystring:
|
||||
versioning = self.backend.get_bucket_versioning(bucket_name)
|
||||
template = self.response_template(S3_BUCKET_GET_VERSIONING)
|
||||
return 200, headers, template.render(status=versioning)
|
||||
return template.render(status=versioning)
|
||||
elif 'policy' in querystring:
|
||||
policy = self.backend.get_bucket_policy(bucket_name)
|
||||
if not policy:
|
||||
template = self.response_template(S3_NO_POLICY)
|
||||
return 404, headers, template.render(bucket_name=bucket_name)
|
||||
return 200, headers, policy
|
||||
elif 'website' in querystring:
|
||||
website_configuration = self.backend.get_bucket_website_configuration(bucket_name)
|
||||
return website_configuration
|
||||
elif 'acl' in querystring:
|
||||
bucket = self.backend.get_bucket(bucket_name)
|
||||
template = self.response_template(S3_OBJECT_ACL_RESPONSE)
|
||||
return template.render(obj=bucket)
|
||||
elif 'versions' in querystring:
|
||||
delimiter = querystring.get('delimiter', [None])[0]
|
||||
encoding_type = querystring.get('encoding-type', [None])[0]
|
||||
@ -157,7 +172,7 @@ class ResponseObject(_TemplateEnvironmentMixin):
|
||||
result_folders=result_folders
|
||||
)
|
||||
|
||||
def _bucket_response_put(self, body, region_name, bucket_name, querystring, headers):
|
||||
def _bucket_response_put(self, request, body, region_name, bucket_name, querystring, headers):
|
||||
if 'versioning' in querystring:
|
||||
ver = re.search('<Status>([A-Za-z]+)</Status>', body)
|
||||
if ver:
|
||||
@ -176,6 +191,14 @@ class ResponseObject(_TemplateEnvironmentMixin):
|
||||
elif 'policy' in querystring:
|
||||
self.backend.set_bucket_policy(bucket_name, body)
|
||||
return 'True'
|
||||
elif 'acl' in querystring:
|
||||
acl = self._acl_from_headers(request.headers)
|
||||
# TODO: Support the XML-based ACL format
|
||||
self.backend.set_bucket_acl(bucket_name, acl)
|
||||
return ""
|
||||
elif 'website' in querystring:
|
||||
self.backend.set_bucket_website_configuration(bucket_name, body)
|
||||
return ""
|
||||
else:
|
||||
try:
|
||||
new_bucket = self.backend.create_bucket(bucket_name, region_name)
|
||||
@ -209,7 +232,7 @@ class ResponseObject(_TemplateEnvironmentMixin):
|
||||
return 409, headers, template.render(bucket=removed_bucket)
|
||||
|
||||
def _bucket_response_post(self, request, bucket_name, headers):
|
||||
if request.path == u'/?delete':
|
||||
if self.is_delete_keys(request.path, bucket_name):
|
||||
return self._bucket_response_delete_keys(request, bucket_name, headers)
|
||||
|
||||
# POST to bucket-url should create file from form
|
||||
@ -294,7 +317,7 @@ class ResponseObject(_TemplateEnvironmentMixin):
|
||||
|
||||
def _key_response(self, request, full_url, headers):
|
||||
parsed_url = urlparse(full_url)
|
||||
query = parse_qs(parsed_url.query)
|
||||
query = parse_qs(parsed_url.query, keep_blank_values=True)
|
||||
method = request.method
|
||||
|
||||
key_name = self.parse_key_name(parsed_url.path)
|
||||
@ -310,18 +333,18 @@ class ResponseObject(_TemplateEnvironmentMixin):
|
||||
if method == 'GET':
|
||||
return self._key_response_get(bucket_name, query, key_name, headers)
|
||||
elif method == 'PUT':
|
||||
return self._key_response_put(request, parsed_url, body, bucket_name, query, key_name, headers)
|
||||
return self._key_response_put(request, body, bucket_name, query, key_name, headers)
|
||||
elif method == 'HEAD':
|
||||
return self._key_response_head(bucket_name, key_name, headers)
|
||||
elif method == 'DELETE':
|
||||
return self._key_response_delete(bucket_name, query, key_name, headers)
|
||||
elif method == 'POST':
|
||||
return self._key_response_post(request, body, parsed_url, bucket_name, query, key_name, headers)
|
||||
return self._key_response_post(request, body, bucket_name, query, key_name, headers)
|
||||
else:
|
||||
raise NotImplementedError("Method {0} has not been impelemented in the S3 backend yet".format(method))
|
||||
|
||||
def _key_response_get(self, bucket_name, query, key_name, headers):
|
||||
if 'uploadId' in query:
|
||||
if query.get('uploadId'):
|
||||
upload_id = query['uploadId'][0]
|
||||
parts = self.backend.list_multipart(bucket_name, upload_id)
|
||||
template = self.response_template(S3_MULTIPART_LIST_RESPONSE)
|
||||
@ -335,14 +358,18 @@ class ResponseObject(_TemplateEnvironmentMixin):
|
||||
version_id = query.get('versionId', [None])[0]
|
||||
key = self.backend.get_key(
|
||||
bucket_name, key_name, version_id=version_id)
|
||||
if 'acl' in query:
|
||||
template = self.response_template(S3_OBJECT_ACL_RESPONSE)
|
||||
return 200, headers, template.render(obj=key)
|
||||
|
||||
if key:
|
||||
headers.update(key.metadata)
|
||||
return 200, headers, key.value
|
||||
else:
|
||||
return 404, headers, ""
|
||||
|
||||
def _key_response_put(self, request, parsed_url, body, bucket_name, query, key_name, headers):
|
||||
if 'uploadId' in query and 'partNumber' in query:
|
||||
def _key_response_put(self, request, body, bucket_name, query, key_name, headers):
|
||||
if query.get('uploadId') and query.get('partNumber'):
|
||||
upload_id = query['uploadId'][0]
|
||||
part_number = int(query['partNumber'][0])
|
||||
if 'x-amz-copy-source' in request.headers:
|
||||
@ -361,16 +388,19 @@ class ResponseObject(_TemplateEnvironmentMixin):
|
||||
return 200, headers, response
|
||||
|
||||
storage_class = request.headers.get('x-amz-storage-class', 'STANDARD')
|
||||
acl = self._acl_from_headers(request.headers)
|
||||
|
||||
if parsed_url.query == 'acl':
|
||||
# We don't implement ACL yet, so just return
|
||||
if 'acl' in query:
|
||||
key = self.backend.get_key(bucket_name, key_name)
|
||||
# TODO: Support the XML-based ACL format
|
||||
key.set_acl(acl)
|
||||
return 200, headers, ""
|
||||
|
||||
if 'x-amz-copy-source' in request.headers:
|
||||
# Copy key
|
||||
src_bucket, src_key = request.headers.get("x-amz-copy-source").split("/", 1)
|
||||
self.backend.copy_key(src_bucket, src_key, bucket_name, key_name,
|
||||
storage=storage_class)
|
||||
storage=storage_class, acl=acl)
|
||||
mdirective = request.headers.get('x-amz-metadata-directive')
|
||||
if mdirective is not None and mdirective == 'REPLACE':
|
||||
new_key = self.backend.get_key(bucket_name, key_name)
|
||||
@ -393,6 +423,7 @@ class ResponseObject(_TemplateEnvironmentMixin):
|
||||
request.streaming = True
|
||||
metadata = metadata_from_headers(request.headers)
|
||||
new_key.set_metadata(metadata)
|
||||
new_key.set_acl(acl)
|
||||
|
||||
template = self.response_template(S3_OBJECT_RESPONSE)
|
||||
headers.update(new_key.response_dict)
|
||||
@ -407,8 +438,40 @@ class ResponseObject(_TemplateEnvironmentMixin):
|
||||
else:
|
||||
return 404, headers, ""
|
||||
|
||||
def _acl_from_headers(self, headers):
|
||||
canned_acl = headers.get('x-amz-acl', '')
|
||||
if canned_acl:
|
||||
return get_canned_acl(canned_acl)
|
||||
|
||||
grants = []
|
||||
for header, value in headers.items():
|
||||
if not header.startswith('x-amz-grant-'):
|
||||
continue
|
||||
|
||||
permission = {
|
||||
'read': 'READ',
|
||||
'write': 'WRITE',
|
||||
'read-acp': 'READ_ACP',
|
||||
'write-acp': 'WRITE_ACP',
|
||||
'full-control': 'FULL_CONTROL',
|
||||
}[header[len('x-amz-grant-'):]]
|
||||
|
||||
grantees = []
|
||||
for key_and_value in value.split(","):
|
||||
key, value = re.match('([^=]+)="([^"]+)"', key_and_value.strip()).groups()
|
||||
if key.lower() == 'id':
|
||||
grantees.append(FakeGrantee(id=value))
|
||||
else:
|
||||
grantees.append(FakeGrantee(uri=value))
|
||||
grants.append(FakeGrant(grantees, [permission]))
|
||||
|
||||
if grants:
|
||||
return FakeAcl(grants)
|
||||
else:
|
||||
return None
|
||||
|
||||
def _key_response_delete(self, bucket_name, query, key_name, headers):
|
||||
if 'uploadId' in query:
|
||||
if query.get('uploadId'):
|
||||
upload_id = query['uploadId'][0]
|
||||
self.backend.cancel_multipart(bucket_name, upload_id)
|
||||
return 204, headers, ""
|
||||
@ -428,8 +491,8 @@ class ResponseObject(_TemplateEnvironmentMixin):
|
||||
raise InvalidPartOrder()
|
||||
yield (pn, p.getElementsByTagName('ETag')[0].firstChild.wholeText)
|
||||
|
||||
def _key_response_post(self, request, body, parsed_url, bucket_name, query, key_name, headers):
|
||||
if body == b'' and parsed_url.query == 'uploads':
|
||||
def _key_response_post(self, request, body, bucket_name, query, key_name, headers):
|
||||
if body == b'' and 'uploads' in query:
|
||||
metadata = metadata_from_headers(request.headers)
|
||||
multipart = self.backend.initiate_multipart(bucket_name, key_name, metadata)
|
||||
|
||||
@ -441,7 +504,7 @@ class ResponseObject(_TemplateEnvironmentMixin):
|
||||
)
|
||||
return 200, headers, response
|
||||
|
||||
if 'uploadId' in query:
|
||||
if query.get('uploadId'):
|
||||
body = self._complete_multipart_body(body)
|
||||
upload_id = query['uploadId'][0]
|
||||
key = self.backend.complete_multipart(bucket_name, upload_id, body)
|
||||
@ -451,7 +514,7 @@ class ResponseObject(_TemplateEnvironmentMixin):
|
||||
key_name=key.name,
|
||||
etag=key.etag,
|
||||
)
|
||||
elif parsed_url.query == 'restore':
|
||||
elif 'restore' in query:
|
||||
es = minidom.parseString(body).getElementsByTagName('Days')
|
||||
days = es[0].childNodes[0].wholeText
|
||||
key = self.backend.get_key(bucket_name, key_name)
|
||||
@ -635,6 +698,37 @@ S3_OBJECT_RESPONSE = """<PutObjectResponse xmlns="http://s3.amazonaws.com/doc/20
|
||||
</PutObjectResponse>
|
||||
</PutObjectResponse>"""
|
||||
|
||||
S3_OBJECT_ACL_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<AccessControlPolicy xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<Owner>
|
||||
<ID>75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a</ID>
|
||||
<DisplayName>webfile</DisplayName>
|
||||
</Owner>
|
||||
<AccessControlList>
|
||||
{% for grant in obj.acl.grants %}
|
||||
<Grant>
|
||||
{% for grantee in grant.grantees %}
|
||||
<Grantee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:type="{{ grantee.type }}">
|
||||
{% if grantee.uri %}
|
||||
<URI>{{ grantee.uri }}</URI>
|
||||
{% endif %}
|
||||
{% if grantee.id %}
|
||||
<ID>{{ grantee.id }}</ID>
|
||||
{% endif %}
|
||||
{% if grantee.display_name %}
|
||||
<DisplayName>{{ grantee.display_name }}</DisplayName>
|
||||
{% endif %}
|
||||
</Grantee>
|
||||
{% endfor %}
|
||||
{% for permission in grant.permissions %}
|
||||
<Permission>{{ permission }}</Permission>
|
||||
{% endfor %}
|
||||
</Grant>
|
||||
{% endfor %}
|
||||
</AccessControlList>
|
||||
</AccessControlPolicy>"""
|
||||
|
||||
S3_OBJECT_COPY_RESPONSE = """<CopyObjectResponse xmlns="http://doc.s3.amazonaws.com/2006-03-01">
|
||||
<CopyObjectResponse>
|
||||
<ETag>{{ key.etag }}</ETag>
|
||||
@ -710,7 +804,7 @@ S3_ALL_MULTIPARTS = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
</Initiator>
|
||||
<Owner>
|
||||
<ID>75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a</ID>
|
||||
<DisplayName>OwnerDisplayName</DisplayName>
|
||||
<DisplayName>webfile</DisplayName>
|
||||
</Owner>
|
||||
<StorageClass>STANDARD</StorageClass>
|
||||
<Initiated>2010-11-10T20:48:33.000Z</Initiated>
|
||||
|
@ -9,8 +9,14 @@ from moto.s3.responses import ResponseObject
|
||||
def parse_key_name(pth):
|
||||
return "/".join(pth.rstrip("/").split("/")[2:])
|
||||
|
||||
|
||||
def is_delete_keys(path, bucket_name):
|
||||
return path == u'/' + bucket_name + u'/?delete'
|
||||
|
||||
|
||||
S3BucketPathResponseInstance = ResponseObject(
|
||||
s3bucket_path_backend,
|
||||
bucket_name_from_url,
|
||||
parse_key_name,
|
||||
is_delete_keys,
|
||||
)
|
||||
|
@ -34,7 +34,7 @@ class Message(object):
|
||||
@property
|
||||
def md5(self):
|
||||
body_md5 = hashlib.md5()
|
||||
body_md5.update(self.body.encode('utf-8'))
|
||||
body_md5.update(self._body.encode('utf-8'))
|
||||
return body_md5.hexdigest()
|
||||
|
||||
@property
|
||||
@ -106,9 +106,10 @@ class Queue(object):
|
||||
'VisibilityTimeout',
|
||||
'WaitTimeSeconds']
|
||||
|
||||
def __init__(self, name, visibility_timeout, wait_time_seconds):
|
||||
def __init__(self, name, visibility_timeout, wait_time_seconds, region):
|
||||
self.name = name
|
||||
self.visibility_timeout = visibility_timeout or 30
|
||||
self.region = region
|
||||
|
||||
# wait_time_seconds will be set to immediate return messages
|
||||
self.wait_time_seconds = wait_time_seconds or 0
|
||||
@ -179,6 +180,10 @@ class Queue(object):
|
||||
result[attribute] = getattr(self, camelcase_to_underscores(attribute))
|
||||
return result
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
return "http://sqs.{0}.amazonaws.com/123456789012/{1}".format(self.region, self.name)
|
||||
|
||||
@property
|
||||
def messages(self):
|
||||
return [message for message in self._messages if message.visible and not message.delayed]
|
||||
@ -196,14 +201,20 @@ class Queue(object):
|
||||
|
||||
|
||||
class SQSBackend(BaseBackend):
|
||||
def __init__(self):
|
||||
def __init__(self, region_name):
|
||||
self.region_name = region_name
|
||||
self.queues = {}
|
||||
super(SQSBackend, self).__init__()
|
||||
|
||||
def reset(self):
|
||||
region_name = self.region_name
|
||||
self.__dict__ = {}
|
||||
self.__init__(region_name)
|
||||
|
||||
def create_queue(self, name, visibility_timeout, wait_time_seconds):
|
||||
queue = self.queues.get(name)
|
||||
if queue is None:
|
||||
queue = Queue(name, visibility_timeout, wait_time_seconds)
|
||||
queue = Queue(name, visibility_timeout, wait_time_seconds, self.region_name)
|
||||
self.queues[name] = queue
|
||||
return queue
|
||||
|
||||
@ -314,4 +325,4 @@ class SQSBackend(BaseBackend):
|
||||
|
||||
sqs_backends = {}
|
||||
for region in boto.sqs.regions():
|
||||
sqs_backends[region.name] = SQSBackend()
|
||||
sqs_backends[region.name] = SQSBackend(region.name)
|
||||
|
@ -11,6 +11,7 @@ from .exceptions import (
|
||||
)
|
||||
|
||||
MAXIMUM_VISIBILTY_TIMEOUT = 43200
|
||||
MAXIMUM_MESSAGE_LENGTH = 262144 # 256 KiB
|
||||
DEFAULT_RECEIVED_MESSAGES = 1
|
||||
SQS_REGION_REGEX = r'://(.+?)\.queue\.amazonaws\.com'
|
||||
|
||||
@ -106,6 +107,9 @@ class SQSResponse(BaseResponse):
|
||||
message = self.querystring.get("MessageBody")[0]
|
||||
delay_seconds = self.querystring.get('DelaySeconds')
|
||||
|
||||
if len(message) > MAXIMUM_MESSAGE_LENGTH:
|
||||
return ERROR_TOO_LONG_RESPONSE, dict(status=400)
|
||||
|
||||
if delay_seconds:
|
||||
delay_seconds = int(delay_seconds[0])
|
||||
else:
|
||||
@ -232,7 +236,7 @@ class SQSResponse(BaseResponse):
|
||||
|
||||
CREATE_QUEUE_RESPONSE = """<CreateQueueResponse>
|
||||
<CreateQueueResult>
|
||||
<QueueUrl>http://sqs.us-east-1.amazonaws.com/123456789012/{{ queue.name }}</QueueUrl>
|
||||
<QueueUrl>{{ queue.url }}</QueueUrl>
|
||||
<VisibilityTimeout>{{ queue.visibility_timeout }}</VisibilityTimeout>
|
||||
</CreateQueueResult>
|
||||
<ResponseMetadata>
|
||||
@ -244,7 +248,7 @@ CREATE_QUEUE_RESPONSE = """<CreateQueueResponse>
|
||||
|
||||
GET_QUEUE_URL_RESPONSE = """<GetQueueUrlResponse>
|
||||
<GetQueueUrlResult>
|
||||
<QueueUrl>http://sqs.us-east-1.amazonaws.com/123456789012/{{ queue.name }}</QueueUrl>
|
||||
<QueueUrl>{{ queue.url }}</QueueUrl>
|
||||
</GetQueueUrlResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>470a6f13-2ed9-4181-ad8a-2fdea142988e</RequestId>
|
||||
@ -254,7 +258,7 @@ GET_QUEUE_URL_RESPONSE = """<GetQueueUrlResponse>
|
||||
LIST_QUEUES_RESPONSE = """<ListQueuesResponse>
|
||||
<ListQueuesResult>
|
||||
{% for queue in queues %}
|
||||
<QueueUrl>http://sqs.us-east-1.amazonaws.com/123456789012/{{ queue.name }}</QueueUrl>
|
||||
<QueueUrl>{{ queue.url }}</QueueUrl>
|
||||
{% endfor %}
|
||||
</ListQueuesResult>
|
||||
<ResponseMetadata>
|
||||
@ -417,3 +421,13 @@ PURGE_QUEUE_RESPONSE = """<PurgeQueueResponse>
|
||||
</RequestId>
|
||||
</ResponseMetadata>
|
||||
</PurgeQueueResponse>"""
|
||||
|
||||
ERROR_TOO_LONG_RESPONSE = """<ErrorResponse xmlns="http://queue.amazonaws.com/doc/2012-11-05/">
|
||||
<Error>
|
||||
<Type>Sender</Type>
|
||||
<Code>InvalidParameterValue</Code>
|
||||
<Message>One or more parameters are invalid. Reason: Message must be shorter than 262144 bytes.</Message>
|
||||
<Detail/>
|
||||
</Error>
|
||||
<RequestId>6fde8d1e-52cd-4581-8cd9-c512f4c64223</RequestId>
|
||||
</ErrorResponse>"""
|
||||
|
@ -6,3 +6,4 @@ coverage
|
||||
freezegun
|
||||
flask
|
||||
boto3
|
||||
six
|
2
setup.py
2
setup.py
@ -20,7 +20,7 @@ extras_require = {
|
||||
|
||||
setup(
|
||||
name='moto',
|
||||
version='0.4.12',
|
||||
version='0.4.18',
|
||||
description='A library that allows your python tests to easily'
|
||||
' mock out the boto library',
|
||||
author='Steve Pulec',
|
||||
|
@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
||||
import boto
|
||||
from boto.ec2.autoscale.launchconfig import LaunchConfiguration
|
||||
from boto.ec2.autoscale.group import AutoScalingGroup
|
||||
from boto.ec2.autoscale import Tag
|
||||
import sure # noqa
|
||||
|
||||
from moto import mock_autoscaling, mock_ec2
|
||||
@ -18,6 +19,7 @@ def test_create_autoscaling_group():
|
||||
)
|
||||
conn.create_launch_configuration(config)
|
||||
|
||||
|
||||
group = AutoScalingGroup(
|
||||
name='tester_group',
|
||||
availability_zones=['us-east-1c', 'us-east-1b'],
|
||||
@ -32,6 +34,13 @@ def test_create_autoscaling_group():
|
||||
placement_group="test_placement",
|
||||
vpc_zone_identifier='subnet-1234abcd',
|
||||
termination_policies=["OldestInstance", "NewestInstance"],
|
||||
tags=[Tag(
|
||||
resource_id='tester_group',
|
||||
key='test_key',
|
||||
value='test_value',
|
||||
propagate_at_launch=True
|
||||
)
|
||||
],
|
||||
)
|
||||
conn.create_auto_scaling_group(group)
|
||||
|
||||
@ -50,6 +59,12 @@ def test_create_autoscaling_group():
|
||||
list(group.load_balancers).should.equal(["test_lb"])
|
||||
group.placement_group.should.equal("test_placement")
|
||||
list(group.termination_policies).should.equal(["OldestInstance", "NewestInstance"])
|
||||
len(list(group.tags)).should.equal(1)
|
||||
tag = list(group.tags)[0]
|
||||
tag.resource_id.should.equal('tester_group')
|
||||
tag.key.should.equal('test_key')
|
||||
tag.value.should.equal('test_value')
|
||||
tag.propagate_at_launch.should.equal(True)
|
||||
|
||||
|
||||
@mock_autoscaling
|
||||
@ -88,6 +103,7 @@ def test_create_autoscaling_groups_defaults():
|
||||
list(group.load_balancers).should.equal([])
|
||||
group.placement_group.should.equal(None)
|
||||
list(group.termination_policies).should.equal([])
|
||||
list(group.tags).should.equal([])
|
||||
|
||||
|
||||
@mock_autoscaling
|
||||
|
@ -40,7 +40,17 @@ def test_create_stack():
|
||||
|
||||
stack = conn.describe_stacks()[0]
|
||||
stack.stack_name.should.equal('test_stack')
|
||||
stack.get_template().should.equal(dummy_template)
|
||||
stack.get_template().should.equal({
|
||||
'GetTemplateResponse': {
|
||||
'GetTemplateResult': {
|
||||
'TemplateBody': dummy_template_json,
|
||||
'ResponseMetadata': {
|
||||
'RequestId': '2d06e36c-ac1d-11e0-a958-f9382b6eb86bEXAMPLE'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
@ -83,7 +93,18 @@ def test_create_stack_from_s3_url():
|
||||
|
||||
stack = conn.describe_stacks()[0]
|
||||
stack.stack_name.should.equal('new-stack')
|
||||
stack.get_template().should.equal(dummy_template)
|
||||
stack.get_template().should.equal(
|
||||
{
|
||||
'GetTemplateResponse': {
|
||||
'GetTemplateResult': {
|
||||
'TemplateBody': dummy_template_json,
|
||||
'ResponseMetadata': {
|
||||
'RequestId': '2d06e36c-ac1d-11e0-a958-f9382b6eb86bEXAMPLE'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
@ -138,7 +159,17 @@ def test_get_template_by_name():
|
||||
)
|
||||
|
||||
template = conn.get_template("test_stack")
|
||||
template.should.equal(dummy_template)
|
||||
template.should.equal({
|
||||
'GetTemplateResponse': {
|
||||
'GetTemplateResult': {
|
||||
'TemplateBody': dummy_template_json,
|
||||
'ResponseMetadata': {
|
||||
'RequestId': '2d06e36c-ac1d-11e0-a958-f9382b6eb86bEXAMPLE'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
@ -243,4 +274,13 @@ def test_stack_tags():
|
||||
# conn.update_stack("test_stack", dummy_template_json2)
|
||||
|
||||
# stack = conn.describe_stacks()[0]
|
||||
# stack.get_template().should.equal(dummy_template2)
|
||||
# stack.get_template().should.equal({
|
||||
# 'GetTemplateResponse': {
|
||||
# 'GetTemplateResult': {
|
||||
# 'TemplateBody': dummy_template_json2,
|
||||
# 'ResponseMetadata': {
|
||||
# 'RequestId': '2d06e36c-ac1d-11e0-a958-f9382b6eb86bEXAMPLE'
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# })
|
||||
|
@ -3,6 +3,7 @@ import json
|
||||
|
||||
import boto
|
||||
import boto.cloudformation
|
||||
import boto.datapipeline
|
||||
import boto.ec2
|
||||
import boto.ec2.autoscale
|
||||
import boto.ec2.elb
|
||||
@ -17,6 +18,7 @@ import sure # noqa
|
||||
from moto import (
|
||||
mock_autoscaling,
|
||||
mock_cloudformation,
|
||||
mock_datapipeline,
|
||||
mock_ec2,
|
||||
mock_elb,
|
||||
mock_iam,
|
||||
@ -287,7 +289,6 @@ def test_stack_elb_integration_with_attached_ec2_instances():
|
||||
ec2_conn = boto.ec2.connect_to_region("us-west-1")
|
||||
reservation = ec2_conn.get_all_instances()[0]
|
||||
ec2_instance = reservation.instances[0]
|
||||
instance_id = ec2_instance.id
|
||||
|
||||
load_balancer.instances[0].id.should.equal(ec2_instance.id)
|
||||
list(load_balancer.availability_zones).should.equal(['us-east1'])
|
||||
@ -1395,3 +1396,83 @@ def test_subnets_should_be_created_with_availability_zone():
|
||||
)
|
||||
subnet = vpc_conn.get_all_subnets(filters={'cidrBlock': '10.0.0.0/24'})[0]
|
||||
subnet.availability_zone.should.equal('us-west-1b')
|
||||
|
||||
|
||||
@mock_cloudformation
|
||||
@mock_datapipeline
|
||||
def test_datapipeline():
|
||||
dp_template = {
|
||||
"AWSTemplateFormatVersion": "2010-09-09",
|
||||
"Resources": {
|
||||
"dataPipeline": {
|
||||
"Properties": {
|
||||
"Activate": "true",
|
||||
"Name": "testDataPipeline",
|
||||
"PipelineObjects": [
|
||||
{
|
||||
"Fields": [
|
||||
{
|
||||
"Key": "failureAndRerunMode",
|
||||
"StringValue": "CASCADE"
|
||||
},
|
||||
{
|
||||
"Key": "scheduleType",
|
||||
"StringValue": "cron"
|
||||
},
|
||||
{
|
||||
"Key": "schedule",
|
||||
"RefValue": "DefaultSchedule"
|
||||
},
|
||||
{
|
||||
"Key": "pipelineLogUri",
|
||||
"StringValue": "s3://bucket/logs"
|
||||
},
|
||||
{
|
||||
"Key": "type",
|
||||
"StringValue": "Default"
|
||||
},
|
||||
],
|
||||
"Id": "Default",
|
||||
"Name": "Default"
|
||||
},
|
||||
{
|
||||
"Fields": [
|
||||
{
|
||||
"Key": "startDateTime",
|
||||
"StringValue": "1970-01-01T01:00:00"
|
||||
},
|
||||
{
|
||||
"Key": "period",
|
||||
"StringValue": "1 Day"
|
||||
},
|
||||
{
|
||||
"Key": "type",
|
||||
"StringValue": "Schedule"
|
||||
}
|
||||
],
|
||||
"Id": "DefaultSchedule",
|
||||
"Name": "RunOnce"
|
||||
}
|
||||
],
|
||||
"PipelineTags": []
|
||||
},
|
||||
"Type": "AWS::DataPipeline::Pipeline"
|
||||
}
|
||||
}
|
||||
}
|
||||
cf_conn = boto.cloudformation.connect_to_region("us-east-1")
|
||||
template_json = json.dumps(dp_template)
|
||||
stack_id = cf_conn.create_stack(
|
||||
"test_stack",
|
||||
template_body=template_json,
|
||||
)
|
||||
|
||||
dp_conn = boto.datapipeline.connect_to_region('us-east-1')
|
||||
data_pipelines = dp_conn.list_pipelines()
|
||||
|
||||
data_pipelines['pipelineIdList'].should.have.length_of(1)
|
||||
data_pipelines['pipelineIdList'][0]['name'].should.equal('testDataPipeline')
|
||||
|
||||
stack_resources = cf_conn.list_stack_resources(stack_id)
|
||||
stack_resources.should.have.length_of(1)
|
||||
stack_resources[0].physical_resource_id.should.equal(data_pipelines['pipelineIdList'][0]['id'])
|
||||
|
175
tests/test_datapipeline/test_datapipeline.py
Normal file
175
tests/test_datapipeline/test_datapipeline.py
Normal file
@ -0,0 +1,175 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import boto.datapipeline
|
||||
import sure # noqa
|
||||
|
||||
from moto import mock_datapipeline
|
||||
from moto.datapipeline.utils import remove_capitalization_of_dict_keys
|
||||
|
||||
|
||||
def get_value_from_fields(key, fields):
|
||||
for field in fields:
|
||||
if field['key'] == key:
|
||||
return field['stringValue']
|
||||
|
||||
|
||||
@mock_datapipeline
|
||||
def test_create_pipeline():
|
||||
conn = boto.datapipeline.connect_to_region("us-west-2")
|
||||
|
||||
res = conn.create_pipeline("mypipeline", "some-unique-id")
|
||||
|
||||
pipeline_id = res["pipelineId"]
|
||||
pipeline_descriptions = conn.describe_pipelines([pipeline_id])["pipelineDescriptionList"]
|
||||
pipeline_descriptions.should.have.length_of(1)
|
||||
|
||||
pipeline_description = pipeline_descriptions[0]
|
||||
pipeline_description['name'].should.equal("mypipeline")
|
||||
pipeline_description["pipelineId"].should.equal(pipeline_id)
|
||||
fields = pipeline_description['fields']
|
||||
|
||||
get_value_from_fields('@pipelineState', fields).should.equal("PENDING")
|
||||
get_value_from_fields('uniqueId', fields).should.equal("some-unique-id")
|
||||
|
||||
|
||||
PIPELINE_OBJECTS = [
|
||||
{
|
||||
"id": "Default",
|
||||
"name": "Default",
|
||||
"fields": [{
|
||||
"key": "workerGroup",
|
||||
"stringValue": "workerGroup"
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id": "Schedule",
|
||||
"name": "Schedule",
|
||||
"fields": [{
|
||||
"key": "startDateTime",
|
||||
"stringValue": "2012-12-12T00:00:00"
|
||||
}, {
|
||||
"key": "type",
|
||||
"stringValue": "Schedule"
|
||||
}, {
|
||||
"key": "period",
|
||||
"stringValue": "1 hour"
|
||||
}, {
|
||||
"key": "endDateTime",
|
||||
"stringValue": "2012-12-21T18:00:00"
|
||||
}]
|
||||
},
|
||||
{
|
||||
"id": "SayHello",
|
||||
"name": "SayHello",
|
||||
"fields": [{
|
||||
"key": "type",
|
||||
"stringValue": "ShellCommandActivity"
|
||||
}, {
|
||||
"key": "command",
|
||||
"stringValue": "echo hello"
|
||||
}, {
|
||||
"key": "parent",
|
||||
"refValue": "Default"
|
||||
}, {
|
||||
"key": "schedule",
|
||||
"refValue": "Schedule"
|
||||
}]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@mock_datapipeline
|
||||
def test_creating_pipeline_definition():
|
||||
conn = boto.datapipeline.connect_to_region("us-west-2")
|
||||
res = conn.create_pipeline("mypipeline", "some-unique-id")
|
||||
pipeline_id = res["pipelineId"]
|
||||
|
||||
conn.put_pipeline_definition(PIPELINE_OBJECTS, pipeline_id)
|
||||
|
||||
pipeline_definition = conn.get_pipeline_definition(pipeline_id)
|
||||
pipeline_definition['pipelineObjects'].should.have.length_of(3)
|
||||
default_object = pipeline_definition['pipelineObjects'][0]
|
||||
default_object['name'].should.equal("Default")
|
||||
default_object['id'].should.equal("Default")
|
||||
default_object['fields'].should.equal([{
|
||||
"key": "workerGroup",
|
||||
"stringValue": "workerGroup"
|
||||
}])
|
||||
|
||||
|
||||
@mock_datapipeline
|
||||
def test_describing_pipeline_objects():
|
||||
conn = boto.datapipeline.connect_to_region("us-west-2")
|
||||
res = conn.create_pipeline("mypipeline", "some-unique-id")
|
||||
pipeline_id = res["pipelineId"]
|
||||
|
||||
conn.put_pipeline_definition(PIPELINE_OBJECTS, pipeline_id)
|
||||
|
||||
objects = conn.describe_objects(["Schedule", "Default"], pipeline_id)['pipelineObjects']
|
||||
|
||||
objects.should.have.length_of(2)
|
||||
default_object = [x for x in objects if x['id'] == 'Default'][0]
|
||||
default_object['name'].should.equal("Default")
|
||||
default_object['fields'].should.equal([{
|
||||
"key": "workerGroup",
|
||||
"stringValue": "workerGroup"
|
||||
}])
|
||||
|
||||
|
||||
@mock_datapipeline
|
||||
def test_activate_pipeline():
|
||||
conn = boto.datapipeline.connect_to_region("us-west-2")
|
||||
|
||||
res = conn.create_pipeline("mypipeline", "some-unique-id")
|
||||
|
||||
pipeline_id = res["pipelineId"]
|
||||
conn.activate_pipeline(pipeline_id)
|
||||
|
||||
pipeline_descriptions = conn.describe_pipelines([pipeline_id])["pipelineDescriptionList"]
|
||||
pipeline_descriptions.should.have.length_of(1)
|
||||
pipeline_description = pipeline_descriptions[0]
|
||||
fields = pipeline_description['fields']
|
||||
|
||||
get_value_from_fields('@pipelineState', fields).should.equal("SCHEDULED")
|
||||
|
||||
|
||||
@mock_datapipeline
|
||||
def test_listing_pipelines():
|
||||
conn = boto.datapipeline.connect_to_region("us-west-2")
|
||||
res1 = conn.create_pipeline("mypipeline1", "some-unique-id1")
|
||||
res2 = conn.create_pipeline("mypipeline2", "some-unique-id2")
|
||||
|
||||
response = conn.list_pipelines()
|
||||
|
||||
response["hasMoreResults"].should.be(False)
|
||||
response["marker"].should.be.none
|
||||
response["pipelineIdList"].should.have.length_of(2)
|
||||
response["pipelineIdList"].should.contain({
|
||||
"id": res1["pipelineId"],
|
||||
"name": "mypipeline1",
|
||||
})
|
||||
response["pipelineIdList"].should.contain({
|
||||
"id": res2["pipelineId"],
|
||||
"name": "mypipeline2"
|
||||
})
|
||||
|
||||
|
||||
# testing a helper function
|
||||
def test_remove_capitalization_of_dict_keys():
|
||||
result = remove_capitalization_of_dict_keys(
|
||||
{
|
||||
"Id": "IdValue",
|
||||
"Fields": [{
|
||||
"Key": "KeyValue",
|
||||
"StringValue": "StringValueValue"
|
||||
}]
|
||||
}
|
||||
)
|
||||
|
||||
result.should.equal({
|
||||
"id": "IdValue",
|
||||
"fields": [{
|
||||
"key": "KeyValue",
|
||||
"stringValue": "StringValueValue"
|
||||
}],
|
||||
})
|
27
tests/test_datapipeline/test_server.py
Normal file
27
tests/test_datapipeline/test_server.py
Normal file
@ -0,0 +1,27 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
import sure # noqa
|
||||
|
||||
import moto.server as server
|
||||
from moto import mock_datapipeline
|
||||
|
||||
'''
|
||||
Test the different server responses
|
||||
'''
|
||||
|
||||
|
||||
@mock_datapipeline
|
||||
def test_list_streams():
|
||||
backend = server.create_backend_app("datapipeline")
|
||||
test_client = backend.test_client()
|
||||
|
||||
res = test_client.post('/',
|
||||
data={"pipelineIds": ["ASdf"]},
|
||||
headers={"X-Amz-Target": "DataPipeline.DescribePipelines"},
|
||||
)
|
||||
|
||||
json_data = json.loads(res.data.decode("utf-8"))
|
||||
json_data.should.equal({
|
||||
'pipelineDescriptionList': []
|
||||
})
|
@ -122,6 +122,33 @@ def test_item_add_and_describe_and_update():
|
||||
})
|
||||
|
||||
|
||||
@requires_boto_gte("2.9")
|
||||
@mock_dynamodb2
|
||||
def test_item_partial_save():
|
||||
table = create_table()
|
||||
|
||||
data = {
|
||||
'forum_name': 'LOLCat Forum',
|
||||
'Body': 'http://url_to_lolcat.gif',
|
||||
'SentBy': 'User A',
|
||||
}
|
||||
|
||||
table.put_item(data=data)
|
||||
returned_item = table.get_item(forum_name="LOLCat Forum")
|
||||
|
||||
returned_item['SentBy'] = 'User B'
|
||||
returned_item.partial_save()
|
||||
|
||||
returned_item = table.get_item(
|
||||
forum_name='LOLCat Forum'
|
||||
)
|
||||
dict(returned_item).should.equal({
|
||||
'forum_name': 'LOLCat Forum',
|
||||
'Body': 'http://url_to_lolcat.gif',
|
||||
'SentBy': 'User B',
|
||||
})
|
||||
|
||||
|
||||
@requires_boto_gte("2.9")
|
||||
@mock_dynamodb2
|
||||
def test_item_put_without_table():
|
||||
|
@ -33,6 +33,7 @@ def test_create_and_delete_volume():
|
||||
cm.exception.status.should.equal(400)
|
||||
cm.exception.request_id.should_not.be.none
|
||||
|
||||
|
||||
@mock_ec2
|
||||
def test_filter_volume_by_id():
|
||||
conn = boto.connect_ec2('the_key', 'the_secret')
|
||||
@ -93,7 +94,9 @@ def test_create_snapshot():
|
||||
conn = boto.connect_ec2('the_key', 'the_secret')
|
||||
volume = conn.create_volume(80, "us-east-1a")
|
||||
|
||||
volume.create_snapshot('a test snapshot')
|
||||
snapshot = volume.create_snapshot('a test snapshot')
|
||||
snapshot.update()
|
||||
snapshot.status.should.equal('completed')
|
||||
|
||||
snapshots = conn.get_all_snapshots()
|
||||
snapshots.should.have.length_of(1)
|
||||
@ -114,6 +117,7 @@ def test_create_snapshot():
|
||||
cm.exception.status.should.equal(400)
|
||||
cm.exception.request_id.should_not.be.none
|
||||
|
||||
|
||||
@mock_ec2
|
||||
def test_filter_snapshot_by_id():
|
||||
conn = boto.connect_ec2('the_key', 'the_secret')
|
||||
@ -134,6 +138,7 @@ def test_filter_snapshot_by_id():
|
||||
s.volume_id.should.be.within([volume2.id, volume3.id])
|
||||
s.region.name.should.equal(conn.region.name)
|
||||
|
||||
|
||||
@mock_ec2
|
||||
def test_snapshot_attribute():
|
||||
conn = boto.connect_ec2('the_key', 'the_secret')
|
||||
@ -215,6 +220,20 @@ def test_snapshot_attribute():
|
||||
user_ids=['user']).should.throw(NotImplementedError)
|
||||
|
||||
|
||||
@mock_ec2
|
||||
def test_create_volume_from_snapshot():
|
||||
conn = boto.connect_ec2('the_key', 'the_secret')
|
||||
volume = conn.create_volume(80, "us-east-1a")
|
||||
|
||||
snapshot = volume.create_snapshot('a test snapshot')
|
||||
snapshot.update()
|
||||
snapshot.status.should.equal('completed')
|
||||
|
||||
new_volume = snapshot.create_volume('us-east-1a')
|
||||
new_volume.size.should.equal(80)
|
||||
new_volume.snapshot_id.should.equal(snapshot.id)
|
||||
|
||||
|
||||
@mock_ec2
|
||||
def test_modify_attribute_blockDeviceMapping():
|
||||
"""
|
||||
@ -234,3 +253,13 @@ def test_modify_attribute_blockDeviceMapping():
|
||||
instance = ec2_backends[conn.region.name].get_instance(instance.id)
|
||||
instance.block_device_mapping.should.have.key('/dev/sda1')
|
||||
instance.block_device_mapping['/dev/sda1'].delete_on_termination.should.be(True)
|
||||
|
||||
|
||||
@mock_ec2
|
||||
def test_volume_tag_escaping():
|
||||
conn = boto.connect_ec2('the_key', 'the_secret')
|
||||
vol = conn.create_volume(10, 'us-east-1a')
|
||||
snapshot = conn.create_snapshot(vol.id, 'Desc')
|
||||
snapshot.add_tags({'key': '</closed>'})
|
||||
|
||||
dict(conn.get_all_snapshots()[0].tags).should.equal({'key': '</closed>'})
|
||||
|
@ -53,7 +53,7 @@ def test_instance_launch_and_terminate():
|
||||
instances.should.have.length_of(1)
|
||||
instances[0].id.should.equal(instance.id)
|
||||
instances[0].state.should.equal('running')
|
||||
instances[0].launch_time.should.equal("2014-01-01T05:00:00Z")
|
||||
instances[0].launch_time.should.equal("2014-01-01T05:00:00.000Z")
|
||||
instances[0].vpc_id.should.equal(None)
|
||||
|
||||
root_device_name = instances[0].root_device_name
|
||||
|
@ -85,3 +85,27 @@ def test_key_pairs_delete_exist():
|
||||
r = conn.delete_key_pair('foo')
|
||||
r.should.be.ok
|
||||
assert len(conn.get_all_key_pairs()) == 0
|
||||
|
||||
|
||||
@mock_ec2
|
||||
def test_key_pairs_import():
|
||||
conn = boto.connect_ec2('the_key', 'the_secret')
|
||||
kp = conn.import_key_pair('foo', b'content')
|
||||
assert kp.name == 'foo'
|
||||
kps = conn.get_all_key_pairs()
|
||||
assert len(kps) == 1
|
||||
assert kps[0].name == 'foo'
|
||||
|
||||
|
||||
@mock_ec2
|
||||
def test_key_pairs_import_exist():
|
||||
conn = boto.connect_ec2('the_key', 'the_secret')
|
||||
kp = conn.import_key_pair('foo', b'content')
|
||||
assert kp.name == 'foo'
|
||||
assert len(conn.get_all_key_pairs()) == 1
|
||||
|
||||
with assert_raises(EC2ResponseError) as cm:
|
||||
conn.create_key_pair('foo')
|
||||
cm.exception.code.should.equal('InvalidKeyPair.Duplicate')
|
||||
cm.exception.status.should.equal(400)
|
||||
cm.exception.request_id.should_not.be.none
|
||||
|
8
tests/test_ec2/test_utils.py
Normal file
8
tests/test_ec2/test_utils.py
Normal file
@ -0,0 +1,8 @@
|
||||
from moto.ec2 import utils
|
||||
|
||||
|
||||
def test_random_key_pair():
|
||||
key_pair = utils.random_key_pair()
|
||||
assert len(key_pair['fingerprint']) == 59
|
||||
assert key_pair['material'].startswith('---- BEGIN RSA PRIVATE KEY ----')
|
||||
assert key_pair['material'].endswith('-----END RSA PRIVATE KEY-----')
|
141
tests/test_kinesis/test_firehose.py
Normal file
141
tests/test_kinesis/test_firehose.py
Normal file
@ -0,0 +1,141 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
|
||||
from botocore.exceptions import ClientError
|
||||
import boto3
|
||||
from freezegun import freeze_time
|
||||
import sure # noqa
|
||||
|
||||
from moto import mock_kinesis
|
||||
|
||||
|
||||
def create_stream(client, stream_name):
|
||||
return client.create_delivery_stream(
|
||||
DeliveryStreamName=stream_name,
|
||||
RedshiftDestinationConfiguration={
|
||||
'RoleARN': 'arn:aws:iam::123456789012:role/firehose_delivery_role',
|
||||
'ClusterJDBCURL': 'jdbc:redshift://host.amazonaws.com:5439/database',
|
||||
'CopyCommand': {
|
||||
'DataTableName': 'outputTable',
|
||||
'CopyOptions': "CSV DELIMITER ',' NULL '\\0'"
|
||||
},
|
||||
'Username': 'username',
|
||||
'Password': 'password',
|
||||
'S3Configuration': {
|
||||
'RoleARN': 'arn:aws:iam::123456789012:role/firehose_delivery_role',
|
||||
'BucketARN': 'arn:aws:s3:::kinesis-test',
|
||||
'Prefix': 'myFolder/',
|
||||
'BufferingHints': {
|
||||
'SizeInMBs': 123,
|
||||
'IntervalInSeconds': 124
|
||||
},
|
||||
'CompressionFormat': 'UNCOMPRESSED',
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@mock_kinesis
|
||||
@freeze_time("2015-03-01")
|
||||
def test_create_stream():
|
||||
client = boto3.client('firehose', region_name='us-east-1')
|
||||
|
||||
response = create_stream(client, 'stream1')
|
||||
stream_arn = response['DeliveryStreamARN']
|
||||
|
||||
response = client.describe_delivery_stream(DeliveryStreamName='stream1')
|
||||
stream_description = response['DeliveryStreamDescription']
|
||||
|
||||
# Sure and Freezegun don't play nicely together
|
||||
created = stream_description.pop('CreateTimestamp')
|
||||
last_updated = stream_description.pop('LastUpdateTimestamp')
|
||||
from dateutil.tz import tzlocal
|
||||
assert created == datetime.datetime(2015, 3, 1, tzinfo=tzlocal())
|
||||
assert last_updated == datetime.datetime(2015, 3, 1, tzinfo=tzlocal())
|
||||
|
||||
stream_description.should.equal({
|
||||
'DeliveryStreamName': 'stream1',
|
||||
'DeliveryStreamARN': stream_arn,
|
||||
'DeliveryStreamStatus': 'ACTIVE',
|
||||
'VersionId': 'string',
|
||||
'Destinations': [
|
||||
{
|
||||
'DestinationId': 'string',
|
||||
'RedshiftDestinationDescription': {
|
||||
'RoleARN': 'arn:aws:iam::123456789012:role/firehose_delivery_role',
|
||||
'ClusterJDBCURL': 'jdbc:redshift://host.amazonaws.com:5439/database',
|
||||
'CopyCommand': {
|
||||
'DataTableName': 'outputTable',
|
||||
'CopyOptions': "CSV DELIMITER ',' NULL '\\0'"
|
||||
},
|
||||
'Username': 'username',
|
||||
'S3DestinationDescription': {
|
||||
'RoleARN': 'arn:aws:iam::123456789012:role/firehose_delivery_role',
|
||||
'BucketARN': 'arn:aws:s3:::kinesis-test',
|
||||
'Prefix': 'myFolder/',
|
||||
'BufferingHints': {
|
||||
'SizeInMBs': 123,
|
||||
'IntervalInSeconds': 124
|
||||
},
|
||||
'CompressionFormat': 'UNCOMPRESSED',
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
"HasMoreDestinations": False,
|
||||
})
|
||||
|
||||
|
||||
@mock_kinesis
|
||||
@freeze_time("2015-03-01")
|
||||
def test_deescribe_non_existant_stream():
|
||||
client = boto3.client('firehose', region_name='us-east-1')
|
||||
|
||||
client.describe_delivery_stream.when.called_with(DeliveryStreamName='not-a-stream').should.throw(ClientError)
|
||||
|
||||
|
||||
@mock_kinesis
|
||||
@freeze_time("2015-03-01")
|
||||
def test_list_and_delete_stream():
|
||||
client = boto3.client('firehose', region_name='us-east-1')
|
||||
|
||||
create_stream(client, 'stream1')
|
||||
create_stream(client, 'stream2')
|
||||
|
||||
set(client.list_delivery_streams()['DeliveryStreamNames']).should.equal(set(['stream1', 'stream2']))
|
||||
|
||||
client.delete_delivery_stream(DeliveryStreamName='stream1')
|
||||
|
||||
set(client.list_delivery_streams()['DeliveryStreamNames']).should.equal(set(['stream2']))
|
||||
|
||||
|
||||
@mock_kinesis
|
||||
def test_put_record():
|
||||
client = boto3.client('firehose', region_name='us-east-1')
|
||||
|
||||
create_stream(client, 'stream1')
|
||||
client.put_record(
|
||||
DeliveryStreamName='stream1',
|
||||
Record={
|
||||
'Data': 'some data'
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@mock_kinesis
|
||||
def test_put_record_batch():
|
||||
client = boto3.client('firehose', region_name='us-east-1')
|
||||
|
||||
create_stream(client, 'stream1')
|
||||
client.put_record_batch(
|
||||
DeliveryStreamName='stream1',
|
||||
Records=[
|
||||
{
|
||||
'Data': 'some data1'
|
||||
},
|
||||
{
|
||||
'Data': 'some data2'
|
||||
},
|
||||
]
|
||||
)
|
@ -239,3 +239,25 @@ def test_deleting_weighted_route():
|
||||
cname = zone.get_cname('cname.testdns.aws.com.', all=True)
|
||||
# When get_cname only had one result, it returns just that result instead of a list.
|
||||
cname.identifier.should.equal('success-test-bar')
|
||||
|
||||
|
||||
@mock_route53
|
||||
def test_deleting_latency_route():
|
||||
conn = boto.connect_route53()
|
||||
|
||||
conn.create_hosted_zone("testdns.aws.com.")
|
||||
zone = conn.get_zone("testdns.aws.com.")
|
||||
|
||||
zone.add_cname("cname.testdns.aws.com", "example.com", identifier=('success-test-foo', 'us-west-2'))
|
||||
zone.add_cname("cname.testdns.aws.com", "example.com", identifier=('success-test-bar', 'us-west-1'))
|
||||
|
||||
cnames = zone.get_cname('cname.testdns.aws.com.', all=True)
|
||||
cnames.should.have.length_of(2)
|
||||
foo_cname = [cname for cname in cnames if cname.identifier == 'success-test-foo'][0]
|
||||
foo_cname.region.should.equal('us-west-2')
|
||||
|
||||
zone.delete_record(foo_cname)
|
||||
cname = zone.get_cname('cname.testdns.aws.com.', all=True)
|
||||
# When get_cname only had one result, it returns just that result instead of a list.
|
||||
cname.identifier.should.equal('success-test-bar')
|
||||
cname.region.should.equal('us-west-1')
|
||||
|
@ -726,7 +726,7 @@ def test_list_versions():
|
||||
|
||||
|
||||
@mock_s3
|
||||
def test_acl_is_ignored_for_now():
|
||||
def test_acl_setting():
|
||||
conn = boto.connect_s3()
|
||||
bucket = conn.create_bucket('foobar')
|
||||
content = b'imafile'
|
||||
@ -741,6 +741,74 @@ def test_acl_is_ignored_for_now():
|
||||
|
||||
assert key.get_contents_as_string() == content
|
||||
|
||||
grants = key.get_acl().acl.grants
|
||||
assert any(g.uri == 'http://acs.amazonaws.com/groups/global/AllUsers' and
|
||||
g.permission == 'READ' for g in grants), grants
|
||||
|
||||
|
||||
@mock_s3
|
||||
def test_acl_setting_via_headers():
|
||||
conn = boto.connect_s3()
|
||||
bucket = conn.create_bucket('foobar')
|
||||
content = b'imafile'
|
||||
keyname = 'test.txt'
|
||||
|
||||
key = Key(bucket, name=keyname)
|
||||
key.content_type = 'text/plain'
|
||||
key.set_contents_from_string(content, headers={
|
||||
'x-amz-grant-full-control': 'uri="http://acs.amazonaws.com/groups/global/AllUsers"'
|
||||
})
|
||||
|
||||
key = bucket.get_key(keyname)
|
||||
|
||||
assert key.get_contents_as_string() == content
|
||||
|
||||
grants = key.get_acl().acl.grants
|
||||
assert any(g.uri == 'http://acs.amazonaws.com/groups/global/AllUsers' and
|
||||
g.permission == 'FULL_CONTROL' for g in grants), grants
|
||||
|
||||
|
||||
@mock_s3
|
||||
def test_acl_switching():
|
||||
conn = boto.connect_s3()
|
||||
bucket = conn.create_bucket('foobar')
|
||||
content = b'imafile'
|
||||
keyname = 'test.txt'
|
||||
|
||||
key = Key(bucket, name=keyname)
|
||||
key.content_type = 'text/plain'
|
||||
key.set_contents_from_string(content, policy='public-read')
|
||||
key.set_acl('private')
|
||||
|
||||
grants = key.get_acl().acl.grants
|
||||
assert not any(g.uri == 'http://acs.amazonaws.com/groups/global/AllUsers' and
|
||||
g.permission == 'READ' for g in grants), grants
|
||||
|
||||
|
||||
@mock_s3
|
||||
def test_bucket_acl_setting():
|
||||
conn = boto.connect_s3()
|
||||
bucket = conn.create_bucket('foobar')
|
||||
|
||||
bucket.make_public()
|
||||
|
||||
grants = bucket.get_acl().acl.grants
|
||||
assert any(g.uri == 'http://acs.amazonaws.com/groups/global/AllUsers' and
|
||||
g.permission == 'READ' for g in grants), grants
|
||||
|
||||
|
||||
@mock_s3
|
||||
def test_bucket_acl_switching():
|
||||
conn = boto.connect_s3()
|
||||
bucket = conn.create_bucket('foobar')
|
||||
bucket.make_public()
|
||||
|
||||
bucket.set_acl('private')
|
||||
|
||||
grants = bucket.get_acl().acl.grants
|
||||
assert not any(g.uri == 'http://acs.amazonaws.com/groups/global/AllUsers' and
|
||||
g.permission == 'READ' for g in grants), grants
|
||||
|
||||
|
||||
@mock_s3
|
||||
def test_unicode_key():
|
||||
@ -902,5 +970,33 @@ def test_boto3_head_object():
|
||||
|
||||
s3.Object('blah', 'hello.txt').meta.client.head_object(Bucket='blah', Key='hello.txt')
|
||||
|
||||
with assert_raises(ClientError) as err:
|
||||
with assert_raises(ClientError):
|
||||
s3.Object('blah', 'hello2.txt').meta.client.head_object(Bucket='blah', Key='hello_bad.txt')
|
||||
|
||||
|
||||
TEST_XML = """\
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ns0:WebsiteConfiguration xmlns:ns0="http://s3.amazonaws.com/doc/2006-03-01/">
|
||||
<ns0:IndexDocument>
|
||||
<ns0:Suffix>index.html</ns0:Suffix>
|
||||
</ns0:IndexDocument>
|
||||
<ns0:RoutingRules>
|
||||
<ns0:RoutingRule>
|
||||
<ns0:Condition>
|
||||
<ns0:KeyPrefixEquals>test/testing</ns0:KeyPrefixEquals>
|
||||
</ns0:Condition>
|
||||
<ns0:Redirect>
|
||||
<ns0:ReplaceKeyWith>test.txt</ns0:ReplaceKeyWith>
|
||||
</ns0:Redirect>
|
||||
</ns0:RoutingRule>
|
||||
</ns0:RoutingRules>
|
||||
</ns0:WebsiteConfiguration>
|
||||
"""
|
||||
|
||||
|
||||
@mock_s3
|
||||
def test_website_configuration_xml():
|
||||
conn = boto.connect_s3()
|
||||
bucket = conn.create_bucket('test-bucket')
|
||||
bucket.set_website_configuration_xml(TEST_XML)
|
||||
bucket.get_website_configuration_xml().should.equal(TEST_XML)
|
||||
|
@ -281,3 +281,40 @@ def test_bucket_key_listing_order():
|
||||
delimiter = '/'
|
||||
keys = [x.name for x in bucket.list(prefix + 'x', delimiter)]
|
||||
keys.should.equal(['toplevel/x/'])
|
||||
|
||||
|
||||
@mock_s3bucket_path
|
||||
def test_delete_keys():
|
||||
conn = create_connection()
|
||||
bucket = conn.create_bucket('foobar')
|
||||
|
||||
Key(bucket=bucket, name='file1').set_contents_from_string('abc')
|
||||
Key(bucket=bucket, name='file2').set_contents_from_string('abc')
|
||||
Key(bucket=bucket, name='file3').set_contents_from_string('abc')
|
||||
Key(bucket=bucket, name='file4').set_contents_from_string('abc')
|
||||
|
||||
result = bucket.delete_keys(['file2', 'file3'])
|
||||
result.deleted.should.have.length_of(2)
|
||||
result.errors.should.have.length_of(0)
|
||||
keys = bucket.get_all_keys()
|
||||
keys.should.have.length_of(2)
|
||||
keys[0].name.should.equal('file1')
|
||||
|
||||
|
||||
@mock_s3bucket_path
|
||||
def test_delete_keys_with_invalid():
|
||||
conn = create_connection()
|
||||
bucket = conn.create_bucket('foobar')
|
||||
|
||||
Key(bucket=bucket, name='file1').set_contents_from_string('abc')
|
||||
Key(bucket=bucket, name='file2').set_contents_from_string('abc')
|
||||
Key(bucket=bucket, name='file3').set_contents_from_string('abc')
|
||||
Key(bucket=bucket, name='file4').set_contents_from_string('abc')
|
||||
|
||||
result = bucket.delete_keys(['abc', 'file3'])
|
||||
|
||||
result.deleted.should.have.length_of(1)
|
||||
result.errors.should.have.length_of(1)
|
||||
keys = bucket.get_all_keys()
|
||||
keys.should.have.length_of(3)
|
||||
keys[0].name.should.equal('file1')
|
||||
|
@ -34,6 +34,8 @@ def test_create_queues_in_multiple_region():
|
||||
list(west1_conn.get_all_queues()).should.have.length_of(1)
|
||||
list(west2_conn.get_all_queues()).should.have.length_of(1)
|
||||
|
||||
west1_conn.get_all_queues()[0].url.should.equal('http://sqs.us-west-1.amazonaws.com/123456789012/test-queue')
|
||||
|
||||
|
||||
@mock_sqs
|
||||
def test_get_queue():
|
||||
@ -168,6 +170,18 @@ def test_send_message_with_delay():
|
||||
queue.count().should.equal(0)
|
||||
|
||||
|
||||
@mock_sqs
|
||||
def test_send_large_message_fails():
|
||||
conn = boto.connect_sqs('the_key', 'the_secret')
|
||||
queue = conn.create_queue("test-queue", visibility_timeout=60)
|
||||
queue.set_message_class(RawMessage)
|
||||
|
||||
body_one = 'test message' * 200000
|
||||
huge_message = queue.new_message(body_one)
|
||||
|
||||
queue.write.when.called_with(huge_message).should.throw(SQSError)
|
||||
|
||||
|
||||
@mock_sqs
|
||||
def test_message_becomes_inflight_when_received():
|
||||
conn = boto.connect_sqs('the_key', 'the_secret')
|
||||
|
Loading…
Reference in New Issue
Block a user